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