mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 22:08:20 +01:00
9b174fd3cc
It is perfectly legitimate to create and/or use a method named `define` in JS outside of RequireJS. Unfortunately our requirejs.php wrapper is dumb and does not understand this. In the long term we need to stop doing this at all. We really should be able to already, but every time I try to something prevents it. In the interim, this change adds a secondary check to see if there is an existing define which _does_ have the right name in it already.
197 lines
7.4 KiB
PHP
197 lines
7.4 KiB
PHP
<?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);
|
|
|
|
/**
|
|
* Helper function to fix missing module names in JavaScript.
|
|
*
|
|
* TODO Remove this function when we find a reliable way to do this in the Grunt task.
|
|
* @param string $modulename
|
|
* @param string $js
|
|
* @return string The modified JavaScript.
|
|
*/
|
|
function requirejs_fix_define(string $modulename, string $js): string {
|
|
// First check whether there is a possible missing module name. That is:
|
|
// define (function(Foo) {
|
|
// instead of:
|
|
// define('mod_foo/bar', function(Foo) {
|
|
$missingmodule = preg_match('/define\(\s*(\[|function)/', $js);
|
|
|
|
// Now check whether the module name is already defined elsewhere.
|
|
// This could be a totally unrelated use of the word define.
|
|
// Note: This code needs to die, in a fire. It is evil and wrong.
|
|
$missingmodule = $missingmodule && !preg_match("@define\s*\(\s*['\"]{$modulename}['\"]@", $js);
|
|
|
|
if ($missingmodule) {
|
|
// 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.
|
|
return implode($replace, explode('define(', $js, 2));
|
|
} else if (!preg_match('/define\s*\(/', $js)) {
|
|
echo(
|
|
"// JS module '{$modulename}' cannot be loaded, or does not contain a javascript" .
|
|
' module in AMD format. "define()" not found.'
|
|
);
|
|
}
|
|
|
|
return $js;
|
|
}
|
|
|
|
// Use the caching only for meaningful revision numbers which prevents future cache poisoning.
|
|
if ($rev > 0 and $rev < (time() + 60 * 60)) {
|
|
// This is "production mode".
|
|
// Some (huge) modules are better loaded lazily (when they are used). If we are requesting
|
|
// one of these modules, only return the one module, not the combo.
|
|
$lazysuffix = "-lazy.js";
|
|
$lazyload = (strpos($module, $lazysuffix) !== false);
|
|
|
|
if ($lazyload) {
|
|
// We are lazy loading a single file - so include the component/filename pair in the etag.
|
|
$etag = sha1($rev . '/' . $component . '/' . $module);
|
|
} else {
|
|
// We loading all (non-lazy) files - so only the rev makes this request unique.
|
|
$etag = sha1($rev);
|
|
}
|
|
|
|
$candidate = $CFG->localcachedir . '/requirejs/' . $etag;
|
|
|
|
if (file_exists($candidate)) {
|
|
if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
|
// We do not actually need to verify the etag value because our files
|
|
// never change in cache because we increment the rev parameter.
|
|
js_send_unmodified(filemtime($candidate), $etag);
|
|
}
|
|
js_send_cached($candidate, $etag, 'requirejs.php');
|
|
exit(0);
|
|
|
|
} else {
|
|
$jsfiles = array();
|
|
if ($lazyload) {
|
|
$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.
|
|
|
|
$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";
|
|
|
|
$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);
|
|
|
|
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);
|
|
}
|
|
|
|
$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');
|
|
}
|