[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * Library functions to facilitate the use of JavaScript in Moodle. 19 * 20 * Note: you can find history of this file in lib/ajax/ajaxlib.php 21 * 22 * @copyright 2009 Tim Hunt, 2010 Petr Skoda 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 * @package core 25 * @category output 26 */ 27 28 defined('MOODLE_INTERNAL') || die(); 29 30 /** 31 * This class tracks all the things that are needed by the current page. 32 * 33 * Normally, the only instance of this class you will need to work with is the 34 * one accessible via $PAGE->requires. 35 * 36 * Typical usage would be 37 * <pre> 38 * $PAGE->requires->js_call_amd('mod_forum/view', 'init'); 39 * </pre> 40 * 41 * It also supports obsoleted coding style with/without YUI3 modules. 42 * <pre> 43 * $PAGE->requires->js_init_call('M.mod_forum.init_view'); 44 * $PAGE->requires->css('/mod/mymod/userstyles.php?id='.$id); // not overridable via themes! 45 * $PAGE->requires->js('/mod/mymod/script.js'); 46 * $PAGE->requires->js('/mod/mymod/small_but_urgent.js', true); 47 * $PAGE->requires->js_function_call('init_mymod', array($data), true); 48 * </pre> 49 * 50 * There are some natural restrictions on some methods. For example, {@link css()} 51 * can only be called before the <head> tag is output. See the comments on the 52 * individual methods for details. 53 * 54 * @copyright 2009 Tim Hunt, 2010 Petr Skoda 55 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 56 * @since Moodle 2.0 57 * @package core 58 * @category output 59 */ 60 class page_requirements_manager { 61 62 /** 63 * @var array List of string available from JS 64 */ 65 protected $stringsforjs = array(); 66 67 /** 68 * @var array List of get_string $a parameters - used for validation only. 69 */ 70 protected $stringsforjs_as = array(); 71 72 /** 73 * @var array List of JS variables to be initialised 74 */ 75 protected $jsinitvariables = array('head'=>array(), 'footer'=>array()); 76 77 /** 78 * @var array Included JS scripts 79 */ 80 protected $jsincludes = array('head'=>array(), 'footer'=>array()); 81 82 /** 83 * @var array Inline scripts using RequireJS module loading. 84 */ 85 protected $amdjscode = array(''); 86 87 /** 88 * @var array List of needed function calls 89 */ 90 protected $jscalls = array('normal'=>array(), 'ondomready'=>array()); 91 92 /** 93 * @var array List of skip links, those are needed for accessibility reasons 94 */ 95 protected $skiplinks = array(); 96 97 /** 98 * @var array Javascript code used for initialisation of page, it should 99 * be relatively small 100 */ 101 protected $jsinitcode = array(); 102 103 /** 104 * @var array of moodle_url Theme sheets, initialised only from core_renderer 105 */ 106 protected $cssthemeurls = array(); 107 108 /** 109 * @var array of moodle_url List of custom theme sheets, these are strongly discouraged! 110 * Useful mostly only for CSS submitted by teachers that is not part of the theme. 111 */ 112 protected $cssurls = array(); 113 114 /** 115 * @var array List of requested event handlers 116 */ 117 protected $eventhandlers = array(); 118 119 /** 120 * @var array Extra modules 121 */ 122 protected $extramodules = array(); 123 124 /** 125 * @var array trackes the names of bits of HTML that are only required once 126 * per page. See {@link has_one_time_item_been_created()}, 127 * {@link set_one_time_item_created()} and {@link should_create_one_time_item_now()}. 128 */ 129 protected $onetimeitemsoutput = array(); 130 131 /** 132 * @var bool Flag indicated head stuff already printed 133 */ 134 protected $headdone = false; 135 136 /** 137 * @var bool Flag indicating top of body already printed 138 */ 139 protected $topofbodydone = false; 140 141 /** 142 * @var stdClass YUI PHPLoader instance responsible for YUI3 loading from PHP only 143 */ 144 protected $yui3loader; 145 146 /** 147 * @var YUI_config default YUI loader configuration 148 */ 149 protected $YUI_config; 150 151 /** 152 * @var array $yuicssmodules 153 */ 154 protected $yuicssmodules = array(); 155 156 /** 157 * @var array Some config vars exposed in JS, please no secret stuff there 158 */ 159 protected $M_cfg; 160 161 /** 162 * @var array list of requested jQuery plugins 163 */ 164 protected $jqueryplugins = array(); 165 166 /** 167 * @var array list of jQuery plugin overrides 168 */ 169 protected $jquerypluginoverrides = array(); 170 171 /** 172 * Page requirements constructor. 173 */ 174 public function __construct() { 175 global $CFG; 176 177 // You may need to set up URL rewrite rule because oversized URLs might not be allowed by web server. 178 $sep = empty($CFG->yuislasharguments) ? '?' : '/'; 179 180 $this->yui3loader = new stdClass(); 181 $this->YUI_config = new YUI_config(); 182 183 if (is_https()) { 184 // On HTTPS sites all JS must be loaded from https sites, 185 // YUI CDN does not support https yet, sorry. 186 $CFG->useexternalyui = 0; 187 } 188 189 // Set up some loader options. 190 $this->yui3loader->local_base = $CFG->httpswwwroot . '/lib/yuilib/'. $CFG->yui3version . '/'; 191 $this->yui3loader->local_comboBase = $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep; 192 193 if (!empty($CFG->useexternalyui)) { 194 $this->yui3loader->base = 'http://yui.yahooapis.com/' . $CFG->yui3version . '/'; 195 $this->yui3loader->comboBase = 'http://yui.yahooapis.com/combo?'; 196 } else { 197 $this->yui3loader->base = $this->yui3loader->local_base; 198 $this->yui3loader->comboBase = $this->yui3loader->local_comboBase; 199 } 200 201 // Enable combo loader? This significantly helps with caching and performance! 202 $this->yui3loader->combine = !empty($CFG->yuicomboloading); 203 204 $jsrev = $this->get_jsrev(); 205 206 // Set up JS YUI loader helper object. 207 $this->YUI_config->base = $this->yui3loader->base; 208 $this->YUI_config->comboBase = $this->yui3loader->comboBase; 209 $this->YUI_config->combine = $this->yui3loader->combine; 210 211 // If we've had to patch any YUI modules between releases, we must override the YUI configuration to include them. 212 // For important information on patching YUI modules, please see http://docs.moodle.org/dev/YUI/Patching. 213 if (!empty($CFG->yuipatchedmodules) && !empty($CFG->yuipatchlevel)) { 214 $this->YUI_config->define_patched_core_modules($this->yui3loader->local_comboBase, 215 $CFG->yui3version, 216 $CFG->yuipatchlevel, 217 $CFG->yuipatchedmodules); 218 } 219 220 $configname = $this->YUI_config->set_config_source('lib/yui/config/yui2.js'); 221 $this->YUI_config->add_group('yui2', array( 222 // Loader configuration for our 2in3, for now ignores $CFG->useexternalyui. 223 'base' => $CFG->httpswwwroot . '/lib/yuilib/2in3/' . $CFG->yui2version . '/build/', 224 'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep, 225 'combine' => $this->yui3loader->combine, 226 'ext' => false, 227 'root' => '2in3/' . $CFG->yui2version .'/build/', 228 'patterns' => array( 229 'yui2-' => array( 230 'group' => 'yui2', 231 'configFn' => $configname, 232 ) 233 ) 234 )); 235 $configname = $this->YUI_config->set_config_source('lib/yui/config/moodle.js'); 236 $this->YUI_config->add_group('moodle', array( 237 'name' => 'moodle', 238 'base' => $CFG->httpswwwroot . '/theme/yui_combo.php' . $sep . 'm/' . $jsrev . '/', 239 'combine' => $this->yui3loader->combine, 240 'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep, 241 'ext' => false, 242 'root' => 'm/'.$jsrev.'/', // Add the rev to the root path so that we can control caching. 243 'patterns' => array( 244 'moodle-' => array( 245 'group' => 'moodle', 246 'configFn' => $configname, 247 ) 248 ) 249 )); 250 251 $this->YUI_config->add_group('gallery', array( 252 'name' => 'gallery', 253 'base' => $CFG->httpswwwroot . '/lib/yuilib/gallery/', 254 'combine' => $this->yui3loader->combine, 255 'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php' . $sep, 256 'ext' => false, 257 'root' => 'gallery/' . $jsrev . '/', 258 'patterns' => array( 259 'gallery-' => array( 260 'group' => 'gallery', 261 ) 262 ) 263 )); 264 265 // Set some more loader options applying to groups too. 266 if ($CFG->debugdeveloper) { 267 // When debugging is enabled, we want to load the non-minified (RAW) versions of YUI library modules rather 268 // than the DEBUG versions as these generally generate too much logging for our purposes. 269 // However we do want the DEBUG versions of our Moodle-specific modules. 270 // To debug a YUI-specific issue, change the yui3loader->filter value to DEBUG. 271 $this->YUI_config->filter = 'RAW'; 272 $this->YUI_config->groups['moodle']['filter'] = 'DEBUG'; 273 274 // We use the yui3loader->filter setting when writing the YUI3 seed scripts into the header. 275 $this->yui3loader->filter = $this->YUI_config->filter; 276 $this->YUI_config->debug = true; 277 } else { 278 $this->yui3loader->filter = null; 279 $this->YUI_config->groups['moodle']['filter'] = null; 280 $this->YUI_config->debug = false; 281 } 282 283 // Include the YUI config log filters. 284 if (!empty($CFG->yuilogexclude) && is_array($CFG->yuilogexclude)) { 285 $this->YUI_config->logExclude = $CFG->yuilogexclude; 286 } 287 if (!empty($CFG->yuiloginclude) && is_array($CFG->yuiloginclude)) { 288 $this->YUI_config->logInclude = $CFG->yuiloginclude; 289 } 290 if (!empty($CFG->yuiloglevel)) { 291 $this->YUI_config->logLevel = $CFG->yuiloglevel; 292 } 293 294 // Add the moodle group's module data. 295 $this->YUI_config->add_moodle_metadata(); 296 297 // Every page should include definition of following modules. 298 $this->js_module($this->find_module('core_filepicker')); 299 $this->js_module($this->find_module('core_comment')); 300 } 301 302 /** 303 * Return the safe config values that get set for javascript in "M.cfg". 304 * 305 * @since 2.9 306 * @return array List of safe config values that are available to javascript. 307 */ 308 public function get_config_for_javascript(moodle_page $page, renderer_base $renderer) { 309 global $CFG; 310 311 if (empty($this->M_cfg)) { 312 // JavaScript should always work with $CFG->httpswwwroot rather than $CFG->wwwroot. 313 // Otherwise, in some situations, users will get warnings about insecure content 314 // on secure pages from their web browser. 315 316 $this->M_cfg = array( 317 'wwwroot' => $CFG->httpswwwroot, // Yes, really. See above. 318 'sesskey' => sesskey(), 319 'loadingicon' => $renderer->pix_url('i/loading_small', 'moodle')->out(false), 320 'themerev' => theme_get_revision(), 321 'slasharguments' => (int)(!empty($CFG->slasharguments)), 322 'theme' => $page->theme->name, 323 'jsrev' => $this->get_jsrev(), 324 'admin' => $CFG->admin, 325 'svgicons' => $page->theme->use_svg_icons() 326 ); 327 if ($CFG->debugdeveloper) { 328 $this->M_cfg['developerdebug'] = true; 329 } 330 if (defined('BEHAT_SITE_RUNNING')) { 331 $this->M_cfg['behatsiterunning'] = true; 332 } 333 334 } 335 return $this->M_cfg; 336 } 337 338 /** 339 * Initialise with the bits of JavaScript that every Moodle page should have. 340 * 341 * @param moodle_page $page 342 * @param core_renderer $renderer 343 */ 344 protected function init_requirements_data(moodle_page $page, core_renderer $renderer) { 345 global $CFG; 346 347 // Init the js config. 348 $this->get_config_for_javascript($page, $renderer); 349 350 // Accessibility stuff. 351 $this->skip_link_to('maincontent', get_string('tocontent', 'access')); 352 353 // Add strings used on many pages. 354 $this->string_for_js('confirmation', 'admin'); 355 $this->string_for_js('cancel', 'moodle'); 356 $this->string_for_js('yes', 'moodle'); 357 358 // Alter links in top frame to break out of frames. 359 if ($page->pagelayout === 'frametop') { 360 $this->js_init_call('M.util.init_frametop'); 361 } 362 363 // Include block drag/drop if editing is on 364 if ($page->user_is_editing()) { 365 $params = array( 366 'courseid' => $page->course->id, 367 'pagetype' => $page->pagetype, 368 'pagelayout' => $page->pagelayout, 369 'subpage' => $page->subpage, 370 'regions' => $page->blocks->get_regions(), 371 'contextid' => $page->context->id, 372 ); 373 if (!empty($page->cm->id)) { 374 $params['cmid'] = $page->cm->id; 375 } 376 // Strings for drag and drop. 377 $this->strings_for_js(array('movecontent', 378 'tocontent', 379 'emptydragdropregion'), 380 'moodle'); 381 $page->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true); 382 } 383 384 // Include the YUI CSS Modules. 385 $page->requires->set_yuicssmodules($page->theme->yuicssmodules); 386 } 387 388 /** 389 * Determine the correct JS Revision to use for this load. 390 * 391 * @return int the jsrev to use. 392 */ 393 protected function get_jsrev() { 394 global $CFG; 395 396 if (empty($CFG->cachejs)) { 397 $jsrev = -1; 398 } else if (empty($CFG->jsrev)) { 399 $jsrev = 1; 400 } else { 401 $jsrev = $CFG->jsrev; 402 } 403 404 return $jsrev; 405 } 406 407 /** 408 * Ensure that the specified JavaScript file is linked to from this page. 409 * 410 * NOTE: This function is to be used in RARE CASES ONLY, please store your JS in module.js file 411 * and use $PAGE->requires->js_init_call() instead or use /yui/ subdirectories for YUI modules. 412 * 413 * By default the link is put at the end of the page, since this gives best page-load performance. 414 * 415 * Even if a particular script is requested more than once, it will only be linked 416 * to once. 417 * 418 * @param string|moodle_url $url The path to the .js file, relative to $CFG->dirroot / $CFG->wwwroot. 419 * For example '/mod/mymod/customscripts.js'; use moodle_url for external scripts 420 * @param bool $inhead initialise in head 421 */ 422 public function js($url, $inhead = false) { 423 $url = $this->js_fix_url($url); 424 $where = $inhead ? 'head' : 'footer'; 425 $this->jsincludes[$where][$url->out()] = $url; 426 } 427 428 /** 429 * Request inclusion of jQuery library in the page. 430 * 431 * NOTE: this should not be used in official Moodle distribution! 432 * 433 * {@see http://docs.moodle.org/dev/jQuery} 434 */ 435 public function jquery() { 436 $this->jquery_plugin('jquery'); 437 } 438 439 /** 440 * Request inclusion of jQuery plugin. 441 * 442 * NOTE: this should not be used in official Moodle distribution! 443 * 444 * jQuery plugins are located in plugin/jquery/* subdirectory, 445 * plugin/jquery/plugins.php lists all available plugins. 446 * 447 * Included core plugins: 448 * - jQuery UI 449 * 450 * Add-ons may include extra jQuery plugins in jquery/ directory, 451 * plugins.php file defines the mapping between plugin names and 452 * necessary page includes. 453 * 454 * Examples: 455 * <code> 456 * // file: mod/xxx/view.php 457 * $PAGE->requires->jquery(); 458 * $PAGE->requires->jquery_plugin('ui'); 459 * $PAGE->requires->jquery_plugin('ui-css'); 460 * </code> 461 * 462 * <code> 463 * // file: theme/yyy/lib.php 464 * function theme_yyy_page_init(moodle_page $page) { 465 * $page->requires->jquery(); 466 * $page->requires->jquery_plugin('ui'); 467 * $page->requires->jquery_plugin('ui-css'); 468 * } 469 * </code> 470 * 471 * <code> 472 * // file: blocks/zzz/block_zzz.php 473 * public function get_required_javascript() { 474 * parent::get_required_javascript(); 475 * $this->page->requires->jquery(); 476 * $page->requires->jquery_plugin('ui'); 477 * $page->requires->jquery_plugin('ui-css'); 478 * } 479 * </code> 480 * 481 * {@see http://docs.moodle.org/dev/jQuery} 482 * 483 * @param string $plugin name of the jQuery plugin as defined in jquery/plugins.php 484 * @param string $component name of the component 485 * @return bool success 486 */ 487 public function jquery_plugin($plugin, $component = 'core') { 488 global $CFG; 489 490 if ($this->headdone) { 491 debugging('Can not add jQuery plugins after starting page output!'); 492 return false; 493 } 494 495 if ($component !== 'core' and in_array($plugin, array('jquery', 'ui', 'ui-css'))) { 496 debugging("jQuery plugin '$plugin' is included in Moodle core, other components can not use the same name.", DEBUG_DEVELOPER); 497 $component = 'core'; 498 } else if ($component !== 'core' and strpos($component, '_') === false) { 499 // Let's normalise the legacy activity names, Frankenstyle rulez! 500 $component = 'mod_' . $component; 501 } 502 503 if (empty($this->jqueryplugins) and ($component !== 'core' or $plugin !== 'jquery')) { 504 // Make sure the jQuery itself is always loaded first, 505 // the order of all other plugins depends on order of $PAGE_>requires->. 506 $this->jquery_plugin('jquery', 'core'); 507 } 508 509 if (isset($this->jqueryplugins[$plugin])) { 510 // No problem, we already have something, first Moodle plugin to register the jQuery plugin wins. 511 return true; 512 } 513 514 $componentdir = core_component::get_component_directory($component); 515 if (!file_exists($componentdir) or !file_exists("$componentdir/jquery/plugins.php")) { 516 debugging("Can not load jQuery plugin '$plugin', missing plugins.php in component '$component'.", DEBUG_DEVELOPER); 517 return false; 518 } 519 520 $plugins = array(); 521 require("$componentdir/jquery/plugins.php"); 522 523 if (!isset($plugins[$plugin])) { 524 debugging("jQuery plugin '$plugin' can not be found in component '$component'.", DEBUG_DEVELOPER); 525 return false; 526 } 527 528 $this->jqueryplugins[$plugin] = new stdClass(); 529 $this->jqueryplugins[$plugin]->plugin = $plugin; 530 $this->jqueryplugins[$plugin]->component = $component; 531 $this->jqueryplugins[$plugin]->urls = array(); 532 533 foreach ($plugins[$plugin]['files'] as $file) { 534 if ($CFG->debugdeveloper) { 535 if (!file_exists("$componentdir/jquery/$file")) { 536 debugging("Invalid file '$file' specified in jQuery plugin '$plugin' in component '$component'"); 537 continue; 538 } 539 $file = str_replace('.min.css', '.css', $file); 540 $file = str_replace('.min.js', '.js', $file); 541 } 542 if (!file_exists("$componentdir/jquery/$file")) { 543 debugging("Invalid file '$file' specified in jQuery plugin '$plugin' in component '$component'"); 544 continue; 545 } 546 if (!empty($CFG->slasharguments)) { 547 $url = new moodle_url("$CFG->httpswwwroot/theme/jquery.php"); 548 $url->set_slashargument("/$component/$file"); 549 550 } else { 551 // This is not really good, we need slasharguments for relative links, this means no caching... 552 $path = realpath("$componentdir/jquery/$file"); 553 if (strpos($path, $CFG->dirroot) === 0) { 554 $url = $CFG->httpswwwroot.preg_replace('/^'.preg_quote($CFG->dirroot, '/').'/', '', $path); 555 // Replace all occurences of backslashes characters in url to forward slashes. 556 $url = str_replace('\\', '/', $url); 557 $url = new moodle_url($url); 558 } else { 559 // Bad luck, fix your server! 560 debugging("Moodle jQuery integration requires 'slasharguments' setting to be enabled."); 561 continue; 562 } 563 } 564 $this->jqueryplugins[$plugin]->urls[] = $url; 565 } 566 567 return true; 568 } 569 570 /** 571 * Request replacement of one jQuery plugin by another. 572 * 573 * This is useful when themes want to replace the jQuery UI theme, 574 * the problem is that theme can not prevent others from including the core ui-css plugin. 575 * 576 * Example: 577 * 1/ generate new jQuery UI theme and place it into theme/yourtheme/jquery/ 578 * 2/ write theme/yourtheme/jquery/plugins.php 579 * 3/ init jQuery from theme 580 * 581 * <code> 582 * // file theme/yourtheme/lib.php 583 * function theme_yourtheme_page_init($page) { 584 * $page->requires->jquery_plugin('yourtheme-ui-css', 'theme_yourtheme'); 585 * $page->requires->jquery_override_plugin('ui-css', 'yourtheme-ui-css'); 586 * } 587 * </code> 588 * 589 * This code prevents loading of standard 'ui-css' which my be requested by other plugins, 590 * the 'yourtheme-ui-css' gets loaded only if some other code requires jquery. 591 * 592 * {@see http://docs.moodle.org/dev/jQuery} 593 * 594 * @param string $oldplugin original plugin 595 * @param string $newplugin the replacement 596 */ 597 public function jquery_override_plugin($oldplugin, $newplugin) { 598 if ($this->headdone) { 599 debugging('Can not override jQuery plugins after starting page output!'); 600 return; 601 } 602 $this->jquerypluginoverrides[$oldplugin] = $newplugin; 603 } 604 605 /** 606 * Return jQuery related markup for page start. 607 * @return string 608 */ 609 protected function get_jquery_headcode() { 610 if (empty($this->jqueryplugins['jquery'])) { 611 // If nobody requested jQuery then do not bother to load anything. 612 // This may be useful for themes that want to override 'ui-css' only if requested by something else. 613 return ''; 614 } 615 616 $included = array(); 617 $urls = array(); 618 619 foreach ($this->jqueryplugins as $name => $unused) { 620 if (isset($included[$name])) { 621 continue; 622 } 623 if (array_key_exists($name, $this->jquerypluginoverrides)) { 624 // The following loop tries to resolve the replacements, 625 // use max 100 iterations to prevent infinite loop resulting 626 // in blank page. 627 $cyclic = true; 628 $oldname = $name; 629 for ($i=0; $i<100; $i++) { 630 $name = $this->jquerypluginoverrides[$name]; 631 if (!array_key_exists($name, $this->jquerypluginoverrides)) { 632 $cyclic = false; 633 break; 634 } 635 } 636 if ($cyclic) { 637 // We can not do much with cyclic references here, let's use the old plugin. 638 $name = $oldname; 639 debugging("Cyclic overrides detected for jQuery plugin '$name'"); 640 641 } else if (empty($name)) { 642 // Developer requested removal of the plugin. 643 continue; 644 645 } else if (!isset($this->jqueryplugins[$name])) { 646 debugging("Unknown jQuery override plugin '$name' detected"); 647 $name = $oldname; 648 649 } else if (isset($included[$name])) { 650 // The plugin was already included, easy. 651 continue; 652 } 653 } 654 655 $plugin = $this->jqueryplugins[$name]; 656 $urls = array_merge($urls, $plugin->urls); 657 $included[$name] = true; 658 } 659 660 $output = ''; 661 $attributes = array('rel' => 'stylesheet', 'type' => 'text/css'); 662 foreach ($urls as $url) { 663 if (preg_match('/\.js$/', $url)) { 664 $output .= html_writer::script('', $url); 665 } else if (preg_match('/\.css$/', $url)) { 666 $attributes['href'] = $url; 667 $output .= html_writer::empty_tag('link', $attributes) . "\n"; 668 } 669 } 670 671 return $output; 672 } 673 674 /** 675 * Returns the actual url through which a script is served. 676 * 677 * @param moodle_url|string $url full moodle url, or shortened path to script 678 * @return moodle_url 679 */ 680 protected function js_fix_url($url) { 681 global $CFG; 682 683 if ($url instanceof moodle_url) { 684 return $url; 685 } else if (strpos($url, '/') === 0) { 686 // Fix the admin links if needed. 687 if ($CFG->admin !== 'admin') { 688 if (strpos($url, "/admin/") === 0) { 689 $url = preg_replace("|^/admin/|", "/$CFG->admin/", $url); 690 } 691 } 692 if (debugging()) { 693 // Check file existence only when in debug mode. 694 if (!file_exists($CFG->dirroot . strtok($url, '?'))) { 695 throw new coding_exception('Attempt to require a JavaScript file that does not exist.', $url); 696 } 697 } 698 if (substr($url, -3) === '.js') { 699 $jsrev = $this->get_jsrev(); 700 if (empty($CFG->slasharguments)) { 701 return new moodle_url($CFG->httpswwwroot.'/lib/javascript.php', array('rev'=>$jsrev, 'jsfile'=>$url)); 702 } else { 703 $returnurl = new moodle_url($CFG->httpswwwroot.'/lib/javascript.php'); 704 $returnurl->set_slashargument('/'.$jsrev.$url); 705 return $returnurl; 706 } 707 } else { 708 return new moodle_url($CFG->httpswwwroot.$url); 709 } 710 } else { 711 throw new coding_exception('Invalid JS url, it has to be shortened url starting with / or moodle_url instance.', $url); 712 } 713 } 714 715 /** 716 * Find out if JS module present and return details. 717 * 718 * @param string $component name of component in frankenstyle, ex: core_group, mod_forum 719 * @return array description of module or null if not found 720 */ 721 protected function find_module($component) { 722 global $CFG, $PAGE; 723 724 $module = null; 725 726 if (strpos($component, 'core_') === 0) { 727 // Must be some core stuff - list here is not complete, this is just the stuff used from multiple places 728 // so that we do nto have to repeat the definition of these modules over and over again. 729 switch($component) { 730 case 'core_filepicker': 731 $module = array('name' => 'core_filepicker', 732 'fullpath' => '/repository/filepicker.js', 733 'requires' => array('base', 'node', 'node-event-simulate', 'json', 'async-queue', 'io-base', 'io-upload-iframe', 'io-form', 'yui2-treeview', 'panel', 'cookie', 'datatable', 'datatable-sort', 'resize-plugin', 'dd-plugin', 'escape', 'moodle-core_filepicker'), 734 'strings' => array(array('lastmodified', 'moodle'), array('name', 'moodle'), array('type', 'repository'), array('size', 'repository'), 735 array('invalidjson', 'repository'), array('error', 'moodle'), array('info', 'moodle'), 736 array('nofilesattached', 'repository'), array('filepicker', 'repository'), array('logout', 'repository'), 737 array('nofilesavailable', 'repository'), array('norepositoriesavailable', 'repository'), 738 array('fileexistsdialogheader', 'repository'), array('fileexistsdialog_editor', 'repository'), 739 array('fileexistsdialog_filemanager', 'repository'), array('renameto', 'repository'), 740 array('referencesexist', 'repository'), array('select', 'repository') 741 )); 742 break; 743 case 'core_comment': 744 $module = array('name' => 'core_comment', 745 'fullpath' => '/comment/comment.js', 746 'requires' => array('base', 'io-base', 'node', 'json', 'yui2-animation', 'overlay'), 747 'strings' => array(array('confirmdeletecomments', 'admin'), array('yes', 'moodle'), array('no', 'moodle')) 748 ); 749 break; 750 case 'core_role': 751 $module = array('name' => 'core_role', 752 'fullpath' => '/admin/roles/module.js', 753 'requires' => array('node', 'cookie')); 754 break; 755 case 'core_completion': 756 $module = array('name' => 'core_completion', 757 'fullpath' => '/course/completion.js'); 758 break; 759 case 'core_message': 760 $module = array('name' => 'core_message', 761 'requires' => array('base', 'node', 'event', 'node-event-simulate'), 762 'fullpath' => '/message/module.js'); 763 break; 764 case 'core_group': 765 $module = array('name' => 'core_group', 766 'fullpath' => '/group/module.js', 767 'requires' => array('node', 'overlay', 'event-mouseenter')); 768 break; 769 case 'core_question_engine': 770 $module = array('name' => 'core_question_engine', 771 'fullpath' => '/question/qengine.js', 772 'requires' => array('node', 'event')); 773 break; 774 case 'core_rating': 775 $module = array('name' => 'core_rating', 776 'fullpath' => '/rating/module.js', 777 'requires' => array('node', 'event', 'overlay', 'io-base', 'json')); 778 break; 779 case 'core_dndupload': 780 $module = array('name' => 'core_dndupload', 781 'fullpath' => '/lib/form/dndupload.js', 782 'requires' => array('node', 'event', 'json', 'core_filepicker'), 783 'strings' => array(array('uploadformlimit', 'moodle'), array('droptoupload', 'moodle'), array('maxfilesreached', 'moodle'), 784 array('dndenabled_inbox', 'moodle'), array('fileexists', 'moodle'), array('maxbytesfile', 'error'), 785 array('sizegb', 'moodle'), array('sizemb', 'moodle'), array('sizekb', 'moodle'), array('sizeb', 'moodle'), 786 array('maxareabytesreached', 'moodle'), array('serverconnection', 'error'), 787 )); 788 break; 789 } 790 791 } else { 792 if ($dir = core_component::get_component_directory($component)) { 793 if (file_exists("$dir/module.js")) { 794 if (strpos($dir, $CFG->dirroot.'/') === 0) { 795 $dir = substr($dir, strlen($CFG->dirroot)); 796 $module = array('name'=>$component, 'fullpath'=>"$dir/module.js", 'requires' => array()); 797 } 798 } 799 } 800 } 801 802 return $module; 803 } 804 805 /** 806 * Append YUI3 module to default YUI3 JS loader. 807 * The structure of module array is described at {@link http://developer.yahoo.com/yui/3/yui/} 808 * 809 * @param string|array $module name of module (details are autodetected), or full module specification as array 810 * @return void 811 */ 812 public function js_module($module) { 813 global $CFG; 814 815 if (empty($module)) { 816 throw new coding_exception('Missing YUI3 module name or full description.'); 817 } 818 819 if (is_string($module)) { 820 $module = $this->find_module($module); 821 } 822 823 if (empty($module) or empty($module['name']) or empty($module['fullpath'])) { 824 throw new coding_exception('Missing YUI3 module details.'); 825 } 826 827 $module['fullpath'] = $this->js_fix_url($module['fullpath'])->out(false); 828 // Add all needed strings. 829 if (!empty($module['strings'])) { 830 foreach ($module['strings'] as $string) { 831 $identifier = $string[0]; 832 $component = isset($string[1]) ? $string[1] : 'moodle'; 833 $a = isset($string[2]) ? $string[2] : null; 834 $this->string_for_js($identifier, $component, $a); 835 } 836 } 837 unset($module['strings']); 838 839 // Process module requirements and attempt to load each. This allows 840 // moodle modules to require each other. 841 if (!empty($module['requires'])){ 842 foreach ($module['requires'] as $requirement) { 843 $rmodule = $this->find_module($requirement); 844 if (is_array($rmodule)) { 845 $this->js_module($rmodule); 846 } 847 } 848 } 849 850 if ($this->headdone) { 851 $this->extramodules[$module['name']] = $module; 852 } else { 853 $this->YUI_config->add_module_config($module['name'], $module); 854 } 855 } 856 857 /** 858 * Returns true if the module has already been loaded. 859 * 860 * @param string|array $module 861 * @return bool True if the module has already been loaded 862 */ 863 protected function js_module_loaded($module) { 864 if (is_string($module)) { 865 $modulename = $module; 866 } else { 867 $modulename = $module['name']; 868 } 869 return array_key_exists($modulename, $this->YUI_config->modules) || 870 array_key_exists($modulename, $this->extramodules); 871 } 872 873 /** 874 * Ensure that the specified CSS file is linked to from this page. 875 * 876 * Because stylesheet links must go in the <head> part of the HTML, you must call 877 * this function before {@link get_head_code()} is called. That normally means before 878 * the call to print_header. If you call it when it is too late, an exception 879 * will be thrown. 880 * 881 * Even if a particular style sheet is requested more than once, it will only 882 * be linked to once. 883 * 884 * Please note use of this feature is strongly discouraged, 885 * it is suitable only for places where CSS is submitted directly by teachers. 886 * (Students must not be allowed to submit any external CSS because it may 887 * contain embedded javascript!). Example of correct use is mod/data. 888 * 889 * @param string $stylesheet The path to the .css file, relative to $CFG->wwwroot. 890 * For example: 891 * $PAGE->requires->css('mod/data/css.php?d='.$data->id); 892 */ 893 public function css($stylesheet) { 894 global $CFG; 895 896 if ($this->headdone) { 897 throw new coding_exception('Cannot require a CSS file after <head> has been printed.', $stylesheet); 898 } 899 900 if ($stylesheet instanceof moodle_url) { 901 // ok 902 } else if (strpos($stylesheet, '/') === 0) { 903 $stylesheet = new moodle_url($CFG->httpswwwroot.$stylesheet); 904 } else { 905 throw new coding_exception('Invalid stylesheet parameter.', $stylesheet); 906 } 907 908 $this->cssurls[$stylesheet->out()] = $stylesheet; 909 } 910 911 /** 912 * Add theme stylesheet to page - do not use from plugin code, 913 * this should be called only from the core renderer! 914 * 915 * @param moodle_url $stylesheet 916 * @return void 917 */ 918 public function css_theme(moodle_url $stylesheet) { 919 $this->cssthemeurls[] = $stylesheet; 920 } 921 922 /** 923 * Ensure that a skip link to a given target is printed at the top of the <body>. 924 * 925 * You must call this function before {@link get_top_of_body_code()}, (if not, an exception 926 * will be thrown). That normally means you must call this before the call to print_header. 927 * 928 * If you ask for a particular skip link to be printed, it is then your responsibility 929 * to ensure that the appropriate <a name="..."> tag is printed in the body of the 930 * page, so that the skip link goes somewhere. 931 * 932 * Even if a particular skip link is requested more than once, only one copy of it will be output. 933 * 934 * @param string $target the name of anchor this link should go to. For example 'maincontent'. 935 * @param string $linktext The text to use for the skip link. Normally get_string('skipto', 'access', ...); 936 */ 937 public function skip_link_to($target, $linktext) { 938 if ($this->topofbodydone) { 939 debugging('Page header already printed, can not add skip links any more, code needs to be fixed.'); 940 return; 941 } 942 $this->skiplinks[$target] = $linktext; 943 } 944 945 /** 946 * !!!DEPRECATED!!! please use js_init_call() if possible 947 * Ensure that the specified JavaScript function is called from an inline script 948 * somewhere on this page. 949 * 950 * By default the call will be put in a script tag at the 951 * end of the page after initialising Y instance, since this gives best page-load 952 * performance and allows you to use YUI3 library. 953 * 954 * If you request that a particular function is called several times, then 955 * that is what will happen (unlike linking to a CSS or JS file, where only 956 * one link will be output). 957 * 958 * The main benefit of the method is the automatic encoding of all function parameters. 959 * 960 * @deprecated 961 * 962 * @param string $function the name of the JavaScritp function to call. Can 963 * be a compound name like 'Y.Event.purgeElement'. Can also be 964 * used to create and object by using a 'function name' like 'new user_selector'. 965 * @param array $arguments and array of arguments to be passed to the function. 966 * When generating the function call, this will be escaped using json_encode, 967 * so passing objects and arrays should work. 968 * @param bool $ondomready If tru the function is only called when the dom is 969 * ready for manipulation. 970 * @param int $delay The delay before the function is called. 971 */ 972 public function js_function_call($function, array $arguments = null, $ondomready = false, $delay = 0) { 973 $where = $ondomready ? 'ondomready' : 'normal'; 974 $this->jscalls[$where][] = array($function, $arguments, $delay); 975 } 976 977 /** 978 * This function appends a block of code to the AMD specific javascript block executed 979 * in the page footer, just after loading the requirejs library. 980 * 981 * The code passed here can rely on AMD module loading, e.g. require('jquery', function($) {...}); 982 * 983 * @param string $code The JS code to append. 984 */ 985 public function js_amd_inline($code) { 986 $this->amdjscode[] = $code; 987 } 988 989 /** 990 * This function creates a minimal JS script that requires and calls a single function from an AMD module with arguments. 991 * If it is called multiple times, it will be executed multiple times. 992 * 993 * @param string $fullmodule The format for module names is <component name>/<module name>. 994 * @param string $func The function from the module to call 995 * @param array $params The params to pass to the function. They will be json encoded, so no nasty classes/types please. 996 */ 997 public function js_call_amd($fullmodule, $func, $params = array()) { 998 global $CFG; 999 1000 list($component, $module) = explode('/', $fullmodule, 2); 1001 1002 $component = clean_param($component, PARAM_COMPONENT); 1003 $module = clean_param($module, PARAM_ALPHANUMEXT); 1004 $func = clean_param($func, PARAM_ALPHANUMEXT); 1005 1006 $jsonparams = array(); 1007 foreach ($params as $param) { 1008 $jsonparams[] = json_encode($param); 1009 } 1010 $strparams = implode(', ', $jsonparams); 1011 if ($CFG->debugdeveloper) { 1012 $toomanyparamslimit = 1024; 1013 if (strlen($strparams) > $toomanyparamslimit) { 1014 debugging('Too many params passed to js_call_amd("' . $fullmodule . '", "' . $func . '")', DEBUG_DEVELOPER); 1015 } 1016 } 1017 1018 $js = 'require(["' . $component . '/' . $module . '"], function(amd) { amd.' . $func . '(' . $strparams . '); });'; 1019 1020 $this->js_amd_inline($js); 1021 } 1022 1023 /** 1024 * Creates a JavaScript function call that requires one or more modules to be loaded. 1025 * 1026 * This function can be used to include all of the standard YUI module types within JavaScript: 1027 * - YUI3 modules [node, event, io] 1028 * - YUI2 modules [yui2-*] 1029 * - Moodle modules [moodle-*] 1030 * - Gallery modules [gallery-*] 1031 * 1032 * Before writing new code that makes extensive use of YUI, you should consider it's replacement AMD/JQuery. 1033 * @see js_call_amd() 1034 * 1035 * @param array|string $modules One or more modules 1036 * @param string $function The function to call once modules have been loaded 1037 * @param array $arguments An array of arguments to pass to the function 1038 * @param string $galleryversion Deprecated: The gallery version to use 1039 * @param bool $ondomready 1040 */ 1041 public function yui_module($modules, $function, array $arguments = null, $galleryversion = null, $ondomready = false) { 1042 if (!is_array($modules)) { 1043 $modules = array($modules); 1044 } 1045 1046 if ($galleryversion != null) { 1047 debugging('The galleryversion parameter to yui_module has been deprecated since Moodle 2.3.'); 1048 } 1049 1050 $jscode = 'Y.use('.join(',', array_map('json_encode', convert_to_array($modules))).',function() {'.js_writer::function_call($function, $arguments).'});'; 1051 if ($ondomready) { 1052 $jscode = "Y.on('domready', function() { $jscode });"; 1053 } 1054 $this->jsinitcode[] = $jscode; 1055 } 1056 1057 /** 1058 * Set the CSS Modules to be included from YUI. 1059 * 1060 * @param array $modules The list of YUI CSS Modules to include. 1061 */ 1062 public function set_yuicssmodules(array $modules = array()) { 1063 $this->yuicssmodules = $modules; 1064 } 1065 1066 /** 1067 * Ensure that the specified JavaScript function is called from an inline script 1068 * from page footer. 1069 * 1070 * @param string $function the name of the JavaScritp function to with init code, 1071 * usually something like 'M.mod_mymodule.init' 1072 * @param array $extraarguments and array of arguments to be passed to the function. 1073 * The first argument is always the YUI3 Y instance with all required dependencies 1074 * already loaded. 1075 * @param bool $ondomready wait for dom ready (helps with some IE problems when modifying DOM) 1076 * @param array $module JS module specification array 1077 */ 1078 public function js_init_call($function, array $extraarguments = null, $ondomready = false, array $module = null) { 1079 $jscode = js_writer::function_call_with_Y($function, $extraarguments); 1080 if (!$module) { 1081 // Detect module automatically. 1082 if (preg_match('/M\.([a-z0-9]+_[^\.]+)/', $function, $matches)) { 1083 $module = $this->find_module($matches[1]); 1084 } 1085 } 1086 1087 $this->js_init_code($jscode, $ondomready, $module); 1088 } 1089 1090 /** 1091 * Add short static javascript code fragment to page footer. 1092 * This is intended primarily for loading of js modules and initialising page layout. 1093 * Ideally the JS code fragment should be stored in plugin renderer so that themes 1094 * may override it. 1095 * 1096 * @param string $jscode 1097 * @param bool $ondomready wait for dom ready (helps with some IE problems when modifying DOM) 1098 * @param array $module JS module specification array 1099 */ 1100 public function js_init_code($jscode, $ondomready = false, array $module = null) { 1101 $jscode = trim($jscode, " ;\n"). ';'; 1102 1103 $uniqid = html_writer::random_id(); 1104 $startjs = " M.util.js_pending('" . $uniqid . "');"; 1105 $endjs = " M.util.js_complete('" . $uniqid . "');"; 1106 1107 if ($module) { 1108 $this->js_module($module); 1109 $modulename = $module['name']; 1110 $jscode = "$startjs Y.use('$modulename', function(Y) { $jscode $endjs });"; 1111 } 1112 1113 if ($ondomready) { 1114 $jscode = "$startjs Y.on('domready', function() { $jscode $endjs });"; 1115 } 1116 1117 $this->jsinitcode[] = $jscode; 1118 } 1119 1120 /** 1121 * Make a language string available to JavaScript. 1122 * 1123 * All the strings will be available in a M.str object in the global namespace. 1124 * So, for example, after a call to $PAGE->requires->string_for_js('course', 'moodle'); 1125 * then the JavaScript variable M.str.moodle.course will be 'Course', or the 1126 * equivalent in the current language. 1127 * 1128 * The arguments to this function are just like the arguments to get_string 1129 * except that $component is not optional, and there are some aspects to consider 1130 * when the string contains {$a} placeholder. 1131 * 1132 * If the string does not contain any {$a} placeholder, you can simply use 1133 * M.str.component.identifier to obtain it. If you prefer, you can call 1134 * M.util.get_string(identifier, component) to get the same result. 1135 * 1136 * If you need to use {$a} placeholders, there are two options. Either the 1137 * placeholder should be substituted in PHP on server side or it should 1138 * be substituted in Javascript at client side. 1139 * 1140 * To substitute the placeholder at server side, just provide the required 1141 * value for the placeholder when you require the string. Because each string 1142 * is only stored once in the JavaScript (based on $identifier and $module) 1143 * you cannot get the same string with two different values of $a. If you try, 1144 * an exception will be thrown. Once the placeholder is substituted, you can 1145 * use M.str or M.util.get_string() as shown above: 1146 * 1147 * // Require the string in PHP and replace the placeholder. 1148 * $PAGE->requires->string_for_js('fullnamedisplay', 'moodle', $USER); 1149 * // Use the result of the substitution in Javascript. 1150 * alert(M.str.moodle.fullnamedisplay); 1151 * 1152 * To substitute the placeholder at client side, use M.util.get_string() 1153 * function. It implements the same logic as {@link get_string()}: 1154 * 1155 * // Require the string in PHP but keep {$a} as it is. 1156 * $PAGE->requires->string_for_js('fullnamedisplay', 'moodle'); 1157 * // Provide the values on the fly in Javascript. 1158 * user = { firstname : 'Harry', lastname : 'Potter' } 1159 * alert(M.util.get_string('fullnamedisplay', 'moodle', user); 1160 * 1161 * If you do need the same string expanded with different $a values in PHP 1162 * on server side, then the solution is to put them in your own data structure 1163 * (e.g. and array) that you pass to JavaScript with {@link data_for_js()}. 1164 * 1165 * @param string $identifier the desired string. 1166 * @param string $component the language file to look in. 1167 * @param mixed $a any extra data to add into the string (optional). 1168 */ 1169 public function string_for_js($identifier, $component, $a = null) { 1170 if (!$component) { 1171 throw new coding_exception('The $component parameter is required for page_requirements_manager::string_for_js().'); 1172 } 1173 if (isset($this->stringsforjs_as[$component][$identifier]) and $this->stringsforjs_as[$component][$identifier] !== $a) { 1174 throw new coding_exception("Attempt to re-define already required string '$identifier' " . 1175 "from lang file '$component' with different \$a parameter?"); 1176 } 1177 if (!isset($this->stringsforjs[$component][$identifier])) { 1178 $this->stringsforjs[$component][$identifier] = new lang_string($identifier, $component, $a); 1179 $this->stringsforjs_as[$component][$identifier] = $a; 1180 } 1181 } 1182 1183 /** 1184 * Make an array of language strings available for JS. 1185 * 1186 * This function calls the above function {@link string_for_js()} for each requested 1187 * string in the $identifiers array that is passed to the argument for a single module 1188 * passed in $module. 1189 * 1190 * <code> 1191 * $PAGE->requires->strings_for_js(array('one', 'two', 'three'), 'mymod', array('a', null, 3)); 1192 * 1193 * // The above is identical to calling: 1194 * 1195 * $PAGE->requires->string_for_js('one', 'mymod', 'a'); 1196 * $PAGE->requires->string_for_js('two', 'mymod'); 1197 * $PAGE->requires->string_for_js('three', 'mymod', 3); 1198 * </code> 1199 * 1200 * @param array $identifiers An array of desired strings 1201 * @param string $component The module to load for 1202 * @param mixed $a This can either be a single variable that gets passed as extra 1203 * information for every string or it can be an array of mixed data where the 1204 * key for the data matches that of the identifier it is meant for. 1205 * 1206 */ 1207 public function strings_for_js($identifiers, $component, $a = null) { 1208 foreach ($identifiers as $key => $identifier) { 1209 if (is_array($a) && array_key_exists($key, $a)) { 1210 $extra = $a[$key]; 1211 } else { 1212 $extra = $a; 1213 } 1214 $this->string_for_js($identifier, $component, $extra); 1215 } 1216 } 1217 1218 /** 1219 * !!!!!!DEPRECATED!!!!!! please use js_init_call() for everything now. 1220 * 1221 * Make some data from PHP available to JavaScript code. 1222 * 1223 * For example, if you call 1224 * <pre> 1225 * $PAGE->requires->data_for_js('mydata', array('name' => 'Moodle')); 1226 * </pre> 1227 * then in JavsScript mydata.name will be 'Moodle'. 1228 * 1229 * @deprecated 1230 * @param string $variable the the name of the JavaScript variable to assign the data to. 1231 * Will probably work if you use a compound name like 'mybuttons.button[1]', but this 1232 * should be considered an experimental feature. 1233 * @param mixed $data The data to pass to JavaScript. This will be escaped using json_encode, 1234 * so passing objects and arrays should work. 1235 * @param bool $inhead initialise in head 1236 * @return void 1237 */ 1238 public function data_for_js($variable, $data, $inhead=false) { 1239 $where = $inhead ? 'head' : 'footer'; 1240 $this->jsinitvariables[$where][] = array($variable, $data); 1241 } 1242 1243 /** 1244 * Creates a YUI event handler. 1245 * 1246 * @param mixed $selector standard YUI selector for elements, may be array or string, element id is in the form "#idvalue" 1247 * @param string $event A valid DOM event (click, mousedown, change etc.) 1248 * @param string $function The name of the function to call 1249 * @param array $arguments An optional array of argument parameters to pass to the function 1250 */ 1251 public function event_handler($selector, $event, $function, array $arguments = null) { 1252 $this->eventhandlers[] = array('selector'=>$selector, 'event'=>$event, 'function'=>$function, 'arguments'=>$arguments); 1253 } 1254 1255 /** 1256 * Returns code needed for registering of event handlers. 1257 * @return string JS code 1258 */ 1259 protected function get_event_handler_code() { 1260 $output = ''; 1261 foreach ($this->eventhandlers as $h) { 1262 $output .= js_writer::event_handler($h['selector'], $h['event'], $h['function'], $h['arguments']); 1263 } 1264 return $output; 1265 } 1266 1267 /** 1268 * Get the inline JavaScript code that need to appear in a particular place. 1269 * @param bool $ondomready 1270 * @return string 1271 */ 1272 protected function get_javascript_code($ondomready) { 1273 $where = $ondomready ? 'ondomready' : 'normal'; 1274 $output = ''; 1275 if ($this->jscalls[$where]) { 1276 foreach ($this->jscalls[$where] as $data) { 1277 $output .= js_writer::function_call($data[0], $data[1], $data[2]); 1278 } 1279 if (!empty($ondomready)) { 1280 $output = " Y.on('domready', function() {\n$output\n});"; 1281 } 1282 } 1283 return $output; 1284 } 1285 1286 /** 1287 * Returns js code to be executed when Y is available. 1288 * @return string 1289 */ 1290 protected function get_javascript_init_code() { 1291 if (count($this->jsinitcode)) { 1292 return implode("\n", $this->jsinitcode) . "\n"; 1293 } 1294 return ''; 1295 } 1296 1297 /** 1298 * Returns js code to load amd module loader, then insert inline script tags 1299 * that contain require() calls using RequireJS. 1300 * @return string 1301 */ 1302 protected function get_amd_footercode() { 1303 global $CFG; 1304 $output = ''; 1305 $jsrev = $this->get_jsrev(); 1306 1307 $jsloader = new moodle_url($CFG->httpswwwroot . '/lib/javascript.php'); 1308 $jsloader->set_slashargument('/' . $jsrev . '/'); 1309 $requirejsloader = new moodle_url($CFG->httpswwwroot . '/lib/requirejs.php'); 1310 $requirejsloader->set_slashargument('/' . $jsrev . '/'); 1311 1312 $requirejsconfig = file_get_contents($CFG->dirroot . '/lib/requirejs/moodle-config.js'); 1313 1314 // No extension required unless slash args is disabled. 1315 $jsextension = '.js'; 1316 if (!empty($CFG->slasharguments)) { 1317 $jsextension = ''; 1318 } 1319 1320 $requirejsconfig = str_replace('[BASEURL]', $requirejsloader, $requirejsconfig); 1321 $requirejsconfig = str_replace('[JSURL]', $jsloader, $requirejsconfig); 1322 $requirejsconfig = str_replace('[JSEXT]', $jsextension, $requirejsconfig); 1323 1324 $output .= html_writer::script($requirejsconfig); 1325 if ($CFG->debugdeveloper) { 1326 $output .= html_writer::script('', $this->js_fix_url('/lib/requirejs/require.js')); 1327 } else { 1328 $output .= html_writer::script('', $this->js_fix_url('/lib/requirejs/require.min.js')); 1329 } 1330 1331 // First include must be to a module with no dependencies, this prevents multiple requests. 1332 $prefix = "require(['core/first'], function() {\n"; 1333 $suffix = "\n});"; 1334 $output .= html_writer::script($prefix . implode(";\n", $this->amdjscode) . $suffix); 1335 return $output; 1336 } 1337 1338 /** 1339 * Returns basic YUI3 CSS code. 1340 * 1341 * @return string 1342 */ 1343 protected function get_yui3lib_headcss() { 1344 global $CFG; 1345 1346 $yuiformat = '-min'; 1347 if ($this->yui3loader->filter === 'RAW') { 1348 $yuiformat = ''; 1349 } 1350 1351 $code = ''; 1352 if ($this->yui3loader->combine) { 1353 if (!empty($this->yuicssmodules)) { 1354 $modules = array(); 1355 foreach ($this->yuicssmodules as $module) { 1356 $modules[] = "$CFG->yui3version/$module/$module-min.css"; 1357 } 1358 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->comboBase.implode('&', $modules).'" />'; 1359 } 1360 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->local_comboBase.'rollup/'.$CFG->yui3version.'/yui-moodlesimple' . $yuiformat . '.css" />'; 1361 1362 } else { 1363 if (!empty($this->yuicssmodules)) { 1364 foreach ($this->yuicssmodules as $module) { 1365 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.$module.'/'.$module.'-min.css" />'; 1366 } 1367 } 1368 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->local_comboBase.'rollup/'.$CFG->yui3version.'/yui-moodlesimple' . $yuiformat . '.css" />'; 1369 } 1370 1371 if ($this->yui3loader->filter === 'RAW') { 1372 $code = str_replace('-min.css', '.css', $code); 1373 } else if ($this->yui3loader->filter === 'DEBUG') { 1374 $code = str_replace('-min.css', '.css', $code); 1375 } 1376 return $code; 1377 } 1378 1379 /** 1380 * Returns basic YUI3 JS loading code. 1381 * 1382 * @return string 1383 */ 1384 protected function get_yui3lib_headcode() { 1385 global $CFG; 1386 1387 $jsrev = $this->get_jsrev(); 1388 1389 $yuiformat = '-min'; 1390 if ($this->yui3loader->filter === 'RAW') { 1391 $yuiformat = ''; 1392 } 1393 1394 $format = '-min'; 1395 if ($this->YUI_config->groups['moodle']['filter'] === 'DEBUG') { 1396 $format = '-debug'; 1397 } 1398 1399 $rollupversion = $CFG->yui3version; 1400 if (!empty($CFG->yuipatchlevel)) { 1401 $rollupversion .= '_' . $CFG->yuipatchlevel; 1402 } 1403 1404 $baserollups = array( 1405 'rollup/' . $rollupversion . "/yui-moodlesimple{$yuiformat}.js", 1406 'rollup/' . $jsrev . "/mcore{$format}.js", 1407 ); 1408 1409 if ($this->yui3loader->combine) { 1410 return '<script type="text/javascript" src="' . 1411 $this->yui3loader->local_comboBase . 1412 implode('&', $baserollups) . 1413 '"></script>'; 1414 } else { 1415 $code = ''; 1416 foreach ($baserollups as $rollup) { 1417 $code .= '<script type="text/javascript" src="'.$this->yui3loader->local_comboBase.$rollup.'"></script>'; 1418 } 1419 return $code; 1420 } 1421 1422 } 1423 1424 /** 1425 * Returns html tags needed for inclusion of theme CSS. 1426 * 1427 * @return string 1428 */ 1429 protected function get_css_code() { 1430 // First of all the theme CSS, then any custom CSS 1431 // Please note custom CSS is strongly discouraged, 1432 // because it can not be overridden by themes! 1433 // It is suitable only for things like mod/data which accepts CSS from teachers. 1434 $attributes = array('rel'=>'stylesheet', 'type'=>'text/css'); 1435 1436 // Add the YUI code first. We want this to be overridden by any Moodle CSS. 1437 $code = $this->get_yui3lib_headcss(); 1438 1439 // This line of code may look funny but it is currently required in order 1440 // to avoid MASSIVE display issues in Internet Explorer. 1441 // As of IE8 + YUI3.1.1 the reference stylesheet (firstthemesheet) gets 1442 // ignored whenever another resource is added until such time as a redraw 1443 // is forced, usually by moving the mouse over the affected element. 1444 $code .= html_writer::tag('script', '/** Required in order to fix style inclusion problems in IE with YUI **/', array('id'=>'firstthemesheet', 'type'=>'text/css')); 1445 1446 $urls = $this->cssthemeurls + $this->cssurls; 1447 foreach ($urls as $url) { 1448 $attributes['href'] = $url; 1449 $code .= html_writer::empty_tag('link', $attributes) . "\n"; 1450 // This id is needed in first sheet only so that theme may override YUI sheets loaded on the fly. 1451 unset($attributes['id']); 1452 } 1453 1454 return $code; 1455 } 1456 1457 /** 1458 * Adds extra modules specified after printing of page header. 1459 * 1460 * @return string 1461 */ 1462 protected function get_extra_modules_code() { 1463 if (empty($this->extramodules)) { 1464 return ''; 1465 } 1466 return html_writer::script(js_writer::function_call('M.yui.add_module', array($this->extramodules))); 1467 } 1468 1469 /** 1470 * Generate any HTML that needs to go inside the <head> tag. 1471 * 1472 * Normally, this method is called automatically by the code that prints the 1473 * <head> tag. You should not normally need to call it in your own code. 1474 * 1475 * @param moodle_page $page 1476 * @param core_renderer $renderer 1477 * @return string the HTML code to to inside the <head> tag. 1478 */ 1479 public function get_head_code(moodle_page $page, core_renderer $renderer) { 1480 global $CFG; 1481 1482 // Note: the $page and $output are not stored here because it would 1483 // create circular references in memory which prevents garbage collection. 1484 $this->init_requirements_data($page, $renderer); 1485 1486 $output = ''; 1487 1488 // Add all standard CSS for this page. 1489 $output .= $this->get_css_code(); 1490 1491 // Set up the M namespace. 1492 $js = "var M = {}; M.yui = {};\n"; 1493 1494 // Capture the time now ASAP during page load. This minimises the lag when 1495 // we try to relate times on the server to times in the browser. 1496 // An example of where this is used is the quiz countdown timer. 1497 $js .= "M.pageloadstarttime = new Date();\n"; 1498 1499 // Add a subset of Moodle configuration to the M namespace. 1500 $js .= js_writer::set_variable('M.cfg', $this->M_cfg, false); 1501 1502 // Set up global YUI3 loader object - this should contain all code needed by plugins. 1503 // Note: in JavaScript just use "YUI().use('overlay', function(Y) { .... });", 1504 // this needs to be done before including any other script. 1505 $js .= $this->YUI_config->get_config_functions(); 1506 $js .= js_writer::set_variable('YUI_config', $this->YUI_config, false) . "\n"; 1507 $js .= "M.yui.loader = {modules: {}};\n"; // Backwards compatibility only, not used any more. 1508 $js = $this->YUI_config->update_header_js($js); 1509 1510 $output .= html_writer::script($js); 1511 1512 // Add variables. 1513 if ($this->jsinitvariables['head']) { 1514 $js = ''; 1515 foreach ($this->jsinitvariables['head'] as $data) { 1516 list($var, $value) = $data; 1517 $js .= js_writer::set_variable($var, $value, true); 1518 } 1519 $output .= html_writer::script($js); 1520 } 1521 1522 // Mark head sending done, it is not possible to anything there. 1523 $this->headdone = true; 1524 1525 return $output; 1526 } 1527 1528 /** 1529 * Generate any HTML that needs to go at the start of the <body> tag. 1530 * 1531 * Normally, this method is called automatically by the code that prints the 1532 * <head> tag. You should not normally need to call it in your own code. 1533 * 1534 * @return string the HTML code to go at the start of the <body> tag. 1535 */ 1536 public function get_top_of_body_code() { 1537 // First the skip links. 1538 $links = ''; 1539 $attributes = array('class' => 'skip'); 1540 foreach ($this->skiplinks as $url => $text) { 1541 $links .= html_writer::link('#'.$url, $text, $attributes); 1542 } 1543 $output = html_writer::tag('div', $links, array('class'=>'skiplinks')) . "\n"; 1544 $this->js_init_call('M.util.init_skiplink'); 1545 1546 // YUI3 JS needs to be loaded early in the body. It should be cached well by the browser. 1547 $output .= $this->get_yui3lib_headcode(); 1548 1549 // Add hacked jQuery support, it is not intended for standard Moodle distribution! 1550 $output .= $this->get_jquery_headcode(); 1551 1552 // Link our main JS file, all core stuff should be there. 1553 $output .= html_writer::script('', $this->js_fix_url('/lib/javascript-static.js')); 1554 1555 // All the other linked things from HEAD - there should be as few as possible. 1556 if ($this->jsincludes['head']) { 1557 foreach ($this->jsincludes['head'] as $url) { 1558 $output .= html_writer::script('', $url); 1559 } 1560 } 1561 1562 // Then the clever trick for hiding of things not needed when JS works. 1563 $output .= html_writer::script("document.body.className += ' jsenabled';") . "\n"; 1564 $this->topofbodydone = true; 1565 return $output; 1566 } 1567 1568 /** 1569 * Generate any HTML that needs to go at the end of the page. 1570 * 1571 * Normally, this method is called automatically by the code that prints the 1572 * page footer. You should not normally need to call it in your own code. 1573 * 1574 * @return string the HTML code to to at the end of the page. 1575 */ 1576 public function get_end_code() { 1577 global $CFG; 1578 $output = ''; 1579 1580 // Set the log level for the JS logging. 1581 $logconfig = new stdClass(); 1582 $logconfig->level = 'warn'; 1583 if ($CFG->debugdeveloper) { 1584 $logconfig->level = 'trace'; 1585 } 1586 $this->js_call_amd('core/log', 'setConfig', array($logconfig)); 1587 1588 // Call amd init functions. 1589 $output .= $this->get_amd_footercode(); 1590 1591 // Add other requested modules. 1592 $output .= $this->get_extra_modules_code(); 1593 1594 $this->js_init_code('M.util.js_complete("init");', true); 1595 1596 // All the other linked scripts - there should be as few as possible. 1597 if ($this->jsincludes['footer']) { 1598 foreach ($this->jsincludes['footer'] as $url) { 1599 $output .= html_writer::script('', $url); 1600 } 1601 } 1602 1603 // Add all needed strings. 1604 // First add core strings required for some dialogues. 1605 $this->strings_for_js(array( 1606 'confirm', 1607 'yes', 1608 'no', 1609 'areyousure', 1610 'closebuttontitle', 1611 'unknownerror', 1612 ), 'moodle'); 1613 if (!empty($this->stringsforjs)) { 1614 $strings = array(); 1615 foreach ($this->stringsforjs as $component=>$v) { 1616 foreach($v as $indentifier => $langstring) { 1617 $strings[$component][$indentifier] = $langstring->out(); 1618 } 1619 } 1620 $output .= html_writer::script(js_writer::set_variable('M.str', $strings)); 1621 } 1622 1623 // Add variables. 1624 if ($this->jsinitvariables['footer']) { 1625 $js = ''; 1626 foreach ($this->jsinitvariables['footer'] as $data) { 1627 list($var, $value) = $data; 1628 $js .= js_writer::set_variable($var, $value, true); 1629 } 1630 $output .= html_writer::script($js); 1631 } 1632 1633 $inyuijs = $this->get_javascript_code(false); 1634 $ondomreadyjs = $this->get_javascript_code(true); 1635 $jsinit = $this->get_javascript_init_code(); 1636 $handlersjs = $this->get_event_handler_code(); 1637 1638 // There is a global Y, make sure it is available in your scope. 1639 $js = "(function() {{$inyuijs}{$ondomreadyjs}{$jsinit}{$handlersjs}})();"; 1640 1641 $output .= html_writer::script($js); 1642 1643 return $output; 1644 } 1645 1646 /** 1647 * Have we already output the code in the <head> tag? 1648 * 1649 * @return bool 1650 */ 1651 public function is_head_done() { 1652 return $this->headdone; 1653 } 1654 1655 /** 1656 * Have we already output the code at the start of the <body> tag? 1657 * 1658 * @return bool 1659 */ 1660 public function is_top_of_body_done() { 1661 return $this->topofbodydone; 1662 } 1663 1664 /** 1665 * Should we generate a bit of content HTML that is only required once on 1666 * this page (e.g. the contents of the modchooser), now? Basically, we call 1667 * {@link has_one_time_item_been_created()}, and if the thing has not already 1668 * been output, we return true to tell the caller to generate it, and also 1669 * call {@link set_one_time_item_created()} to record the fact that it is 1670 * about to be generated. 1671 * 1672 * That is, a typical usage pattern (in a renderer method) is: 1673 * <pre> 1674 * if (!$this->page->requires->should_create_one_time_item_now($thing)) { 1675 * return ''; 1676 * } 1677 * // Else generate it. 1678 * </pre> 1679 * 1680 * @param string $thing identifier for the bit of content. Should be of the form 1681 * frankenstyle_things, e.g. core_course_modchooser. 1682 * @return bool if true, the caller should generate that bit of output now, otherwise don't. 1683 */ 1684 public function should_create_one_time_item_now($thing) { 1685 if ($this->has_one_time_item_been_created($thing)) { 1686 return false; 1687 } 1688 1689 $this->set_one_time_item_created($thing); 1690 return true; 1691 } 1692 1693 /** 1694 * Has a particular bit of HTML that is only required once on this page 1695 * (e.g. the contents of the modchooser) already been generated? 1696 * 1697 * Normally, you can use the {@link should_create_one_time_item_now()} helper 1698 * method rather than calling this method directly. 1699 * 1700 * @param string $thing identifier for the bit of content. Should be of the form 1701 * frankenstyle_things, e.g. core_course_modchooser. 1702 * @return bool whether that bit of output has been created. 1703 */ 1704 public function has_one_time_item_been_created($thing) { 1705 return isset($this->onetimeitemsoutput[$thing]); 1706 } 1707 1708 /** 1709 * Indicate that a particular bit of HTML that is only required once on this 1710 * page (e.g. the contents of the modchooser) has been generated (or is about to be)? 1711 * 1712 * Normally, you can use the {@link should_create_one_time_item_now()} helper 1713 * method rather than calling this method directly. 1714 * 1715 * @param string $thing identifier for the bit of content. Should be of the form 1716 * frankenstyle_things, e.g. core_course_modchooser. 1717 */ 1718 public function set_one_time_item_created($thing) { 1719 if ($this->has_one_time_item_been_created($thing)) { 1720 throw new coding_exception($thing . ' is only supposed to be ouput ' . 1721 'once per page, but it seems to be being output again.'); 1722 } 1723 return $this->onetimeitemsoutput[$thing] = true; 1724 } 1725 } 1726 1727 /** 1728 * This class represents the YUI configuration. 1729 * 1730 * @copyright 2013 Andrew Nicols 1731 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1732 * @since Moodle 2.5 1733 * @package core 1734 * @category output 1735 */ 1736 class YUI_config { 1737 /** 1738 * These settings must be public so that when the object is converted to json they are exposed. 1739 * Note: Some of these are camelCase because YUI uses camelCase variable names. 1740 * 1741 * The settings are described and documented in the YUI API at: 1742 * - http://yuilibrary.com/yui/docs/api/classes/config.html 1743 * - http://yuilibrary.com/yui/docs/api/classes/Loader.html 1744 */ 1745 public $debug = false; 1746 public $base; 1747 public $comboBase; 1748 public $combine; 1749 public $filter = null; 1750 public $insertBefore = 'firstthemesheet'; 1751 public $groups = array(); 1752 public $modules = array(); 1753 1754 /** 1755 * @var array List of functions used by the YUI Loader group pattern recognition. 1756 */ 1757 protected $jsconfigfunctions = array(); 1758 1759 /** 1760 * Create a new group within the YUI_config system. 1761 * 1762 * @param String $name The name of the group. This must be unique and 1763 * not previously used. 1764 * @param Array $config The configuration for this group. 1765 * @return void 1766 */ 1767 public function add_group($name, $config) { 1768 if (isset($this->groups[$name])) { 1769 throw new coding_exception("A YUI configuration group for '{$name}' already exists. To make changes to this group use YUI_config->update_group()."); 1770 } 1771 $this->groups[$name] = $config; 1772 } 1773 1774 /** 1775 * Update an existing group configuration 1776 * 1777 * Note, any existing configuration for that group will be wiped out. 1778 * This includes module configuration. 1779 * 1780 * @param String $name The name of the group. This must be unique and 1781 * not previously used. 1782 * @param Array $config The configuration for this group. 1783 * @return void 1784 */ 1785 public function update_group($name, $config) { 1786 if (!isset($this->groups[$name])) { 1787 throw new coding_exception('The Moodle YUI module does not exist. You must define the moodle module config using YUI_config->add_module_config first.'); 1788 } 1789 $this->groups[$name] = $config; 1790 } 1791 1792 /** 1793 * Set the value of a configuration function used by the YUI Loader's pattern testing. 1794 * 1795 * Only the body of the function should be passed, and not the whole function wrapper. 1796 * 1797 * The JS function your write will be passed a single argument 'name' containing the 1798 * name of the module being loaded. 1799 * 1800 * @param $function String the body of the JavaScript function. This should be used i 1801 * @return String the name of the function to use in the group pattern configuration. 1802 */ 1803 public function set_config_function($function) { 1804 $configname = 'yui' . (count($this->jsconfigfunctions) + 1) . 'ConfigFn'; 1805 if (isset($this->jsconfigfunctions[$configname])) { 1806 throw new coding_exception("A YUI config function with this name already exists. Config function names must be unique."); 1807 } 1808 $this->jsconfigfunctions[$configname] = $function; 1809 return '@' . $configname . '@'; 1810 } 1811 1812 /** 1813 * Allow setting of the config function described in {@see set_config_function} from a file. 1814 * The contents of this file are then passed to set_config_function. 1815 * 1816 * When jsrev is positive, the function is minified and stored in a MUC cache for subsequent uses. 1817 * 1818 * @param $file The path to the JavaScript function used for YUI configuration. 1819 * @return String the name of the function to use in the group pattern configuration. 1820 */ 1821 public function set_config_source($file) { 1822 global $CFG; 1823 $cache = cache::make('core', 'yuimodules'); 1824 1825 // Attempt to get the metadata from the cache. 1826 $keyname = 'configfn_' . $file; 1827 $fullpath = $CFG->dirroot . '/' . $file; 1828 if (!isset($CFG->jsrev) || $CFG->jsrev == -1) { 1829 $cache->delete($keyname); 1830 $configfn = file_get_contents($fullpath); 1831 } else { 1832 $configfn = $cache->get($keyname); 1833 if ($configfn === false) { 1834 require_once($CFG->libdir . '/jslib.php'); 1835 $configfn = core_minify::js_files(array($fullpath)); 1836 $cache->set($keyname, $configfn); 1837 } 1838 } 1839 return $this->set_config_function($configfn); 1840 } 1841 1842 /** 1843 * Retrieve the list of JavaScript functions for YUI_config groups. 1844 * 1845 * @return String The complete set of config functions 1846 */ 1847 public function get_config_functions() { 1848 $configfunctions = ''; 1849 foreach ($this->jsconfigfunctions as $functionname => $function) { 1850 $configfunctions .= "var {$functionname} = function(me) {"; 1851 $configfunctions .= $function; 1852 $configfunctions .= "};\n"; 1853 } 1854 return $configfunctions; 1855 } 1856 1857 /** 1858 * Update the header JavaScript with any required modification for the YUI Loader. 1859 * 1860 * @param $js String The JavaScript to manipulate. 1861 * @return String the modified JS string. 1862 */ 1863 public function update_header_js($js) { 1864 // Update the names of the the configFn variables. 1865 // The PHP json_encode function cannot handle literal names so we have to wrap 1866 // them in @ and then replace them with literals of the same function name. 1867 foreach ($this->jsconfigfunctions as $functionname => $function) { 1868 $js = str_replace('"@' . $functionname . '@"', $functionname, $js); 1869 } 1870 return $js; 1871 } 1872 1873 /** 1874 * Add configuration for a specific module. 1875 * 1876 * @param String $name The name of the module to add configuration for. 1877 * @param Array $config The configuration for the specified module. 1878 * @param String $group The name of the group to add configuration for. 1879 * If not specified, then this module is added to the global 1880 * configuration. 1881 * @return void 1882 */ 1883 public function add_module_config($name, $config, $group = null) { 1884 if ($group) { 1885 if (!isset($this->groups[$name])) { 1886 throw new coding_exception('The Moodle YUI module does not exist. You must define the moodle module config using YUI_config->add_module_config first.'); 1887 } 1888 if (!isset($this->groups[$group]['modules'])) { 1889 $this->groups[$group]['modules'] = array(); 1890 } 1891 $modules = &$this->groups[$group]['modules']; 1892 } else { 1893 $modules = &$this->modules; 1894 } 1895 $modules[$name] = $config; 1896 } 1897 1898 /** 1899 * Add the moodle YUI module metadata for the moodle group to the YUI_config instance. 1900 * 1901 * If js caching is disabled, metadata will not be served causing YUI to calculate 1902 * module dependencies as each module is loaded. 1903 * 1904 * If metadata does not exist it will be created and stored in a MUC entry. 1905 * 1906 * @return void 1907 */ 1908 public function add_moodle_metadata() { 1909 global $CFG; 1910 if (!isset($this->groups['moodle'])) { 1911 throw new coding_exception('The Moodle YUI module does not exist. You must define the moodle module config using YUI_config->add_module_config first.'); 1912 } 1913 1914 if (!isset($this->groups['moodle']['modules'])) { 1915 $this->groups['moodle']['modules'] = array(); 1916 } 1917 1918 $cache = cache::make('core', 'yuimodules'); 1919 if (!isset($CFG->jsrev) || $CFG->jsrev == -1) { 1920 $metadata = array(); 1921 $metadata = $this->get_moodle_metadata(); 1922 $cache->delete('metadata'); 1923 } else { 1924 // Attempt to get the metadata from the cache. 1925 if (!$metadata = $cache->get('metadata')) { 1926 $metadata = $this->get_moodle_metadata(); 1927 $cache->set('metadata', $metadata); 1928 } 1929 } 1930 1931 // Merge with any metadata added specific to this page which was added manually. 1932 $this->groups['moodle']['modules'] = array_merge($this->groups['moodle']['modules'], 1933 $metadata); 1934 } 1935 1936 /** 1937 * Determine the module metadata for all moodle YUI modules. 1938 * 1939 * This works through all modules capable of serving YUI modules, and attempts to get 1940 * metadata for each of those modules. 1941 * 1942 * @return Array of module metadata 1943 */ 1944 private function get_moodle_metadata() { 1945 $moodlemodules = array(); 1946 // Core isn't a plugin type or subsystem - handle it seperately. 1947 if ($module = $this->get_moodle_path_metadata(core_component::get_component_directory('core'))) { 1948 $moodlemodules = array_merge($moodlemodules, $module); 1949 } 1950 1951 // Handle other core subsystems. 1952 $subsystems = core_component::get_core_subsystems(); 1953 foreach ($subsystems as $subsystem => $path) { 1954 if (is_null($path)) { 1955 continue; 1956 } 1957 if ($module = $this->get_moodle_path_metadata($path)) { 1958 $moodlemodules = array_merge($moodlemodules, $module); 1959 } 1960 } 1961 1962 // And finally the plugins. 1963 $plugintypes = core_component::get_plugin_types(); 1964 foreach ($plugintypes as $plugintype => $pathroot) { 1965 $pluginlist = core_component::get_plugin_list($plugintype); 1966 foreach ($pluginlist as $plugin => $path) { 1967 if ($module = $this->get_moodle_path_metadata($path)) { 1968 $moodlemodules = array_merge($moodlemodules, $module); 1969 } 1970 } 1971 } 1972 1973 return $moodlemodules; 1974 } 1975 1976 /** 1977 * Helper function process and return the YUI metadata for all of the modules under the specified path. 1978 * 1979 * @param String $path the UNC path to the YUI src directory. 1980 * @return Array the complete array for frankenstyle directory. 1981 */ 1982 private function get_moodle_path_metadata($path) { 1983 // Add module metadata is stored in frankenstyle_modname/yui/src/yui_modname/meta/yui_modname.json. 1984 $baseyui = $path . '/yui/src'; 1985 $modules = array(); 1986 if (is_dir($baseyui)) { 1987 $items = new DirectoryIterator($baseyui); 1988 foreach ($items as $item) { 1989 if ($item->isDot() or !$item->isDir()) { 1990 continue; 1991 } 1992 $metafile = realpath($baseyui . '/' . $item . '/meta/' . $item . '.json'); 1993 if (!is_readable($metafile)) { 1994 continue; 1995 } 1996 $metadata = file_get_contents($metafile); 1997 $modules = array_merge($modules, (array) json_decode($metadata)); 1998 } 1999 } 2000 return $modules; 2001 } 2002 2003 /** 2004 * Define YUI modules which we have been required to patch between releases. 2005 * 2006 * We must do this because we aggressively cache content on the browser, and we must also override use of the 2007 * external CDN which will serve the true authoritative copy of the code without our patches. 2008 * 2009 * @param String combobase The local combobase 2010 * @param String yuiversion The current YUI version 2011 * @param Int patchlevel The patch level we're working to for YUI 2012 * @param Array patchedmodules An array containing the names of the patched modules 2013 * @return void 2014 */ 2015 public function define_patched_core_modules($combobase, $yuiversion, $patchlevel, $patchedmodules) { 2016 // The version we use is suffixed with a patchlevel so that we can get additional revisions between YUI releases. 2017 $subversion = $yuiversion . '_' . $patchlevel; 2018 2019 if ($this->comboBase == $combobase) { 2020 // If we are using the local combobase in the loader, we can add a group and still make use of the combo 2021 // loader. We just need to specify a different root which includes a slightly different YUI version number 2022 // to include our patchlevel. 2023 $patterns = array(); 2024 $modules = array(); 2025 foreach ($patchedmodules as $modulename) { 2026 // We must define the pattern and module here so that the loader uses our group configuration instead of 2027 // the standard module definition. We may lose some metadata provided by upstream but this will be 2028 // loaded when the module is loaded anyway. 2029 $patterns[$modulename] = array( 2030 'group' => 'yui-patched', 2031 ); 2032 $modules[$modulename] = array(); 2033 } 2034 2035 // Actually add the patch group here. 2036 $this->add_group('yui-patched', array( 2037 'combine' => true, 2038 'root' => $subversion . '/', 2039 'patterns' => $patterns, 2040 'modules' => $modules, 2041 )); 2042 2043 } else { 2044 // The CDN is in use - we need to instead use the local combobase for this module and override the modules 2045 // definition. We cannot use the local base - we must use the combobase because we cannot invalidate the 2046 // local base in browser caches. 2047 $fullpathbase = $combobase . $subversion . '/'; 2048 foreach ($patchedmodules as $modulename) { 2049 $this->modules[$modulename] = array( 2050 'fullpath' => $fullpathbase . $modulename . '/' . $modulename . '-min.js' 2051 ); 2052 } 2053 } 2054 } 2055 } 2056 2057 /** 2058 * Invalidate all server and client side JS caches. 2059 */ 2060 function js_reset_all_caches() { 2061 global $CFG; 2062 2063 $next = time(); 2064 if (isset($CFG->jsrev) and $next <= $CFG->jsrev and $CFG->jsrev - $next < 60*60) { 2065 // This resolves problems when reset is requested repeatedly within 1s, 2066 // the < 1h condition prevents accidental switching to future dates 2067 // because we might not recover from it. 2068 $next = $CFG->jsrev+1; 2069 } 2070 2071 set_config('jsrev', $next); 2072 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |