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