MDL-28544 workaround for missing YUI 3.4.0 loader info
[moodle.git] / lib / outputrequirementslib.php
CommitLineData
0bb38e8c
PS
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18
19/**
20 * Library functions to facilitate the use of JavaScript in Moodle.
21 *
78bfb562
PS
22 * @package core
23 * @subpackage lib
24 * @copyright 2009 Tim Hunt, 2010 Petr Skoda
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0bb38e8c
PS
26 */
27
78bfb562
PS
28defined('MOODLE_INTERNAL') || die();
29
0bb38e8c
PS
30// note: you can find history of this file in lib/ajax/ajaxlib.php
31
32/**
33 * This class tracks all the things that are needed by the current page.
34 *
35 * Normally, the only instance of this class you will need to work with is the
36 * one accessible via $PAGE->requires.
37 *
173ba4f1 38 * Typical usage would be
0bb38e8c 39 * <pre>
3b073254 40 * $PAGE->requires->js_init_call('M.mod_forum.init_view');
0bb38e8c
PS
41 * </pre>
42 *
43 * It also supports obsoleted coding style withouth YUI3 modules.
44 * <pre>
173ba4f1 45 * $PAGE->requires->css('/mod/mymod/userstyles.php?id='.$id); // not overridable via themes!
0bb38e8c
PS
46 * $PAGE->requires->js('/mod/mymod/script.js');
47 * $PAGE->requires->js('/mod/mymod/small_but_urgent.js', true);
48 * $PAGE->requires->js_function_call('init_mymod', array($data), true);
49 * </pre>
50 *
51 * There are some natural restrictions on some methods. For example, {@link css()}
52 * can only be called before the <head> tag is output. See the comments on the
53 * individual methods for details.
54 *
55 * @copyright 2009 Tim Hunt, 2010 Petr Skoda
56 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
57 * @since Moodle 2.0
58 */
59class page_requirements_manager {
60 /** List of string available from JS */
61 protected $stringsforjs = array();
62 /** List of JS variables to be initialised */
63 protected $jsinitvariables = array('head'=>array(), 'footer'=>array());
64 /** Included JS scripts */
65 protected $jsincludes = array('head'=>array(), 'footer'=>array());
66 /** List of needed function calls */
67 protected $jscalls = array('normal'=>array(), 'ondomready'=>array());
68 /**
69 * List of skip links, those are needed for accessibility reasons
70 * @var array
71 */
72 protected $skiplinks = array();
73 /**
173ba4f1 74 * Javascript code used for initialisation of page, it should be relatively small
0bb38e8c
PS
75 * @var array
76 */
77 protected $jsinitcode = array();
78 /**
79 * Theme sheets, initialised only from core_renderer
80 * @var array of moodle_url
81 */
82 protected $cssthemeurls = array();
83 /**
84 * List of custom theme sheets, these are strongly discouraged!
85 * Useful mostly only for CSS submitted by teachers that is not part of the theme.
86 * @var array of moodle_url
87 */
88 protected $cssurls = array();
89 /**
90 * List of requested event handlers
91 * @var array
92 */
93 protected $eventhandlers = array();
94 /**
95 * Extra modules
96 * @var array
97 */
98 protected $extramodules = array();
99 /** Flag indicated head stuff already printed */
100 protected $headdone = false;
101 /** Flag indicating top of body already printed */
102 protected $topofbodydone = false;
103
104 /** YUI PHPLoader instance responsible for YUI2 loading from PHP only */
105 protected $yui2loader;
106 /** YUI PHPLoader instance responsible for YUI3 loading from PHP only */
107 protected $yui3loader;
ef7329dc 108 /** YUI loader information for YUI3 loading from javascript */
0bb38e8c
PS
109 protected $M_yui_loader;
110 /** some config vars exposed in JS, please no secret stuff there */
111 protected $M_cfg;
d067fc47
SH
112 /** stores debug backtraces from when JS modules were included in the page */
113 protected $debug_moduleloadstacktraces = array();
0bb38e8c
PS
114
115 /**
116 * Page requirements constructor.
117 */
118 public function __construct() {
119 global $CFG;
120
121 require_once("$CFG->libdir/yui/phploader/phploader/loader.php");
122
ef7329dc 123 $this->yui3loader = new stdClass();
0bb38e8c
PS
124 $this->yui2loader = new YAHOO_util_Loader($CFG->yui2version);
125
126 // set up some loader options
127 if (debugging('', DEBUG_DEVELOPER)) {
573e7e33
DC
128 $this->yui3loader->filter = YUI_DEBUG; // alternatively we could use just YUI_RAW here
129 $this->yui2loader->filter = YUI_DEBUG; // alternatively we could use just YUI_RAW here
0bb38e8c
PS
130 } else {
131 $this->yui3loader->filter = null;
132 $this->yui2loader->filter = null;
133 }
134 if (!empty($CFG->useexternalyui)) {
135 $this->yui3loader->base = 'http://yui.yahooapis.com/' . $CFG->yui3version . '/build/';
136 $this->yui2loader->base = 'http://yui.yahooapis.com/' . $CFG->yui2version . '/build/';
137 $this->yui3loader->comboBase = 'http://yui.yahooapis.com/combo?';
138 $this->yui2loader->comboBase = 'http://yui.yahooapis.com/combo?';
139 } else {
140 $this->yui3loader->base = $CFG->httpswwwroot . '/lib/yui/'. $CFG->yui3version . '/build/';
141 $this->yui2loader->base = $CFG->httpswwwroot . '/lib/yui/'. $CFG->yui2version . '/build/';
142 $this->yui3loader->comboBase = $CFG->httpswwwroot . '/theme/yui_combo.php?';
143 $this->yui2loader->comboBase = $CFG->httpswwwroot . '/theme/yui_combo.php?';
144 }
145
146 // enable combo loader? this significantly helps with caching and performance!
147 $this->yui3loader->combine = !empty($CFG->yuicomboloading);
148 $this->yui2loader->combine = !empty($CFG->yuicomboloading);
149
77387297
SH
150 if (empty($CFG->cachejs)) {
151 $jsrev = -1;
152 } else if (empty($CFG->jsrev)) {
153 $jsrev = 1;
154 } else {
155 $jsrev = $CFG->jsrev;
156 }
f312e878 157
0bb38e8c
PS
158 // set up JS YUI loader helper object
159 $this->M_yui_loader = new stdClass();
160 $this->M_yui_loader->base = $this->yui3loader->base;
161 $this->M_yui_loader->comboBase = $this->yui3loader->comboBase;
162 $this->M_yui_loader->combine = $this->yui3loader->combine;
163 $this->M_yui_loader->filter = ($this->yui3loader->filter == YUI_DEBUG) ? 'debug' : '';
164 $this->M_yui_loader->insertBefore = 'firstthemesheet';
165 $this->M_yui_loader->modules = array();
2b722f87
SH
166 $this->M_yui_loader->groups = array(
167 'moodle' => array(
168 'name' => 'moodle',
77387297 169 'base' => $CFG->httpswwwroot . '/theme/yui_combo.php?moodle/'.$jsrev.'/',
2b722f87
SH
170 'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php?',
171 'combine' => $this->yui3loader->combine,
172 'filter' => '',
173 'ext' => false,
77387297 174 'root' => 'moodle/'.$jsrev.'/', // Add the rev to the root path so that we can control caching
2b722f87
SH
175 'patterns' => array(
176 'moodle-' => array(
177 'group' => 'moodle',
178 'configFn' => '@MOODLECONFIGFN@'
2a102b90 179 ),
2b722f87 180 'root' => 'moodle'
2a102b90 181 )
2b722f87
SH
182 ),
183 'local' => array(
184 'name' => 'gallery',
185 'base' => $CFG->wwwroot.'/lib/yui/gallery/',
186 'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php?',
187 'combine' => $this->yui3loader->combine,
188 'filter' => $this->M_yui_loader->filter,
189 'ext' => false,
190 'root' => 'gallery/',
191 'patterns' => array(
192 'gallery-' => array(
193 'group' => 'gallery',
194 'configFn' => '@GALLERYCONFIGFN@',
195 ),
196 'root' => 'gallery'
197 )
198 )
199 );
0bb38e8c 200 $this->add_yui2_modules(); // adds loading info for all YUI2 modules
984915af 201 $this->js_module($this->find_module('core_filepicker'));
0bb38e8c
PS
202 $this->js_module($this->find_module('core_dock'));
203
0bb38e8c
PS
204 }
205
206 /**
207 * This method adds yui2 modules into the yui3 JS loader-
208 * @return void
209 */
210 protected function add_yui2_modules() {
211 //note: this function is definitely not perfect, because
212 // it adds tons of markup into each page, but it can be
213 // abstracted into separate JS file with proper headers
214 global $CFG;
215
216 $GLOBALS['yui_current'] = array();
217 require($CFG->libdir.'/yui/phploader/lib/meta/config_'.$CFG->yui2version.'.php');
218 $info = $GLOBALS['yui_current'];
219 unset($GLOBALS['yui_current']);
220
221 if (empty($CFG->yuicomboloading)) {
222 $urlbase = $this->yui2loader->base;
223 } else {
224 $urlbase = $this->yui2loader->comboBase.$CFG->yui2version.'/build/';
225 }
226
227 $modules = array();
228 $ignored = array(); // list of CSS modules that are not needed
229 foreach ($info['moduleInfo'] as $name => $module) {
230 if ($module['type'] === 'css') {
231 $ignored[$name] = true;
232 } else {
233 $modules['yui2-'.$name] = $module;
234 }
235 }
236 foreach ($modules as $name=>$module) {
237 $module['fullpath'] = $urlbase.$module['path']; // fix path to point to correct location
238 unset($module['path']);
0139ec3f 239 unset($module['skinnable']); // we load all YUI2 css automatically, this prevents weird missing css loader problems
0bb38e8c
PS
240 foreach(array('requires', 'optional', 'supersedes') as $fixme) {
241 if (!empty($module[$fixme])) {
242 $fixed = false;
243 foreach ($module[$fixme] as $key=>$dep) {
244 if (isset($ignored[$dep])) {
245 unset($module[$fixme][$key]);
246 $fixed = true;
247 } else {
248 $module[$fixme][$key] = 'yui2-'.$dep;
249 }
250 }
251 if ($fixed) {
252 $module[$fixme] = array_merge($module[$fixme]); // fix keys
253 }
254 }
255 }
256 $this->M_yui_loader->modules[$name] = $module;
d067fc47
SH
257 if (debugging('', DEBUG_DEVELOPER)) {
258 if (!array_key_exists($name, $this->debug_moduleloadstacktraces)) {
259 $this->debug_moduleloadstacktraces[$name] = array();
260 }
261 $this->debug_moduleloadstacktraces[$name][] = format_backtrace(debug_backtrace());
262 }
0bb38e8c
PS
263 }
264 }
265
266 /**
267 * Initialise with the bits of JavaScript that every Moodle page should have.
268 *
269 * @param moodle_page $page
270 * @param core_renderer $output
271 */
272 protected function init_requirements_data(moodle_page $page, core_renderer $renderer) {
273 global $CFG;
274
275 // JavaScript should always work with $CFG->httpswwwroot rather than $CFG->wwwroot.
276 // Otherwise, in some situations, users will get warnings about insecure content
4aea3cc7 277 // on secure pages from their web browser.
0bb38e8c
PS
278
279 $this->M_cfg = array(
280 'wwwroot' => $CFG->httpswwwroot, // Yes, really. See above.
281 'sesskey' => sesskey(),
282 'loadingicon' => $renderer->pix_url('i/loading_small', 'moodle')->out(false),
283 'themerev' => theme_get_revision(),
284 'theme' => $page->theme->name,
fcd2cbaf 285 'jsrev' => ((empty($CFG->cachejs) or empty($CFG->jsrev)) ? -1 : $CFG->jsrev),
0bb38e8c
PS
286 );
287 if (debugging('', DEBUG_DEVELOPER)) {
288 $this->M_cfg['developerdebug'] = true;
289 $this->yui2_lib('logger');
290 }
291
292 // accessibility stuff
2cf81209 293 $this->skip_link_to('maincontent', get_string('tocontent', 'access'));
0bb38e8c
PS
294
295 // to be removed soon
296 $this->yui2_lib('dom'); // at least javascript-static.js needs to be migrated to YUI3
297
298 $this->string_for_js('confirmation', 'admin');
299 $this->string_for_js('cancel', 'moodle');
300 $this->string_for_js('yes', 'moodle');
879b4f9a 301
4aea3cc7
PS
302 if ($page->pagelayout === 'frametop') {
303 $this->js_init_call('M.util.init_frametop');
304 }
0bb38e8c
PS
305 }
306
307 /**
308 * Ensure that the specified JavaScript file is linked to from this page.
309 *
310 * NOTE: This function is to be used in rare cases only, please store your JS in module.js file
311 * and use $PAGE->requires->js_init_call() instead.
312 *
313 * By default the link is put at the end of the page, since this gives best page-load performance.
314 *
315 * Even if a particular script is requested more than once, it will only be linked
316 * to once.
317 *
318 * @param string|moodle_url $url The path to the .js file, relative to $CFG->dirroot / $CFG->wwwroot.
319 * For example '/mod/mymod/customscripts.js'; use moodle_url for external scripts
320 * @param bool $inhead initialise in head
321 * @return void
322 */
323 public function js($url, $inhead=false) {
324 $url = $this->js_fix_url($url);
325 $where = $inhead ? 'head' : 'footer';
326 $this->jsincludes[$where][$url->out()] = $url;
327 }
328
329 /**
173ba4f1 330 * Ensure that the specified YUI2 library file, and all its required dependencies,
0bb38e8c
PS
331 * are linked to from this page.
332 *
333 * By default the link is put at the end of the page, since this gives best page-load
334 * performance. Optional dependencies are not loaded automatically - if you want
335 * them you will need to load them first with other calls to this method.
336 *
173ba4f1 337 * Even if a particular library is requested more than once (perhaps as a dependency
0bb38e8c
PS
338 * of other libraries) it will only be linked to once.
339 *
340 * The library is leaded as soon as possible, if $OUTPUT->header() not used yet it
341 * is put into the page header, otherwise it is loaded in the page footer.
342 *
343 * @param string|array $libname the name of the YUI2 library you require. For example 'autocomplete'.
344 * @return void
345 */
346 public function yui2_lib($libname) {
347 $libnames = (array)$libname;
348 foreach ($libnames as $lib) {
349 $this->yui2loader->load($lib);
350 }
351 }
352
353 /**
354 * Returns the actual url through which a script is served.
355 * @param moodle_url|string $url full moodle url, or shortened path to script
356 * @return moodle_url
357 */
358 protected function js_fix_url($url) {
359 global $CFG;
360
361 if ($url instanceof moodle_url) {
362 return $url;
363 } else if (strpos($url, '/') === 0) {
364 if (debugging()) {
365 // check file existence only when in debug mode
366 if (!file_exists($CFG->dirroot . strtok($url, '?'))) {
44fab7fa 367 throw new coding_exception('Attempt to require a JavaScript file that does not exist.', $url);
0bb38e8c
PS
368 }
369 }
621df74e 370 if (!empty($CFG->cachejs) and !empty($CFG->jsrev) and strpos($url, '/lib/editor/') !== 0 and substr($url, -3) === '.js') {
0139ec3f
PS
371 return new moodle_url($CFG->httpswwwroot.'/lib/javascript.php', array('file'=>$url, 'rev'=>$CFG->jsrev));
372 } else {
373 return new moodle_url($CFG->httpswwwroot.$url);
374 }
0bb38e8c
PS
375 } else {
376 throw new coding_exception('Invalid JS url, it has to be shortened url starting with / or moodle_url instance.', $url);
377 }
378 }
379
380 /**
173ba4f1 381 * Find out if JS module present and return details.
88623be1 382 * @param string $component name of component in frankenstyle, ex: core_group, mod_forum
0bb38e8c
PS
383 * @return array description of module or null if not found
384 */
88623be1 385 protected function find_module($component) {
0bb38e8c
PS
386 global $CFG;
387
388 $module = null;
389
879b4f9a 390
88623be1 391 if (strpos($component, 'core_') === 0) {
0bb38e8c
PS
392 // must be some core stuff - list here is not complete, this is just the stuff used from multiple places
393 // so that we do nto have to repeat the definition of these modules over and over again
88623be1 394 switch($component) {
0bb38e8c
PS
395 case 'core_filepicker':
396 $module = array('name' => 'core_filepicker',
397 'fullpath' => '/repository/filepicker.js',
0e77c304 398 'requires' => array('base', 'node', 'node-event-simulate', 'json', 'async-queue', 'io', 'yui2-button', 'yui2-container', 'yui2-layout', 'yui2-menu', 'yui2-treeview', 'yui2-dragdrop', 'yui2-cookie'),
87ad1edc
PS
399 'strings' => array(array('add', 'repository'), array('back', 'repository'), array('cancel', 'moodle'), array('close', 'repository'),
400 array('cleancache', 'repository'), array('copying', 'repository'), array('date', 'repository'), array('downloadsucc', 'repository'),
401 array('emptylist', 'repository'), array('error', 'repository'), array('federatedsearch', 'repository'),
402 array('filenotnull', 'repository'), array('getfile', 'repository'), array('help', 'moodle'), array('iconview', 'repository'),
403 array('invalidjson', 'repository'), array('linkexternal', 'repository'), array('listview', 'repository'),
404 array('loading', 'repository'), array('login', 'repository'), array('logout', 'repository'), array('noenter', 'repository'),
405 array('noresult', 'repository'), array('manageurl', 'repository'), array('popup', 'repository'), array('preview', 'repository'),
406 array('refresh', 'repository'), array('save', 'repository'), array('saveas', 'repository'), array('saved', 'repository'),
407 array('saving', 'repository'), array('search', 'repository'), array('searching', 'repository'), array('size', 'repository'),
408 array('submit', 'repository'), array('sync', 'repository'), array('title', 'repository'), array('upload', 'repository'),
409 array('uploading', 'repository'), array('xhtmlerror', 'repository'),
a6b53a7c 410 array('cancel'), array('chooselicense', 'repository'), array('author', 'repository'),
ed5f6c0f 411 array('ok', 'moodle'), array('error', 'moodle'), array('info', 'moodle'), array('norepositoriesavailable', 'repository'), array('norepositoriesexternalavailable', 'repository'),
f312e878 412 array('nofilesattached', 'repository'), array('filepicker', 'repository'),
f392caba
DC
413 array('nofilesavailable', 'repository'), array('overwrite', 'repository'),
414 array('renameto', 'repository'), array('fileexists', 'repository'),
415 array('fileexistsdialogheader', 'repository'), array('fileexistsdialog_editor', 'repository'),
416 array('fileexistsdialog_filemanager', 'repository')
879b4f9a 417 ));
0bb38e8c
PS
418 break;
419 case 'core_comment':
420 $module = array('name' => 'core_comment',
421 'fullpath' => '/comment/comment.js',
4b201bc6 422 'requires' => array('base', 'io', 'node', 'json', 'yui2-animation', 'overlay'),
535b4a51 423 'strings' => array(array('confirmdeletecomments', 'admin'), array('yes', 'moodle'), array('no', 'moodle'))
34e20eb4 424 );
0bb38e8c
PS
425 break;
426 case 'core_role':
427 $module = array('name' => 'core_role',
87b4981b
SH
428 'fullpath' => '/admin/roles/module.js',
429 'requires' => array('node', 'cookie'));
0bb38e8c
PS
430 break;
431 case 'core_completion':
432 $module = array('name' => 'core_completion',
433 'fullpath' => '/course/completion.js');
434 break;
435 case 'core_dock':
436 $module = array('name' => 'core_dock',
437 'fullpath' => '/blocks/dock.js',
7e4617f7 438 'requires' => array('base', 'node', 'event-custom', 'event-mouseenter', 'event-resize'),
b496e634 439 'strings' => array(array('addtodock', 'block'),array('undockitem', 'block'),array('undockall', 'block'),array('thisdirectionvertical', 'langconfig')));
0bb38e8c 440 break;
0bb38e8c
PS
441 case 'core_message':
442 $module = array('name' => 'core_message',
1d72e9d4 443 'requires' => array('base', 'node', 'event', 'node-event-simulate'),
0bb38e8c
PS
444 'fullpath' => '/message/module.js');
445 break;
c2489597
SH
446 case 'core_group':
447 $module = array('name' => 'core_group',
448 'fullpath' => '/group/module.js',
449 'requires' => array('node', 'overlay', 'event-mouseenter'));
450 break;
157434a5
TH
451 case 'core_question_engine':
452 $module = array('name' => 'core_question_engine',
453 'fullpath' => '/question/qengine.js',
454 'requires' => array('node', 'event'));
455 break;
3dcdf440
PS
456 case 'core_rating':
457 $module = array('name' => 'core_rating',
a09aeee4
AD
458 'fullpath' => '/rating/module.js',
459 'requires' => array('node', 'event', 'overlay', 'io', 'json'));
460 break;
fb2fb46e
DC
461 case 'core_filetree':
462 $module = array('name' => 'core_filetree',
463 'fullpath' => '/files/module.js',
464 'requires' => array('node', 'event', 'overlay', 'io', 'json', 'yui2-treeview'));
465 break;
0bb38e8c
PS
466 }
467
468 } else {
ae586328
PS
469 if ($dir = get_component_directory($component)) {
470 if (file_exists("$dir/module.js")) {
471 if (strpos($dir, $CFG->dirroot.'/') === 0) {
472 $dir = substr($dir, strlen($CFG->dirroot));
473 $module = array('name'=>$component, 'fullpath'=>"$dir/module.js", 'requires' => array());
474 }
0bb38e8c
PS
475 }
476 }
477 }
478
479 return $module;
480 }
481
482 /**
483 * Append YUI3 module to default YUI3 JS loader.
484 * The structure of module array is described at http://developer.yahoo.com/yui/3/yui/:
485 * @param string|array $module name of module (details are autodetected), or full module specification as array
486 * @return void
487 */
488 public function js_module($module) {
489 global $CFG;
490
491 if (empty($module)) {
492 throw new coding_exception('Missing YUI3 module name or full description.');
493 }
494
495 if (is_string($module)) {
496 $module = $this->find_module($module);
497 }
498
499 if (empty($module) or empty($module['name']) or empty($module['fullpath'])) {
500 throw new coding_exception('Missing YUI3 module details.');
501 }
502
d067fc47
SH
503 // Don't load this module if we already have, no need to!
504 if ($this->js_module_loaded($module['name'])) {
505 if (debugging('', DEBUG_DEVELOPER)) {
506 $this->debug_moduleloadstacktraces[$module['name']][] = format_backtrace(debug_backtrace());
507 }
508 return;
509 }
510
0bb38e8c 511 $module['fullpath'] = $this->js_fix_url($module['fullpath'])->out(false);
87ad1edc
PS
512 // add all needed strings
513 if (!empty($module['strings'])) {
514 foreach ($module['strings'] as $string) {
515 $identifier = $string[0];
516 $component = isset($string[1]) ? $string[1] : 'moodle';
517 $a = isset($string[2]) ? $string[2] : null;
518 $this->string_for_js($identifier, $component, $a);
519 }
520 }
521 unset($module['strings']);
522
d067fc47
SH
523 // Process module requirements and attempt to load each. This allows
524 // moodle modules to require each other.
525 if (!empty($module['requires'])){
526 foreach ($module['requires'] as $requirement) {
527 $rmodule = $this->find_module($requirement);
528 if (is_array($rmodule)) {
529 $this->js_module($rmodule);
530 }
531 }
532 }
621df74e 533
0bb38e8c
PS
534 if ($this->headdone) {
535 $this->extramodules[$module['name']] = $module;
536 } else {
537 $this->M_yui_loader->modules[$module['name']] = $module;
538 }
d067fc47
SH
539 if (debugging('', DEBUG_DEVELOPER)) {
540 if (!array_key_exists($module['name'], $this->debug_moduleloadstacktraces)) {
541 $this->debug_moduleloadstacktraces[$module['name']] = array();
542 }
543 $this->debug_moduleloadstacktraces[$module['name']][] = format_backtrace(debug_backtrace());
544 }
545 }
546
547 /**
548 * Returns true if the module has already been loaded.
549 *
550 * @param string|array $modulename
551 * @return bool True if the module has already been loaded
552 */
553 protected function js_module_loaded($module) {
554 if (is_string($module)) {
555 $modulename = $module;
556 } else {
557 $modulename = $module['name'];
558 }
559 return array_key_exists($modulename, $this->M_yui_loader->modules) ||
560 array_key_exists($modulename, $this->extramodules);
561 }
562
563 /**
564 * Returns the stacktraces from loading js modules.
565 * @return array
566 */
567 public function get_loaded_modules() {
568 return $this->debug_moduleloadstacktraces;
0bb38e8c
PS
569 }
570
571 /**
572 * Ensure that the specified CSS file is linked to from this page.
573 *
574 * Because stylesheet links must go in the <head> part of the HTML, you must call
575 * this function before {@link get_head_code()} is called. That normally means before
576 * the call to print_header. If you call it when it is too late, an exception
577 * will be thrown.
578 *
579 * Even if a particular style sheet is requested more than once, it will only
580 * be linked to once.
581 *
86c2da17 582 * Please note use of this feature is strongly discouraged,
0bb38e8c
PS
583 * it is suitable only for places where CSS is submitted directly by teachers.
584 * (Students must not be allowed to submit any external CSS because it may
585 * contain embedded javascript!). Example of correct use is mod/data.
586 *
587 * @param string $stylesheet The path to the .css file, relative to $CFG->wwwroot.
588 * For example:
589 * $PAGE->requires->css('mod/data/css.php?d='.$data->id);
590 */
591 public function css($stylesheet) {
592 global $CFG;
593
594 if ($this->headdone) {
595 throw new coding_exception('Cannot require a CSS file after &lt;head> has been printed.', $stylesheet);
596 }
597
598 if ($stylesheet instanceof moodle_url) {
599 // ok
600 } else if (strpos($stylesheet, '/') === 0) {
601 $stylesheet = new moodle_url($CFG->httpswwwroot.$stylesheet);
602 } else {
603 throw new coding_exception('Invalid stylesheet parameter.', $stylesheet);
604 }
605
606 $this->cssurls[$stylesheet->out()] = $stylesheet; // overrides
607 }
608
609 /**
610 * Add theme stylkesheet to page - do not use from plugin code,
611 * this should be called only from the core renderer!
612 * @param moodle_url $stylesheet
613 * @return void
614 */
615 public function css_theme(moodle_url $stylesheet) {
616 $this->cssthemeurls[] = $stylesheet;
617 }
618
619 /**
620 * Ensure that a skip link to a given target is printed at the top of the <body>.
621 *
622 * You must call this function before {@link get_top_of_body_code()}, (if not, an exception
623 * will be thrown). That normally means you must call this before the call to print_header.
624 *
625 * If you ask for a particular skip link to be printed, it is then your responsibility
173ba4f1 626 * to ensure that the appropriate <a name="..."> tag is printed in the body of the
0bb38e8c
PS
627 * page, so that the skip link goes somewhere.
628 *
629 * Even if a particular skip link is requested more than once, only one copy of it will be output.
630 *
631 * @param $target the name of anchor this link should go to. For example 'maincontent'.
632 * @param $linktext The text to use for the skip link. Normally get_string('skipto', 'access', ...);
633 */
634 public function skip_link_to($target, $linktext) {
635 if ($this->topofbodydone) {
636 debugging('Page header already printed, can not add skip links any more, code needs to be fixed.');
637 return;
638 }
639 $this->skiplinks[$target] = $linktext;
640 }
641
642 /**
643 * !!!DEPRECATED!!! please use js_init_call() if possible
644 * Ensure that the specified JavaScript function is called from an inline script
645 * somewhere on this page.
646 *
647 * By default the call will be put in a script tag at the
648 * end of the page after initialising Y instance, since this gives best page-load
649 * performance and allows you to use YUI3 library.
650 *
651 * If you request that a particular function is called several times, then
652 * that is what will happen (unlike linking to a CSS or JS file, where only
653 * one link will be output).
654 *
173ba4f1 655 * The main benefit of the method is the automatic encoding of all function parameters.
0bb38e8c
PS
656 *
657 * @param string $function the name of the JavaScritp function to call. Can
658 * be a compound name like 'Y.Event.purgeElement'. Can also be
659 * used to create and object by using a 'function name' like 'new user_selector'.
660 * @param array $arguments and array of arguments to be passed to the function.
661 * When generating the function call, this will be escaped using json_encode,
662 * so passing objects and arrays should work.
663 * @param bool $ondomready
664 * @param int $delay
665 * @return void
666 */
667 public function js_function_call($function, array $arguments = null, $ondomready = false, $delay = 0) {
668 $where = $ondomready ? 'ondomready' : 'normal';
669 $this->jscalls[$where][] = array($function, $arguments, $delay);
670 }
671
2a102b90 672 /**
2b722f87 673 * Adds a call to make use of a YUI gallery module. DEPRECATED DO NOT USE!!!
2a102b90 674 *
2b722f87 675 * @deprecated DO NOT USE
2a102b90
SH
676 *
677 * @param string|array $modules One or more gallery modules to require
678 * @param string $version
679 * @param string $function
680 * @param array $arguments
681 * @param bool $ondomready
682 */
683 public function js_gallery_module($modules, $version, $function, array $arguments = null, $ondomready = false) {
684 global $CFG;
2b722f87
SH
685 debugging('This function will be removed before 2.0 is released please change it from js_gallery_module to yui_module', DEBUG_DEVELOPER);
686 $this->yui_module($modules, $function, $arguments, $version, $ondomready);
687 }
688
689 /**
690 * Creates a JavaScript function call that requires one or more modules to be loaded
691 *
692 * This function can be used to include all of the standard YUI module types within JavaScript:
693 * - YUI3 modules [node, event, io]
694 * - YUI2 modules [yui2-*]
695 * - Moodle modules [moodle-*]
696 * - Gallery modules [gallery-*]
697 *
698 * @param array|string $modules One or more modules
699 * @param string $function The function to call once modules have been loaded
700 * @param array $arguments An array of arguments to pass to the function
701 * @param string $galleryversion The gallery version to use
702 * @param bool $ondomready
703 */
704 public function yui_module($modules, $function, array $arguments = null, $galleryversion = '2010.04.08-12-35', $ondomready = false) {
6e6e3881
PS
705 global $CFG;
706
2a102b90
SH
707 if (!is_array($modules)) {
708 $modules = array($modules);
709 }
710 if (empty($CFG->useexternalyui) || true) {
711 // We need to set the M.yui.galleryversion to the correct version
2b722f87 712 $jscode = 'M.yui.galleryversion='.json_encode($galleryversion).';';
2a102b90
SH
713 } else {
714 // Set Y's config.gallery to the version
2b722f87 715 $jscode = 'Y.config.gallery='.json_encode($galleryversion).';';
2a102b90 716 }
1a7cb87e 717 $jscode .= 'Y.use('.join(',', array_map('json_encode', $modules)).',function() {'.js_writer::function_call($function, $arguments).'});';
2a102b90
SH
718 if ($ondomready) {
719 $jscode = "Y.on('domready', function() { $jscode });";
720 }
721 $this->jsinitcode[] = $jscode;
722 }
723
0bb38e8c
PS
724 /**
725 * Ensure that the specified JavaScript function is called from an inline script
726 * from page footer.
727 *
728 * @param string $function the name of the JavaScritp function to with init code,
729 * usually something like 'M.mod_mymodule.init'
730 * @param array $extraarguments and array of arguments to be passed to the function.
731 * The first argument is always the YUI3 Y instance with all required dependencies
732 * already loaded.
733 * @param bool $ondomready wait for dom ready (helps with some IE problems when modifying DOM)
734 * @param array $module JS module specification array
735 * @return void
736 */
737 public function js_init_call($function, array $extraarguments = null, $ondomready = false, array $module = null) {
738 $jscode = js_writer::function_call_with_Y($function, $extraarguments);
739 if (!$module) {
740 // detect module automatically
741 if (preg_match('/M\.([a-z0-9]+_[^\.]+)/', $function, $matches)) {
742 $module = $this->find_module($matches[1]);
743 }
744 }
745
746 $this->js_init_code($jscode, $ondomready, $module);
747 }
748
749 /**
750 * Add short static javascript code fragment to page footer.
751 * This is intended primarily for loading of js modules and initialising page layout.
752 * Ideally the JS code fragment should be stored in plugin renderer so that themes
753 * may override it.
754 * @param string $jscode
755 * @param bool $ondomready wait for dom ready (helps with some IE problems when modifying DOM)
756 * @param array $module JS module specification array
757 * @return void
758 */
759 public function js_init_code($jscode, $ondomready = false, array $module = null) {
760 $jscode = trim($jscode, " ;\n"). ';';
761
762 if ($module) {
763 $this->js_module($module);
764 $modulename = $module['name'];
765 $jscode = "Y.use('$modulename', function(Y) { $jscode });";
766 }
767
768 if ($ondomready) {
769 $jscode = "Y.on('domready', function() { $jscode });";
770 }
771
772 $this->jsinitcode[] = $jscode;
773 }
774
775 /**
776 * Make a language string available to JavaScript.
777 *
2b728cb5 778 * All the strings will be available in a M.str object in the global namespace.
0bb38e8c 779 * So, for example, after a call to $PAGE->requires->string_for_js('course', 'moodle');
2b728cb5 780 * then the JavaScript variable M.str.moodle.course will be 'Course', or the
0bb38e8c
PS
781 * equivalent in the current language.
782 *
783 * The arguments to this function are just like the arguments to get_string
7f91ba53
DM
784 * except that $component is not optional, and there are some aspects to consider
785 * when the string contains {$a} placeholder.
786 *
787 * If the string does not contain any {$a} placeholder, you can simply use
788 * M.str.component.identifier to obtain it. If you prefer, you can call
789 * M.util.get_string(identifier, component) to get the same result.
790 *
791 * If you need to use {$a} placeholders, there are two options. Either the
792 * placeholder should be substituted in PHP on server side or it should
793 * be substituted in Javascript at client side.
794 *
795 * To substitute the placeholder at server side, just provide the required
796 * value for the placeholder when you require the string. Because each string
797 * is only stored once in the JavaScript (based on $identifier and $module)
798 * you cannot get the same string with two different values of $a. If you try,
799 * an exception will be thrown. Once the placeholder is substituted, you can
800 * use M.str or M.util.get_string() as shown above:
801 *
802 * // require the string in PHP and replace the placeholder
803 * $PAGE->requires->string_for_js('fullnamedisplay', 'moodle', $USER);
804 * // use the result of the substitution in Javascript
805 * alert(M.str.moodle.fullnamedisplay);
806 *
807 * To substitute the placeholder at client side, use M.util.get_string()
808 * function. It implements the same logic as {@see get_string()}:
809 *
810 * // require the string in PHP but keep {$a} as it is
811 * $PAGE->requires->string_for_js('fullnamedisplay', 'moodle');
812 * // provide the values on the fly in Javascript
813 * user = { firstname : 'Harry', lastname : 'Potter' }
814 * alert(M.util.get_string('fullnamedisplay', 'moodle', user);
815 *
816 * If you do need the same string expanded with different $a values in PHP
817 * on server side, then the solution is to put them in your own data structure
818 * (e.g. and array) that you pass to JavaScript with {@link data_for_js()}.
0bb38e8c
PS
819 *
820 * @param string $identifier the desired string.
821 * @param string $module the language file to look in.
822 * @param mixed $a any extra data to add into the string (optional).
823 */
824 public function string_for_js($identifier, $component, $a = NULL) {
825 $string = get_string($identifier, $component, $a);
826 if (!$component) {
827 throw new coding_exception('The $module parameter is required for page_requirements_manager::string_for_js.');
828 }
df997f84 829 if (isset($this->stringsforjs[$component][$identifier]) && $this->stringsforjs[$component][$identifier] !== $string) {
0bb38e8c 830 throw new coding_exception("Attempt to re-define already required string '$identifier' " .
df997f84 831 "from lang file '$component'. Did you already ask for it with a different \$a? {$this->stringsforjs[$component][$identifier]} !== $string");
0bb38e8c
PS
832 }
833 $this->stringsforjs[$component][$identifier] = $string;
834 }
835
836 /**
837 * Make an array of language strings available for JS
838 *
839 * This function calls the above function {@link string_for_js()} for each requested
840 * string in the $identifiers array that is passed to the argument for a single module
841 * passed in $module.
842 *
843 * <code>
b9934a17 844 * $PAGE->requires->strings_for_js(array('one', 'two', 'three'), 'mymod', array('a', null, 3));
0bb38e8c 845 *
173ba4f1 846 * // The above is identitical to calling
0bb38e8c 847 *
b9934a17
DM
848 * $PAGE->requires->string_for_js('one', 'mymod', 'a');
849 * $PAGE->requires->string_for_js('two', 'mymod');
850 * $PAGE->requires->string_for_js('three', 'mymod', 3);
0bb38e8c
PS
851 * </code>
852 *
853 * @param array $identifiers An array of desired strings
854 * @param string $component The module to load for
855 * @param mixed $a This can either be a single variable that gets passed as extra
856 * information for every string or it can be an array of mixed data where the
857 * key for the data matches that of the identifier it is meant for.
858 *
859 */
860 public function strings_for_js($identifiers, $component, $a=NULL) {
861 foreach ($identifiers as $key => $identifier) {
862 if (is_array($a) && array_key_exists($key, $a)) {
863 $extra = $a[$key];
864 } else {
865 $extra = $a;
866 }
867 $this->string_for_js($identifier, $component, $extra);
868 }
869 }
870
871 /**
872 * !!!!!!DEPRECATED!!!!!! please use js_init_call() for everything now.
873 *
874 * Make some data from PHP available to JavaScript code.
875 *
876 * For example, if you call
877 * <pre>
878 * $PAGE->requires->data_for_js('mydata', array('name' => 'Moodle'));
879 * </pre>
880 * then in JavsScript mydata.name will be 'Moodle'.
881 * @param string $variable the the name of the JavaScript variable to assign the data to.
882 * Will probably work if you use a compound name like 'mybuttons.button[1]', but this
883 * should be considered an experimental feature.
884 * @param mixed $data The data to pass to JavaScript. This will be escaped using json_encode,
885 * so passing objects and arrays should work.
886 * @param bool $inhead initialise in head
887 * @return void
888 */
889 public function data_for_js($variable, $data, $inhead=false) {
890 $where = $inhead ? 'head' : 'footer';
891 $this->jsinitvariables[$where][] = array($variable, $data);
892 }
893
894 /**
895 * Creates a YUI event handler.
896 *
897 * @param mixed $selector standard YUI selector for elemnts, may be array or string, element id is in the form "#idvalue"
898 * @param string $event A valid DOM event (click, mousedown, change etc.)
899 * @param string $function The name of the function to call
900 * @param array $arguments An optional array of argument parameters to pass to the function
901 * @return void
879b4f9a
DC
902 */
903 public function event_handler($selector, $event, $function, array $arguments = null) {
0bb38e8c
PS
904 $this->eventhandlers[] = array('selector'=>$selector, 'event'=>$event, 'function'=>$function, 'arguments'=>$arguments);
905 }
906
907 /**
908 * Returns code needed for registering of event handlers.
909 * @return string JS code
910 */
911 protected function get_event_handler_code() {
912 $output = '';
913 foreach ($this->eventhandlers as $h) {
914 $output .= js_writer::event_handler($h['selector'], $h['event'], $h['function'], $h['arguments']);
915 }
916 return $output;
917 }
918
919 /**
920 * Get the inline JavaScript code that need to appear in a particular place.
921 * @return bool $ondomready
922 */
923 protected function get_javascript_code($ondomready) {
924 $where = $ondomready ? 'ondomready' : 'normal';
925 $output = '';
926 if ($this->jscalls[$where]) {
927 foreach ($this->jscalls[$where] as $data) {
e6a3ea28 928 $output .= js_writer::function_call($data[0], $data[1], $data[2]);
0bb38e8c
PS
929 }
930 if (!empty($ondomready)) {
931 $output = " Y.on('domready', function() {\n$output\n });";
932 }
933 }
934 return $output;
935 }
936
937 /**
938 * Returns js code to be executed when Y is available.
939 * @return unknown_type
940 */
941 protected function get_javascript_init_code() {
942 if (count($this->jsinitcode)) {
943 return implode("\n", $this->jsinitcode) . "\n";
944 }
945 return '';
946 }
947
948 /**
949 * Returns basic YUI3 JS loading code.
950 * YUI3 is using autoloading of both CSS and JS code.
951 *
952 * Major benefit of this compared to standard js/csss loader is much improved
953 * caching, better browser cache utilisation, much fewer http requests.
954 *
955 * @return string
956 */
957 protected function get_yui3lib_headcode() {
ef7329dc
PS
958 global $CFG;
959
960 $code = '';
961
962 if ($this->yui3loader->combine) {
963 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->comboBase
964 .$CFG->yui3version.'/build/cssreset/reset-min.css&amp;'
965 .$CFG->yui3version.'/build/cssfonts/fonts-min.css&amp;'
966 .$CFG->yui3version.'/build/cssgrids/grids-min.css&amp;'
967 .$CFG->yui3version.'/build/cssbase/base-min.css" />';
968 } else {
969 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssreset/reset-min.css" />';
970 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssfonts/fonts-min.css" />';
971 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssgrids/grids-min.css" />';
972 $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssbase/base-min.css" />';
973 }
974
975 if (debugging('', DEBUG_DEVELOPER)) {
976 $code .= '<script src="'.$this->yui3loader->base.'yui/yui-debug.js"></script>';
977 } else {
978 $code .= '<script src="'.$this->yui3loader->base.'yui/yui-min.js"></script>';
979 }
980
0bb38e8c
PS
981 return $code;
982 }
983
984 /**
985 * Returns basic YUI2 JS loading code.
986 * It can be called manually at any time.
987 * If called manually the result needs to be output using echo().
988 *
989 * Major benefit of this compared to standard js loader is much improved
990 * caching, better browser cache utilisation, much fewer http requests.
991 *
992 * All YUI2 CSS is loaded automatically.
993 *
994 * @return string JS embedding code
995 */
996 public function get_yui2lib_code() {
997 global $CFG;
998
999 if ($this->headdone) {
1000 $code = $this->yui2loader->script();
1001 } else {
1002 $code = $this->yui2loader->script();
1003 if ($this->yui2loader->combine) {
1004 $skinurl = $this->yui2loader->comboBase . $CFG->yui2version . '/build/assets/skins/sam/skin.css';
1005 } else {
1006 $skinurl = $this->yui2loader->base . 'assets/skins/sam/skin.css';
1007 }
1008 // please note this is a temporary hack until we fully migrate to later YUI3 that has all the widgets
1009 $attributes = array('rel'=>'stylesheet', 'type'=>'text/css', 'href'=>$skinurl);
1010 $code .= "\n" . html_writer::empty_tag('link', $attributes) . "\n";
1011 }
1012 $code = str_replace('&amp;', '&', $code);
1013 $code = str_replace('&', '&amp;', $code);
1014 return $code;
1015 }
1016
1017 /**
1018 * Returns html tags needed for inclusion of theme CSS
1019 * @return string
1020 */
1021 protected function get_css_code() {
1022 // First of all the theme CSS, then any custom CSS
1023 // Please note custom CSS is strongly discouraged,
1024 // because it can not be overridden by themes!
1025 // It is suitable only for things like mod/data which accepts CSS from teachers.
692e9fa5 1026 $attributes = array('rel'=>'stylesheet', 'type'=>'text/css');
0bb38e8c 1027
692e9fa5
SH
1028 // This line of code may look funny but it is currently required in order
1029 // to avoid MASSIVE display issues in Internet Explorer.
1030 // As of IE8 + YUI3.1.1 the reference stylesheet (firstthemesheet) gets
1031 // ignored whenever another resource is added until such time as a redraw
1032 // is forced, usually by moving the mouse over the affected element.
f4136193 1033 $code = html_writer::tag('script', '/** Required in order to fix style inclusion problems in IE with YUI **/', array('id'=>'firstthemesheet', 'type'=>'text/css'));
0bb38e8c
PS
1034
1035 $urls = $this->cssthemeurls + $this->cssurls;
0bb38e8c
PS
1036 foreach ($urls as $url) {
1037 $attributes['href'] = $url;
1038 $code .= html_writer::empty_tag('link', $attributes) . "\n";
1039 // this id is needed in first sheet only so that theme may override YUI sheets laoded on the fly
1040 unset($attributes['id']);
1041 }
1042
1043 return $code;
1044 }
1045
1046 /**
1047 * Adds extra modules specified after printing of page header
1048 */
1049 protected function get_extra_modules_code() {
1050 if (empty($this->extramodules)) {
1051 return '';
1052 }
1053 return html_writer::script(js_writer::function_call('M.yui.add_module', array($this->extramodules)));
1054 }
1055
1056 /**
1057 * Generate any HTML that needs to go inside the <head> tag.
1058 *
1059 * Normally, this method is called automatically by the code that prints the
1060 * <head> tag. You should not normally need to call it in your own code.
1061 *
1062 * @return string the HTML code to to inside the <head> tag.
1063 */
1064 public function get_head_code(moodle_page $page, core_renderer $renderer) {
1065 global $CFG;
1066
1067 // note: the $page and $output are not stored here because it would
1068 // create circular references in memory which prevents garbage collection
1069 $this->init_requirements_data($page, $renderer);
1070
1071 // yui3 JS and CSS is always loaded first - it is cached in browser
1072 $output = $this->get_yui3lib_headcode();
1073
1074 // BC: load basic YUI2 for now, all yui2 things should be loaded via Y.use('yui2-oldmodulename')
1075 $output .= $this->get_yui2lib_code();
1076
1077 // now theme CSS + custom CSS in this specific order
1078 $output .= $this->get_css_code();
1079
1080 // set up global YUI3 loader object - this should contain all code needed by plugins
1081 // note: in JavaScript just use "YUI(M.yui.loader).use('overlay', function(Y) { .... });"
1082 // this needs to be done before including any other script
2b722f87
SH
1083 $js = "var M = {}; M.yui = {}; var moodleConfigFn = function(me) {var p = me.path, b = me.name.replace(/^moodle-/,'').split('-', 3), n = b.pop();if (/(skin|core)/.test(n)) {n = b.pop();me.type = 'css';};me.path = b.join('-')+'/'+n+'/'+n+'.'+me.type;}; var galleryConfigFn = function(me) {var p = me.path,v=M.yui.galleryversion,f;if(/-(skin|core)/.test(me.name)) {me.type = 'css';p = p.replace(/-(skin|core)/, '').replace(/\.js/, '.css').split('/'), f = p.pop().replace(/(\-(min|debug))/, '');if (/-skin/.test(me.name)) {p.splice(p.length,0,v,'assets','skins','sam', f);} else {p.splice(p.length,0,v,'assets', f);};} else {p = p.split('/'), f = p.pop();p.splice(p.length,0,v, f);};me.path = p.join('/');};\n";
1084 $js .= js_writer::set_variable('M.yui.loader', $this->M_yui_loader, false) . "\n";
0bb38e8c 1085 $js .= js_writer::set_variable('M.cfg', $this->M_cfg, false);
2b722f87
SH
1086 $js = str_replace('"@GALLERYCONFIGFN@"', 'galleryConfigFn', $js);
1087 $js = str_replace('"@MOODLECONFIGFN@"', 'moodleConfigFn', $js);
1088
0bb38e8c
PS
1089 $output .= html_writer::script($js);
1090
1091 // link our main JS file, all core stuff should be there
0139ec3f 1092 $output .= html_writer::script('', $this->js_fix_url('/lib/javascript-static.js'));
0bb38e8c
PS
1093
1094 // add variables
1095 if ($this->jsinitvariables['head']) {
1096 $js = '';
1097 foreach ($this->jsinitvariables['head'] as $data) {
1098 list($var, $value) = $data;
1099 $js .= js_writer::set_variable($var, $value, true);
1100 }
1101 $output .= html_writer::script($js);
1102 }
1103
1104 // all the other linked things from HEAD - there should be as few as possible
1105 if ($this->jsincludes['head']) {
1106 foreach ($this->jsincludes['head'] as $url) {
1107 $output .= html_writer::script('', $url);
1108 }
1109 }
1110
1111 // mark head sending done, it is not possible to anything there
1112 $this->headdone = true;
1113
1114 return $output;
1115 }
1116
1117 /**
1118 * Generate any HTML that needs to go at the start of the <body> tag.
1119 *
1120 * Normally, this method is called automatically by the code that prints the
1121 * <head> tag. You should not normally need to call it in your own code.
1122 *
1123 * @return string the HTML code to go at the start of the <body> tag.
1124 */
1125 public function get_top_of_body_code() {
1126 // first the skip links
1127 $links = '';
1128 $attributes = array('class'=>'skip');
1129 foreach ($this->skiplinks as $url => $text) {
1130 $attributes['href'] = '#' . $url;
26acc814 1131 $links .= html_writer::tag('a', $text, $attributes);
0bb38e8c 1132 }
26acc814 1133 $output = html_writer::tag('div', $links, array('class'=>'skiplinks')) . "\n";
0bb38e8c
PS
1134
1135 // then the clever trick for hiding of things not needed when JS works
1136 $output .= html_writer::script("document.body.className += ' jsenabled';") . "\n";
1137 $this->topofbodydone = true;
1138 return $output;
1139 }
1140
1141 /**
1142 * Generate any HTML that needs to go at the end of the page.
1143 *
1144 * Normally, this method is called automatically by the code that prints the
1145 * page footer. You should not normally need to call it in your own code.
1146 *
1147 * @return string the HTML code to to at the end of the page.
1148 */
1149 public function get_end_code() {
2cf81209 1150 global $CFG;
0bb38e8c
PS
1151 // add other requested modules
1152 $output = $this->get_extra_modules_code();
1153
1154 // add missing YUI2 YUI - to be removed once we convert everything to YUI3!
1155 $output .= $this->get_yui2lib_code();
2cf81209 1156
0bb38e8c
PS
1157 // all the other linked scripts - there should be as few as possible
1158 if ($this->jsincludes['footer']) {
1159 foreach ($this->jsincludes['footer'] as $url) {
1160 $output .= html_writer::script('', $url);
1161 }
1162 }
1163
1164 // add all needed strings
1165 if (!empty($this->stringsforjs)) {
2b728cb5 1166 $output .= html_writer::script(js_writer::set_variable('M.str', $this->stringsforjs));
0bb38e8c
PS
1167 }
1168
1169 // add variables
1170 if ($this->jsinitvariables['footer']) {
1171 $js = '';
1172 foreach ($this->jsinitvariables['footer'] as $data) {
1173 list($var, $value) = $data;
1174 $js .= js_writer::set_variable($var, $value, true);
1175 }
1176 $output .= html_writer::script($js);
1177 }
1178
1179 $inyuijs = $this->get_javascript_code(false);
1180 $ondomreadyjs = $this->get_javascript_code(true);
1181 $jsinit = $this->get_javascript_init_code();
1182 $handlersjs = $this->get_event_handler_code();
1183
0139ec3f 1184 // there is no global Y, make sure it is available in your scope
0bb38e8c
PS
1185 $js = "YUI(M.yui.loader).use('node', function(Y) {\n{$inyuijs}{$ondomreadyjs}{$jsinit}{$handlersjs}\n});";
1186
1187 $output .= html_writer::script($js);
1188
1189 return $output;
1190 }
1191
1192 /**
1193 * @return boolean Have we already output the code in the <head> tag?
1194 */
1195 public function is_head_done() {
1196 return $this->headdone;
1197 }
1198
1199 /**
1200 * @return boolean Have we already output the code at the start of the <body> tag?
1201 */
1202 public function is_top_of_body_done() {
1203 return $this->topofbodydone;
1204 }
1205}
0139ec3f
PS
1206
1207/**
1208 * Invalidate all server and client side JS caches.
1209 * @return void
1210 */
1211function js_reset_all_caches() {
1212 global $CFG;
1213 require_once("$CFG->libdir/filelib.php");
1214
1215 set_config('jsrev', empty($CFG->jsrev) ? 1 : $CFG->jsrev+1);
09c48cea 1216 fulldelete("$CFG->dataroot/cache/js");
0139ec3f
PS
1217}
1218