Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
/lib/ -> requirejs.php (source)

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file is serving optimised JS for RequireJS.
  19   *
  20   * @package    core
  21   * @copyright  2015 Damyon Wiese
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  // Disable moodle specific debug messages and any errors in output,
  26  // comment out when debugging or better look into error log!
  27  define('NO_DEBUG_DISPLAY', true);
  28  
  29  // We need just the values from config.php and minlib.php.
  30  define('ABORT_AFTER_CONFIG', true);
  31  require('../config.php'); // This stops immediately at the beginning of lib/setup.php.
  32  require_once("$CFG->dirroot/lib/jslib.php");
  33  require_once("$CFG->dirroot/lib/classes/requirejs.php");
  34  
  35  $slashargument = min_get_slash_argument();
  36  if (!$slashargument) {
  37      // The above call to min_get_slash_argument should always work.
  38      die('Invalid request');
  39  }
  40  
  41  $slashargument = ltrim($slashargument, '/');
  42  if (substr_count($slashargument, '/') < 1) {
  43      header('HTTP/1.0 404 not found');
  44      die('Slash argument must contain both a revision and a file path');
  45  }
  46  // Split into revision and module name.
  47  list($rev, $file) = explode('/', $slashargument, 2);
  48  $rev  = min_clean_param($rev, 'INT');
  49  $file = '/' . min_clean_param($file, 'SAFEPATH');
  50  
  51  // Only load js files from the js modules folder from the components.
  52  $jsfiles = array();
  53  list($unused, $component, $module) = explode('/', $file, 3);
  54  
  55  /**
  56   * Helper function to fix missing module names in JavaScript.
  57   *
  58   * TODO Remove this function when we find a reliable way to do this in the Grunt task.
  59   * @param string $modulename
  60   * @param string $js
  61   * @return string The modified JavaScript.
  62   */
  63  function requirejs_fix_define(string $modulename, string $js): string {
  64      // First check whether there is a possible missing module name. That is:
  65      // define (function(Foo) {
  66      // instead of:
  67      // define('mod_foo/bar', function(Foo) {
  68      $missingmodule = preg_match('/define\(\s*(\[|function)/', $js);
  69  
  70      // Now check whether the module name is already defined elsewhere.
  71      // This could be a totally unrelated use of the word define.
  72      // Note: This code needs to die, in a fire. It is evil and wrong.
  73      $missingmodule = $missingmodule && !preg_match("@define\s*\(\s*['\"]{$modulename}['\"]@", $js);
  74  
  75      if ($missingmodule) {
  76          // If the JavaScript module has been defined without specifying a name then we'll
  77          // add the Moodle module name now.
  78          $replace = 'define(\'' . $modulename . '\', ';
  79  
  80          // Replace only the first occurrence.
  81          return implode($replace, explode('define(', $js, 2));
  82      } else if (!preg_match('/define\s*\(/', $js)) {
  83          echo(
  84              "// JS module '{$modulename}' cannot be loaded, or does not contain a javascript" .
  85              ' module in AMD format. "define()" not found.' . "\n"
  86          );
  87      }
  88  
  89      return $js;
  90  }
  91  
  92  // Use the caching only for meaningful revision numbers which prevents future cache poisoning.
  93  if ($rev > 0 and $rev < (time() + 60 * 60)) {
  94      // This is "production mode".
  95      // Some (huge) modules are better loaded lazily (when they are used). If we are requesting
  96      // one of these modules, only return the one module, not the combo.
  97      $lazysuffix = "-lazy.js";
  98      $lazyload = (strpos($module, $lazysuffix) !== false);
  99  
 100      if ($lazyload) {
 101          // We are lazy loading a single file - so include the component/filename pair in the etag.
 102          $etag = sha1($rev . '/' . $component . '/' . $module);
 103      } else {
 104          // We loading all (non-lazy) files - so only the rev makes this request unique.
 105          $etag = sha1($rev);
 106      }
 107  
 108      $candidate = $CFG->localcachedir . '/requirejs/' . $etag;
 109  
 110      if (file_exists($candidate)) {
 111          if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
 112              // We do not actually need to verify the etag value because our files
 113              // never change in cache because we increment the rev parameter.
 114              js_send_unmodified(filemtime($candidate), $etag);
 115          }
 116          js_send_cached($candidate, $etag, 'requirejs.php');
 117          exit(0);
 118  
 119      } else {
 120          $jsfiles = array();
 121          if ($lazyload) {
 122              $jsfiles = core_requirejs::find_one_amd_module($component, $module);
 123          } else {
 124              // Here we respond to the request by returning ALL amd modules. This saves
 125              // round trips in production.
 126  
 127              $jsfiles = core_requirejs::find_all_amd_modules();
 128          }
 129  
 130          $content = '';
 131          foreach ($jsfiles as $modulename => $jsfile) {
 132              $js = file_get_contents($jsfile);
 133              if ($js === false) {
 134                  error_log('Failed to load JavaScript file ' . $jsfile);
 135                  $js = "/* Failed to load JavaScript file {$jsfile}. */\n";
 136                  $content = $js . $content;
 137                  continue;
 138              }
 139              // Remove source map link.
 140              $js = preg_replace('~//# sourceMappingURL.*$~s', '', $js);
 141              $js = rtrim($js);
 142              $js .= "\n";
 143  
 144              $js = requirejs_fix_define($modulename, $js);
 145  
 146              $content .= $js;
 147          }
 148  
 149          js_write_cache_file_content($candidate, $content);
 150          // Verify nothing failed in cache file creation.
 151          clearstatcache();
 152          if (file_exists($candidate)) {
 153              js_send_cached($candidate, $etag, 'requirejs.php');
 154              exit(0);
 155          }
 156      }
 157  }
 158  
 159  // If we've made it here then we're in "dev mode" where everything is lazy loaded.
 160  // So all files will be served one at a time.
 161  $jsfiles = core_requirejs::find_one_amd_module($component, $module, false);
 162  
 163  if (!empty($jsfiles)) {
 164      $modulename = array_keys($jsfiles)[0];
 165      $jsfile = $jsfiles[$modulename];
 166      $shortfilename = str_replace($CFG->dirroot, '', $jsfile);
 167      $mapfile = $jsfile . '.map';
 168  
 169      if (file_exists($mapfile)) {
 170          // We've got a a source map file so we can return the minified file here and
 171          // the source map will be used by the browser to debug.
 172          $js = file_get_contents($jsfile);
 173          // Fix the source map link for the file.
 174          $js = preg_replace(
 175              '~//# sourceMappingURL.*$~s',
 176              "//# sourceMappingURL={$CFG->wwwroot}/lib/jssourcemap.php{$file}",
 177              $js
 178          );
 179          $js = rtrim($js);
 180      } else {
 181          // This file doesn't have a map file. We might be dealing with an older source file from
 182          // a plugin or previous version of Moodle so we should just return the full original source
 183          // like we used to.
 184          $originalsource = str_replace('/amd/build/', '/amd/src/', $jsfile);
 185          $originalsource = str_replace('.min.js', '.js', $originalsource);
 186          $js = file_get_contents($originalsource);
 187          $js = rtrim($js);
 188      }
 189  
 190      $js = requirejs_fix_define($modulename, $js);
 191  
 192      js_send_uncached($js, 'requirejs.php');
 193  } else {
 194      // We can't find the requested file.
 195      header('HTTP/1.0 404 not found');
 196  }