Actualizado moodle a v3.10 en servidor de 1and1/ionos

Ya tocaba. Realizo la pertinente copia de seguridad, cambio la versión de MySQL, convierto las tablas de UTF8 a utf8mb4 para que puedan contener «emojis», habilito en el servidor la versión recomendada de PHP (7.4) que de las otras ya no hay soporte, ejecuto el script de actualización y cruzo los dedos (lo de aguantar la respiración, paso, que le lleva 10min…) TODO OK!

Hasta que descubres que las imágenes de resumen de los cursos , las fotos de tus repositorios de preguntas y algún que otro elemento ahora ya no se muestra. Vale. Que no cunda el pánico…..

Veamos:

  1. Revisando permisos en la carpeta de archivos….. todo OK.
  2. Revisando los registros en la base de datos, comparando los «hash» y localizando el documento original….Pues sí, están ahí.
  3. Cambiando de «tema» visual….. Nada, sigue igual.
  4. ¿Problema al migrar las tablas? ¿Se ha producido alguna actualización de las URLs? No debería. Veamos el código HTML del objeto que no se muestra….
https://diocesanos.es/moodle/pluginfile.php/13019/course/overviewfiles/tic.jpg

… y con la versión anterior era algo así como:

https://diocesanos.es/moodle/pluginfile.php?file=/13019/course/overviewfiles/tic.jpg

YA ESTA! Al realizar la actualización se ha abilitado por defecto la opción «slasharguments». Vamos a la configuración del servidor de moodle y….

O será la revés. Qué pasará si la activo…. DESASTRE! Adiós a los estilos CSS y a todas las imágenes (por lo menos).

NOTA: el servidor linux de 1and1 / Ionos  NO soporta el uso de «slasharguments» de ninguna forma (al menos por ahora), y no puede habilitarse mediante archivos «.htaccess»

No existe opción, parámetro o ajuste en la configuración de Moodle, del servidor (las que me dejan tocar) o del sistema de archivos que muestre como antes (v3.5)  las imágenes que «han desaparecido» (y las he probado todas).

«Googleando» un poco descubro que el problema comienza precisamente al actualizar la versión 3.5 en adelante, y se da principalmente en instalaciones con 1and1…..pero nadie da una solución satisfactoria…hasta que llego a un post [MDL-28538] que hace referencia a un «error» y propuesta de corrección sobre el uso de la variable «PATH_INFO» del servidor… o eso intenta porque dicha variable nunca llega a definirse!

El fichero «weblib.php»

Moodle tiene una «librería» de funciones para ayudar generar los contenidos web en «moodle/lib/weblib.php«, y entre otras muchas útiles proporciona la función «get_file_argument()» a partir de la línea 1116: 

/**
 * Extracts file argument either from file parameter or PATH_INFO
 *
 * Note: $scriptname parameter is not needed anymore
 *
 * @return string file path (only safe characters)
 */
