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