See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 401 and 402] [Versions 401 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 responsible for serving of yui Javascript and CSS 19 * 20 * @package core 21 * @copyright 2009 Petr Skoda (skodak) {@link http://skodak.org} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 26 // disable moodle specific debug messages and any errors in output, 27 // comment out when debugging or better look into error log! 28 define('NO_DEBUG_DISPLAY', true); 29 30 // we need just the values from config.php and minlib.php 31 define('ABORT_AFTER_CONFIG', true); 32 require('../config.php'); // this stops immediately at the beginning of lib/setup.php 33 34 // get special url parameters 35 36 list($parts, $slasharguments) = combo_params(); 37 if (!$parts) { 38 combo_not_found(); 39 } 40 41 $parts = trim($parts, '&'); 42 43 // Remove any duplicate parts, since each file only needs to be loaded once (which also helps reduce total file size). 44 $parts = implode('&', array_unique(explode('&', $parts))); 45 46 // Limit length of parts to match the YUI loader limit of 1024, to prevent loading an arbitrary number of files. 47 if (strlen($parts) > 1024) { 48 $parts = substr($parts, 0, 1024); 49 50 // If the shortened $parts has been cut off mid-way through a filename, trim back to the end of the previous filename. 51 if (substr($parts, -3) !== '.js' && substr($parts, -4) !== '.css') { 52 $parts = substr($parts, 0, strrpos($parts, '&')); 53 } 54 } 55 56 // find out what we are serving - only one type per request 57 $content = ''; 58 if (substr($parts, -3) === '.js') { 59 $mimetype = 'application/javascript'; 60 } else if (substr($parts, -4) === '.css') { 61 $mimetype = 'text/css'; 62 } else { 63 combo_not_found(); 64 } 65 66 $etag = sha1($parts); 67 68 // if they are requesting a revision that's not -1, and they have supplied an 69 // If-Modified-Since header, we can send back a 304 Not Modified since the 70 // content never changes (the rev number is increased any time the content changes) 71 if (strpos($parts, '/-1/') === false and (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))) { 72 $lifetime = 60*60*24*360; // 1 year, we do not change YUI versions often, there are a few custom yui modules 73 header('HTTP/1.1 304 Not Modified'); 74 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); 75 header('Cache-Control: public, max-age='.$lifetime); 76 header('Content-Type: '.$mimetype); 77 header('Etag: "'.$etag.'"'); 78 die; 79 } 80 81 $parts = explode('&', $parts); 82 $cache = true; 83 $lastmodified = 0; 84 85 while (count($parts)) { 86 $part = array_shift($parts); 87 if (empty($part)) { 88 continue; 89 } 90 $filecontent = ''; 91 $part = min_clean_param($part, 'SAFEPATH'); 92 $bits = explode('/', $part); 93 if (count($bits) < 2) { 94 $content .= "\n// Wrong combo resource $part!\n"; 95 continue; 96 } 97 98 $version = array_shift($bits); 99 if ($version === 'rollup') { 100 $yuipatchedversion = explode('_', array_shift($bits)); 101 $revision = $yuipatchedversion[0]; 102 $rollupname = array_shift($bits); 103 104 if (strpos($rollupname, 'yui-moodlesimple') !== false) { 105 if (substr($rollupname, -3) === '.js') { 106 // Determine which version of this rollup should be used. 107 $filesuffix = '.js'; 108 preg_match('/(-(debug|min))?\.js/', $rollupname, $matches); 109 if (isset($matches[1])) { 110 $filesuffix = $matches[0]; 111 } 112 113 $type = 'js'; 114 } else if (substr($rollupname, -4) === '.css') { 115 $type = 'css'; 116 } else { 117 continue; 118 } 119 120 // Allow support for revisions on YUI between official releases. 121 // We can just discard the subrevision since it is only used to invalidate the browser cache. 122 $yuipatchedversion = explode('_', $revision); 123 $yuiversion = $yuipatchedversion[0]; 124 125 $yuimodules = array( 126 'yui', 127 'oop', 128 'event-custom-base', 129 'dom-core', 130 'dom-base', 131 'color-base', 132 'dom-style', 133 'selector-native', 134 'selector', 135 'node-core', 136 'node-base', 137 'event-base', 138 'event-base-ie', 139 'pluginhost-base', 140 'pluginhost-config', 141 'event-delegate', 142 'node-event-delegate', 143 'node-pluginhost', 144 'dom-screen', 145 'node-screen', 146 'node-style', 147 'querystring-stringify-simple', 148 'io-base', 149 'json-parse', 150 'transition', 151 'selector-css2', 152 'selector-css3', 153 'dom-style-ie', 154 155 // Some extras we use everywhere. 156 'escape', 157 158 'attribute-core', 159 'event-custom-complex', 160 'base-core', 161 'attribute-base', 162 'attribute-extras', 163 'attribute-observable', 164 'base-observable', 165 'base-base', 166 'base-pluginhost', 167 'base-build', 168 'event-synthetic', 169 170 'attribute-complex', 171 'event-mouseenter', 172 'event-key', 173 'event-outside', 174 'event-focus', 175 'classnamemanager', 176 'widget-base', 177 'widget-htmlparser', 178 'widget-skin', 179 'widget-uievents', 180 'widget-stdmod', 181 'widget-position', 182 'widget-position-align', 183 'widget-stack', 184 'widget-position-constrain', 185 'overlay', 186 187 'widget-autohide', 188 'button-core', 189 'button-plugin', 190 'widget-buttons', 191 'widget-modality', 192 'panel', 193 'yui-throttle', 194 'dd-ddm-base', 195 'dd-drag', 196 'dd-plugin', 197 198 // Cache is used by moodle-core-tooltip which we include everywhere. 199 'cache-base', 200 ); 201 202 // We need to add these new parts to the beginning of the $parts list, not the end. 203 if ($type === 'js') { 204 $newparts = array(); 205 foreach ($yuimodules as $module) { 206 $newparts[] = $yuiversion . '/' . $module . '/' . $module . $filesuffix; 207 } 208 $newparts[] = 'yuiuseall/yuiuseall'; 209 $parts = array_merge($newparts, $parts); 210 } else { 211 $newparts = array(); 212 foreach ($yuimodules as $module) { 213 $candidate = $yuiversion . '/' . $module . '/assets/skins/sam/' . $module . '.css'; 214 if (!file_exists("$CFG->libdir/yuilib/$candidate")) { 215 continue; 216 } 217 $newparts[] = $candidate; 218 } 219 if ($newparts) { 220 $parts = array_merge($newparts, $parts); 221 } 222 } 223 } 224 225 continue; 226 } 227 if ($version === 'm') { 228 $version = 'moodle'; 229 } 230 if ($version === 'moodle') { 231 if (count($bits) <= 3) { 232 // This is an invalid module load attempt. 233 $content .= "\n// Incorrect moodle module inclusion. Not enough component information in {$part}.\n"; 234 continue; 235 } 236 $revision = (int)array_shift($bits); 237 if (!min_is_revision_valid_and_current($revision)) { 238 // A non-current revision means please don't cache the JS 239 $revision = -1; 240 $cache = false; 241 } 242 $frankenstyle = array_shift($bits); 243 $filename = array_pop($bits); 244 $modulename = $bits[0]; 245 $dir = core_component::get_component_directory($frankenstyle); 246 247 // For shifted YUI modules, we need the YUI module name in frankenstyle format. 248 $frankenstylemodulename = join('-', array($version, $frankenstyle, $modulename)); 249 $frankenstylefilename = preg_replace('/' . $modulename . '/', $frankenstylemodulename, $filename); 250 251 // Submodules are stored in a directory with the full submodule name. 252 // We need to remove the -debug.js, -min.js, and .js from the file name to calculate that directory name. 253 $frankenstyledirectoryname = str_replace(array('-min.js', '-debug.js', '.js', '.css'), '', $frankenstylefilename); 254 255 // By default, try and use the /yui/build directory. 256 $contentfile = $dir . '/yui/build/' . $frankenstyledirectoryname; 257 if ($mimetype == 'text/css') { 258 // CSS assets are in a slightly different place to the JS. 259 $contentfile = $contentfile . '/assets/skins/sam/' . $frankenstylefilename; 260 261 // Add the path to the bits to handle fallback for non-shifted assets. 262 $bits[] = 'assets'; 263 $bits[] = 'skins'; 264 $bits[] = 'sam'; 265 } else { 266 $contentfile = $contentfile . '/' . $frankenstylefilename; 267 } 268 269 // If the shifted versions don't exist, fall back to the non-shifted file. 270 if (!file_exists($contentfile) or !is_file($contentfile)) { 271 // We have to revert to the non-minified and non-debug versions. 272 $filename = preg_replace('/-(min|debug)\./', '.', $filename); 273 $contentfile = $dir . '/yui/' . join('/', $bits) . '/' . $filename; 274 } 275 } else if ($version === '2in3') { 276 $contentfile = "$CFG->libdir/yuilib/$part"; 277 278 } else if ($version == 'gallery') { 279 if (count($bits) <= 2) { 280 // This is an invalid module load attempt. 281 $content .= "\n// Incorrect moodle module inclusion. Not enough component information in {$part}.\n"; 282 continue; 283 } 284 $revision = (int)array_shift($bits); 285 if (!min_is_revision_valid_and_current($revision)) { 286 // A non-current revision means please don't cache the JS 287 $revision = -1; 288 $cache = false; 289 } 290 $contentfile = "$CFG->libdir/yuilib/gallery/" . join('/', $bits); 291 292 } else if ($version == 'yuiuseall') { 293 // Create global Y that is available in global scope, 294 // this is the trick behind original SimpleYUI. 295 $filecontent = "var Y = YUI().use('*');"; 296 297 } else { 298 // Allow support for revisions on YUI between official releases. 299 // We can just discard the subrevision since it is only used to invalidate the browser cache. 300 $yuipatchedversion = explode('_', $version); 301 $yuiversion = $yuipatchedversion[0]; 302 if ($yuiversion != $CFG->yui3version) { 303 $content .= "\n// Wrong yui version $part!\n"; 304 continue; 305 } 306 $newpart = explode('/', $part); 307 $newpart[0] = $yuiversion; 308 $part = implode('/', $newpart); 309 $contentfile = "$CFG->libdir/yuilib/$part"; 310 } 311 if (!file_exists($contentfile) or !is_file($contentfile)) { 312 $location = '$CFG->dirroot'.preg_replace('/^'.preg_quote($CFG->dirroot, '/').'/', '', $contentfile); 313 $content .= "\n// Combo resource $part ($location) not found!\n"; 314 continue; 315 } 316 317 if (empty($filecontent)) { 318 $filecontent = file_get_contents($contentfile); 319 } 320 $fmodified = filemtime($contentfile); 321 if ($fmodified > $lastmodified) { 322 $lastmodified = $fmodified; 323 } 324 325 $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot); 326 $sep = ($slasharguments ? '/' : '?file='); 327 328 if ($mimetype === 'text/css') { 329 if ($version == 'moodle') { 330 // Search for all images in the file and replace with an appropriate link to the yui_image.php script 331 $imagebits = array( 332 $sep . $version, 333 $frankenstyle, 334 $modulename, 335 array_shift($bits), 336 '$1.$2' 337 ); 338 339 $filecontent = preg_replace('/([a-z0-9_-]+)\.(png|gif)/', $relroot . '/theme/yui_image.php' . implode('/', $imagebits), $filecontent); 340 } else if ($version == '2in3') { 341 // First we need to remove relative paths to images. These are used by YUI modules to make use of global assets. 342 // I've added this as a separate regex so it can be easily removed once 343 // YUI standardise there CSS methods 344 $filecontent = preg_replace('#(\.\./\.\./\.\./\.\./assets/skins/sam/)?([a-z0-9_-]+)\.(png|gif)#', '$2.$3', $filecontent); 345 346 // search for all images in yui2 CSS and serve them through the yui_image.php script 347 $filecontent = preg_replace('/([a-z0-9_-]+)\.(png|gif)/', $relroot.'/theme/yui_image.php'.$sep.$CFG->yui2version.'/$1.$2', $filecontent); 348 349 } else if ($version == 'gallery') { 350 // Replace any references to the CDN with a relative link. 351 $filecontent = preg_replace('#(' . preg_quote('http://yui.yahooapis.com/') . '(gallery-[^/]*/))#', '../../../../', $filecontent); 352 353 // Replace all relative image links with the a link to yui_image.php. 354 $filecontent = preg_replace('#(' . preg_quote('../../../../') . ')(gallery-[^/]*/assets/skins/sam/[a-z0-9_-]+)\.(png|gif)#', 355 $relroot . '/theme/yui_image.php' . $sep . '/gallery/' . $revision . '/$2.$3', $filecontent); 356 357 } else { 358 // First we need to remove relative paths to images. These are used by YUI modules to make use of global assets. 359 // I've added this as a separate regex so it can be easily removed once 360 // YUI standardise there CSS methods 361 $filecontent = preg_replace('#(\.\./\.\./\.\./\.\./assets/skins/sam/)?([a-z0-9_-]+)\.(png|gif)#', '$2.$3', $filecontent); 362 363 // search for all images in yui2 CSS and serve them through the yui_image.php script 364 $filecontent = preg_replace('/([a-z0-9_-]+)\.(png|gif)/', $relroot.'/theme/yui_image.php'.$sep.$version.'/$1.$2', $filecontent); 365 } 366 } 367 368 $content .= $filecontent; 369 } 370 371 if ($lastmodified == 0) { 372 $lastmodified = time(); 373 } 374 375 if ($cache) { 376 combo_send_cached($content, $mimetype, $etag, $lastmodified); 377 } else { 378 combo_send_uncached($content, $mimetype); 379 } 380 381 382 /** 383 * Send the JavaScript cached 384 * @param string $content 385 * @param string $mimetype 386 * @param string $etag 387 * @param int $lastmodified 388 */ 389 function combo_send_cached($content, $mimetype, $etag, $lastmodified) { 390 $lifetime = 60*60*24*360; // 1 year, we do not change YUI versions often, there are a few custom yui modules 391 392 header('Content-Disposition: inline; filename="combo"'); 393 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT'); 394 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); 395 header('Pragma: '); 396 header('Cache-Control: public, max-age='.$lifetime.', immutable'); 397 header('Accept-Ranges: none'); 398 header('Content-Type: '.$mimetype); 399 header('Etag: "'.$etag.'"'); 400 if (!min_enable_zlib_compression()) { 401 header('Content-Length: '.strlen($content)); 402 } 403 404 echo $content; 405 die; 406 } 407 408 /** 409 * Send the JavaScript uncached 410 * @param string $content 411 * @param string $mimetype 412 */ 413 function combo_send_uncached($content, $mimetype) { 414 header('Content-Disposition: inline; filename="combo"'); 415 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT'); 416 header('Expires: '. gmdate('D, d M Y H:i:s', time() + 2) .' GMT'); 417 header('Pragma: '); 418 header('Accept-Ranges: none'); 419 header('Content-Type: '.$mimetype); 420 if (!min_enable_zlib_compression()) { 421 header('Content-Length: '.strlen($content)); 422 } 423 424 echo $content; 425 die; 426 } 427 428 function combo_not_found($message = '') { 429 header('HTTP/1.0 404 not found'); 430 if ($message) { 431 echo $message; 432 } else { 433 echo 'Combo resource not found, sorry.'; 434 } 435 die; 436 } 437 438 function combo_params() { 439 if (isset($_SERVER['QUERY_STRING']) and strpos($_SERVER['QUERY_STRING'], 'file=/') === 0) { 440 // url rewriting 441 $slashargument = substr($_SERVER['QUERY_STRING'], 6); 442 return array($slashargument, true); 443 444 } else if (isset($_SERVER['REQUEST_URI']) and strpos($_SERVER['REQUEST_URI'], '?') !== false) { 445 $parts = explode('?', $_SERVER['REQUEST_URI'], 2); 446 return array($parts[1], false); 447 448 } else if (isset($_SERVER['QUERY_STRING']) and strpos($_SERVER['QUERY_STRING'], '?') !== false) { 449 // note: buggy or misconfigured IIS does return the query string in REQUEST_URI 450 return array($_SERVER['QUERY_STRING'], false); 451 452 } else if ($slashargument = min_get_slash_argument(false)) { 453 $slashargument = ltrim($slashargument, '/'); 454 return array($slashargument, true); 455 456 } else { 457 // unsupported server, sorry! 458 combo_not_found('Unsupported server - query string can not be determined, try disabling YUI combo loading in admin settings.'); 459 } 460 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body