function get_file_argument() {
    global $SCRIPT;

    $relativepath = false;
    $hasforcedslashargs = false;

    if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
        // Checks whether $_SERVER['REQUEST_URI'] contains '/pluginfile.php/'
        // instead of '/pluginfile.php?', when serving a file from e.g. mod_imscp or mod_scorm.
        if ((strpos($_SERVER['REQUEST_URI'], '/pluginfile.php/') !== false)
                && isset($_SERVER['PATH_INFO']) && !empty($_SERVER['PATH_INFO'])) {
            // Exclude edge cases like '/pluginfile.php/?file='.
            $args = explode('/', ltrim($_SERVER['PATH_INFO'], '/'));
            $hasforcedslashargs = (count($args) > 2); // Always at least: context, component and filearea.
        }
    }
    if (!$hasforcedslashargs) {
        $relativepath = optional_param('file', false, PARAM_PATH);
    }

    if ($relativepath !== false and $relativepath !== '') {
        return $relativepath;
    }
    $relativepath = false;

    // Then try extract file from the slasharguments.
    if (stripos($_SERVER['SERVER_SOFTWARE'], 'iis') !== false) {
        // NOTE: IIS tends to convert all file paths to single byte DOS encoding,
        //       we can not use other methods because they break unicode chars,
        //       the only ways are to use URL rewriting
        //       OR
        //       to properly set the 'FastCGIUtf8ServerVariables' registry key.
        if (isset($_SERVER['PATH_INFO']) and $_SERVER['PATH_INFO'] !== '') {
            // Check that PATH_INFO works == must not contain the script name.
            if (strpos($_SERVER['PATH_INFO'], $SCRIPT) === false) {
                $relativepath = clean_param(urldecode($_SERVER['PATH_INFO']), PARAM_PATH);
            }
        }
    } else {
        // All other apache-like servers depend on PATH_INFO.
        if (isset($_SERVER['PATH_INFO'])) {
            if (isset($_SERVER['SCRIPT_NAME']) and strpos($_SERVER['PATH_INFO'], $_SERVER['SCRIPT_NAME']) === 0) {
                $relativepath = substr($_SERVER['PATH_INFO'], strlen($_SERVER['SCRIPT_NAME']));
            } else {
                $relativepath = $_SERVER['PATH_INFO'];
            }
            $relativepath = clean_param($relativepath, PARAM_PATH);
        }
    }

    return $relativepath;
}

Si insertásemos la línea

 echo var_export($_SERVER, true);

al inicio de la misma y recargásemos la URL de la imagen que echamos en falta en moodle obtendríamos (en un servidor de 1and1) algo similar a esto:

array (
 'REDIRECT_UNIQUE_ID' => 'X-xxxxxxxxxxx', 
 'REDIRECT_DOCUMENT_ROOT' => '/kunden/homepages/x/xxxxx/x/xxxxx/htdocs', 
 'REDIRECT_HTTPS' => 'on', 
 'REDIRECT_HANDLER' => 'x-mapp-php5.4',
 'REDIRECT_STATUS' => '200', 
 'UNIQUE_ID' => 'X-xxxxxxxxxxx', 
 'HTTPS' => 'on', 
 'HTTP_HOST' => 'diocesanos.es', 
 'HTTP_USER_AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0', 
 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 
 'HTTP_ACCEPT_LANGUAGE' => 'es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3', 
 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate, br', 
 'HTTP_UPGRADE_INSECURE_REQUESTS' => '1', 
 'HTTP_CACHE_CONTROL' => 'max-age=0', 
 'HTTP_COOKIE' => 'xxxxxxxxxxx', 
 'PATH' => '/bin:/usr/bin', 
 'SERVER_SIGNATURE' => '', 
 'SERVER_SOFTWARE' => 'Apache', 
 'SERVER_NAME' => 'diocesanos.es', 
 'SERVER_ADDR' => '11.22.33.44', 
 'SERVER_PORT' => '443', 
 'REMOTE_ADDR' => '55.66.77.88', 
 'DOCUMENT_ROOT' => '/kunden/homepages/x/xxxxx/x/xxxxx/htdocs', 
 'REQUEST_SCHEME' => 'https', 
 'CONTEXT_PREFIX' => '/system-bin/', 
 'CONTEXT_DOCUMENT_ROOT' => '/kunden/usr/lib/cgi-bin/', 
 'SERVER_ADMIN' => 'webmaster@diocesanos.es', 
 'SCRIPT_FILENAME' => '/kunden/homepages/x/xxxxx/x/xxxxx/htdocs/moodle/pluginfile.php', 
 'REMOTE_PORT' => '25220', 
 'REDIRECT_URL' => '/moodle/pluginfile.php/93019/course/overviewfiles/tic.jpg', 
 'GATEWAY_INTERFACE' => 'CGI/1.1', 
 'SERVER_PROTOCOL' => 'HTTP/1.1', 
 'REQUEST_METHOD' => 'GET', 
 'QUERY_STRING' => '', 
 'REQUEST_URI' => '/moodle/pluginfile.php/93019/course/overviewfiles/tic.jpg', 
 'SCRIPT_NAME' => '/moodle/pluginfile.php', 
 'STATUS' => '200', 
 'ORIG_PATH_INFO' => '/93019/course/overviewfiles/tic.jpg', 
 'ORIG_PATH_TRANSLATED' => '/kunden/homepages/x/xxxxx/x/xxxxx/htdocs/moodle/pluginfile.php', 
 'PHP_SELF' => '/moodle/pluginfile.php', 
 'REQUEST_TIME_FLOAT' => 1609568156.633002, 'REQUEST_TIME' => 1609568156, 'argv' => array ( ), 'argc' => 0,
 ) 

