Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/lib/ -> requirejs.php (source)
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * This file is serving optimised JS for RequireJS.
 *
 * @package    core
 * @copyright  2015 Damyon Wiese
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

// Disable moodle specific debug messages and any errors in output,
// comment out when debugging or better look into error log!
define('NO_DEBUG_DISPLAY', true);

// We need just the values from config.php and minlib.php.
define('ABORT_AFTER_CONFIG', true);
require('../config.php'); // This stops immediately at the beginning of lib/setup.php.
require_once("$CFG->dirroot/lib/jslib.php");
require_once("$CFG->dirroot/lib/classes/requirejs.php");

$slashargument = min_get_slash_argument();
if (!$slashargument) {
    // The above call to min_get_slash_argument should always work.
    die('Invalid request');
}

$slashargument = ltrim($slashargument, '/');
if (substr_count($slashargument, '/') < 1) {
    header('HTTP/1.0 404 not found');
    die('Slash argument must contain both a revision and a file path');
}
// Split into revision and module name.
list($rev, $file) = explode('/', $slashargument, 2);
$rev  = min_clean_param($rev, 'INT');
$file = '/' . min_clean_param($file, 'SAFEPATH');

// Only load js files from the js modules folder from the components.
$jsfiles = array();
list($unused, $component, $module) = explode('/', $file, 3);

> /** // Use the caching only for meaningful revision numbers which prevents future cache poisoning. > * Helper function to fix missing module names in JavaScript. if ($rev > 0 and $rev < (time() + 60 * 60)) { > * // This is "production mode". > * TODO Remove this function when we find a reliable way to do this in the Grunt task. // Some (huge) modules are better loaded lazily (when they are used). If we are requesting > * @param string $modulename // one of these modules, only return the one module, not the combo. > * @param string $js $lazysuffix = "-lazy.js"; > * @return string The modified JavaScript. $lazyload = (strpos($module, $lazysuffix) !== false); > */ > function requirejs_fix_define(string $modulename, string $js): string { if ($lazyload) { > // First check whether there is a possible missing module name. That is: // We are lazy loading a single file - so include the component/filename pair in the etag. > // define (function(Foo) { $etag = sha1($rev . '/' . $component . '/' . $module); > // instead of: } else { > // define('mod_foo/bar', function(Foo) { // We loading all (non-lazy) files - so only the rev makes this request unique. > $missingmodule = preg_match('/define\(\s*(\[|function)/', $js); $etag = sha1($rev); > } > // Now check whether the module name is already defined elsewhere. > // This could be a totally unrelated use of the word define. $candidate = $CFG->localcachedir . '/requirejs/' . $etag; > // Note: This code needs to die, in a fire. It is evil and wrong. > $missingmodule = $missingmodule && !preg_match("@define\s*\(\s*['\"]{$modulename}['\"]@", $js); if (file_exists($candidate)) { > if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { > if ($missingmodule) { // We do not actually need to verify the etag value because our files > // If the JavaScript module has been defined without specifying a name then we'll // never change in cache because we increment the rev parameter. > // add the Moodle module name now. js_send_unmodified(filemtime($candidate), $etag); > $replace = 'define(\'' . $modulename . '\', '; } > js_send_cached($candidate, $etag, 'requirejs.php'); > // Replace only the first occurrence. exit(0); > return implode($replace, explode('define(', $js, 2)); > } else if (!preg_match('/define\s*\(/', $js)) { } else { > echo( $jsfiles = array(); > "// JS module '{$modulename}' cannot be loaded, or does not contain a javascript" . if ($lazyload) { > ' module in AMD format. "define()" not found.' . "\n" $jsfiles = core_requirejs::find_one_amd_module($component, $module); > ); } else { > } // Here we respond to the request by returning ALL amd modules. This saves > // round trips in production. > return $js; > } $jsfiles = core_requirejs::find_all_amd_modules(); >
} $content = ''; foreach ($jsfiles as $modulename => $jsfile) { $js = file_get_contents($jsfile); if ($js === false) { error_log('Failed to load JavaScript file ' . $jsfile); $js = "/* Failed to load JavaScript file {$jsfile}. */\n"; $content = $js . $content; continue; } // Remove source map link. $js = preg_replace('~//# sourceMappingURL.*$~s', '', $js); $js = rtrim($js); $js .= "\n";
< if (preg_match('/define\(\s*(\[|function)/', $js)) { < // If the JavaScript module has been defined without specifying a name then we'll < // add the Moodle module name now. < $replace = 'define(\'' . $modulename . '\', '; < $search = 'define('; < // Replace only the first occurrence. < $js = implode($replace, explode($search, $js, 2)); < }
> $js = requirejs_fix_define($modulename, $js);
$content .= $js; } js_write_cache_file_content($candidate, $content); // Verify nothing failed in cache file creation. clearstatcache(); if (file_exists($candidate)) { js_send_cached($candidate, $etag, 'requirejs.php'); exit(0); } } } // If we've made it here then we're in "dev mode" where everything is lazy loaded. // So all files will be served one at a time.
< $jsfiles = core_requirejs::find_one_amd_module($component, $module, false);
> $jsfiles = core_requirejs::find_one_amd_module($component, $module);
if (!empty($jsfiles)) { $modulename = array_keys($jsfiles)[0]; $jsfile = $jsfiles[$modulename]; $shortfilename = str_replace($CFG->dirroot, '', $jsfile); $mapfile = $jsfile . '.map'; if (file_exists($mapfile)) { // We've got a a source map file so we can return the minified file here and // the source map will be used by the browser to debug. $js = file_get_contents($jsfile); // Fix the source map link for the file. $js = preg_replace( '~//# sourceMappingURL.*$~s', "//# sourceMappingURL={$CFG->wwwroot}/lib/jssourcemap.php{$file}", $js ); $js = rtrim($js); } else { // This file doesn't have a map file. We might be dealing with an older source file from // a plugin or previous version of Moodle so we should just return the full original source // like we used to. $originalsource = str_replace('/amd/build/', '/amd/src/', $jsfile); $originalsource = str_replace('.min.js', '.js', $originalsource); $js = file_get_contents($originalsource); $js = rtrim($js); }
< if (preg_match('/define\(\s*(\[|function)/', $js)) { < // If the JavaScript module has been defined without specifying a name then we'll < // add the Moodle module name now. < $replace = 'define(\'' . $modulename . '\', '; < < // Replace only the first occurrence. < $js = implode($replace, explode('define(', $js, 2)); < } else if (!preg_match('/define\s*\(/', $js)) { < debugging('JS file: ' . $shortfilename . ' cannot be loaded, or does not contain a javascript' . < ' module in AMD format. "define()" not found.', DEBUG_DEVELOPER); < }
> $js = requirejs_fix_define($modulename, $js);
js_send_uncached($js, 'requirejs.php'); } else { // We can't find the requested file. header('HTTP/1.0 404 not found'); }