… y por ningún lado aparece la variable «PATH_INFO«, sino «ORIG_PATH_INFO«.

Solución

Hagamos algunos cambios a la función:

/**
 * Extracts file argument either from file parameter or PATH_INFO
 *
 * Note: $scriptname parameter is not needed anymore
 *
 * @return string file path (only safe characters)
 */
function get_file_argument() {
    global $SCRIPT;

    $relativepath = false;
    $hasforcedslashargs = false;
    $pathinfo = '';

    if (isset($_SERVER['PATH_INFO'])) {
        $pathinfo = $_SERVER['PATH_INFO'];
    } else if (isset($_SERVER['ORIG_PATH_INFO'])) {
        $pathinfo = $_SERVER['ORIG_PATH_INFO'];
    }

    if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
        // Checks whether $_SERVER['REQUEST_URI'] contains '/pluginfile.php/'
        // instead of '/pluginfile.php?', when serving a file from e.g. mod_imscp or mod_scorm.
        if ((strpos($_SERVER['REQUEST_URI'], '/pluginfile.php/') !== false)
                && !empty($pathinfo)) {
            // Exclude edge cases like '/pluginfile.php/?file='.
            $args = explode('/', ltrim($pathinfo, '/'));
            $hasforcedslashargs = (count($args) > 2); // Always at least: context, component and filearea.
        }
    }
    if (!$hasforcedslashargs) {
        $relativepath = optional_param('file', false, PARAM_PATH);
    }

    if ($relativepath !== false and $relativepath !== '') {
        return $relativepath;
    }
    $relativepath = false;

    // Then try extract file from the slasharguments.
    if (stripos($_SERVER['SERVER_SOFTWARE'], 'iis') !== false) {
        // NOTE: IIS tends to convert all file paths to single byte DOS encoding,
        //       we can not use other methods because they break unicode chars,
        //       the only ways are to use URL rewriting
        //       OR
        //       to properly set the 'FastCGIUtf8ServerVariables' registry key.
        if ($pathinfo !== '') {
            // Check that PATH_INFO works == must not contain the script name.
            if (strpos($pathinfo, $SCRIPT) === false) {
                $relativepath = clean_param(urldecode($pathinfo), PARAM_PATH);
            }
        }
    } else {
        // All other apache-like servers depend on PATH_INFO.
        if ($pathinfo !== '') {
            if (isset($_SERVER['SCRIPT_NAME']) and strpos($pathinfo, $_SERVER['SCRIPT_NAME']) === 0) {
                $relativepath = substr($pathinfo, strlen($_SERVER['SCRIPT_NAME']));
            } else {
                $relativepath = $pathinfo;
            }
            $relativepath = clean_param($relativepath, PARAM_PATH);
        }
    }

    return $relativepath;
}

… sustituimos el archivo «weblib.php» por el modificado y EUREKA! Todo vuelve a mostrarse como debería haberlo hecho a la primera.

IMPORTANTE: este cambio debemos realizarlo con cada nueva actualización, a no ser que ya incorporen la correción desde el archivo «weblib.php» original.