5cf751db79b53a52f1416664a49c7c829b899d4a
[moodle.git] / lib / ajax / ajaxlib.php
1 <?php
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/>.
19 /**
20  * Library functions to facilitate the use of JavaScript in Moodle.
21  *
22  * @package   moodlecore
23  * @copyright 2009 Tim Hunt
24  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
28 /**
29  * Initialise a {@link page_requirements_manager} with the bits of JavaScript that every
30  * Moodle page should have.
31  *
32  * @param page_requirements_manager $requires The page_requirements_manager to initialise.
33  */
34 function setup_core_javascript(page_requirements_manager $requires) {
35     global $CFG, $OUTPUT, $PAGE;
37     // JavaScript should always work with $CFG->httpswwwroot rather than $CFG->wwwroot.
38     // Otherwise, in some situations, users will get warnings about insecure content
39     // on sercure pages from their web browser.
41     $config = array(
42         'wwwroot' => $CFG->httpswwwroot, // Yes, really. See above.
43         'sesskey' => sesskey(),
44         'loadingicon' => $OUTPUT->pix_url('i/loading_small', 'moodle')->out_raw(),
45         'themerev' => theme_get_revision(),
46         'theme' => $PAGE->theme->name,
47     );
48     if (debugging('', DEBUG_DEVELOPER)) {
49         $config['developerdebug'] = true;
50     }
51     $requires->data_for_js('moodle_cfg', $config)->in_head();
53     if (debugging('', DEBUG_DEVELOPER)) {
54         $requires->yui2_lib('logger');
55     }
57     // YUI3 init code
58     $requires->yui3_lib(array('cssreset', 'cssbase', 'cssfonts', 'cssgrids')); // full CSS reset
59     $requires->yui3_lib('yui'); // allows autoloading of everything else
62     $requires->skip_link_to('maincontent', get_string('tocontent', 'access'));
64     // Note that, as a short-cut, the code
65     // $js = "document.body.className += ' jsenabled';\n";
66     // is hard-coded in {@link page_requirements_manager::get_top_of_body_code)
67     $requires->yui2_lib('container');
68     $requires->yui2_lib('connection');
69     $requires->string_for_js('confirmation', 'admin');
70     $requires->string_for_js('cancel', 'moodle');
71     $requires->string_for_js('yes', 'moodle');
72     $requires->js_function_call('init_help_icons');
73 }
76 /**
77  * This class tracks all the things that are needed by the current page.
78  *
79  * Normally, the only instance of this  class you will need to work with is the
80  * one accessible via $PAGE->requires.
81  *
82  * Typical useage would be
83  * <pre>
84  *     $PAGE->requires->css('mod/mymod/userstyles.php?id='.$id); // not overriddable via themes!
85  *     $PAGE->requires->js('mod/mymod/script.js');
86  *     $PAGE->requires->js('mod/mymod/small_but_urgent.js')->in_head();
87  *     $PAGE->requires->js_function_call('init_mymod', array($data))->on_dom_ready();
88  * </pre>
89  *
90  * There are some natural restrictions on some methods. For example, {@link css()}
91  * can only be called before the <head> tag is output. See the comments on the
92  * individual methods for details.
93  *
94  * @copyright 2009 Tim Hunt
95  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
96  * @since Moodle 2.0
97  */
98 class page_requirements_manager {
99     const WHEN_IN_HEAD = 0;
100     const WHEN_TOP_OF_BODY = 10;
101     const WHEN_AT_END = 20;
102     const WHEN_ON_DOM_READY = 30;
104     protected $linkedrequirements = array();
105     protected $stringsforjs = array();
106     protected $requiredjscode = array();
108     protected $variablesinitialised = array('mstr' => 1); // 'mstr' is special. See string_for_js.
110     protected $headdone = false;
111     protected $topofbodydone = false;
113     /** YUI PHPLoader instance responsible for YUI3 laoding in page head only */
114     protected $yui3loader;
115     /** YUI PHPLoader instance responsible for YUI2 laoding */
116     protected $yui2loader;
118     /**
119      * Page requirements constructor.
120      */
121     public function __construct() {
122         global $CFG;
123         require_once("$CFG->libdir/yui/phploader/phploader/loader.php");
125         $this->yui3loader = new YAHOO_util_Loader($CFG->yui3version);
126         $this->yui2loader = new YAHOO_util_Loader($CFG->yui2version);
128         // set up some loader options
129         if (debugging('', DEBUG_DEVELOPER)) {
130             $this->yui3loader->filter = YUI_DEBUG; // alternatively we could use just YUI_RAW here
131             $this->yui2loader->filter = YUI_DEBUG; // alternatively we could use just YUI_RAW here
132         } else {
133             $this->yui3loader->filter = null;
134             $this->yui2loader->filter = null;
135         }
136         if (!empty($CFG->useexternalyui)) {
137             $this->yui3loader->base = 'http://yui.yahooapis.com/' . $CFG->yui3version . '/build/';
138             $this->yui2loader->base = 'http://yui.yahooapis.com/' . $CFG->yui2version . '/build/';
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         }
144         // This file helps to minimise number of http requests and implements proper caching
145         $this->yui3loader->comboBase = $CFG->httpswwwroot . '/theme/yui_combo.php?';
146         $this->yui2loader->comboBase = $CFG->httpswwwroot . '/theme/yui_combo.php?';
148         // enable combo loader? this significantly helps with caching and performance
149         $this->yui3loader->combine = !empty($CFG->yuicomboloading);
150         $this->yui2loader->combine = !empty($CFG->yuicomboloading);
151     }
153     /**
154      * Ensure that the specified JavaScript file is linked to from this page.
155      *
156      * By default the link is put at the end of the page, since this gives best page-load performance.
157      *
158      * Even if a particular script is requested more than once, it will only be linked
159      * to once.
160      *
161      * @param $jsfile The path to the .js file, relative to $CFG->dirroot / $CFG->wwwroot.
162      *      No leading slash. For example 'mod/mymod/customscripts.js';
163      * @param boolean $fullurl This parameter is intended for internal use only.
164      *      However, in exceptional circumstances you may wish to use it to link
165      *      to JavaScript on another server. For example, lib/recaptchalib.php has to
166      *      do this. This really should only be done in exceptional circumstances. This
167      *      may change in the future without warning.
168      *      (If true, $jsfile is treaded as a full URL, not relative $CFG->wwwroot.)
169      * @return required_js The required_js object. This allows you to control when the
170      *      link to the script is output by calling methods like {@link required_js::asap()} or
171      *      {@link required_js::in_head()}.
172      */
173     public function js($jsfile, $fullurl = false) {
174         global $CFG;
175         if (!$fullurl) {
176             // strtok is used to trim off any GET string arguments before looking for the file
177             if (!file_exists($CFG->dirroot . '/' . strtok($jsfile, '?'))) {
178                 throw new coding_exception('Attept to require a JavaScript file that does not exist.', $jsfile);
179             }
180             $url = $CFG->httpswwwroot . '/' . $jsfile;
181         } else {
182             $url = $jsfile;
183         }
184         if (!isset($this->linkedrequirements[$url])) {
185             $this->linkedrequirements[$url] = new required_js($this, $url);
186         }
187         return $this->linkedrequirements[$url];
188     }
190     /**
191      * Ensure that the specified YUI2 library file, and all its required dependancies,
192      * are linked to from this page.
193      *
194      * By default the link is put at the end of the page, since this gives best page-load
195      * performance. Optional dependencies are not loaded automatically - if you want
196      * them you will need to load them first with other calls to this method.
197      *
198      * Even if a particular library is requested more than once (perhaps as a dependancy
199      * of other libraries) it will only be linked to once.
200      *
201      * The library is leaded as soon as possible, if $OUTPUT->header() not used yet it
202      * is put into the page header, otherwise it is loaded in the page footer. 
203      *
204      * @param string|array $libname the name of the YUI2 library you require. For example 'autocomplete'.
205      * @return void
206      */
207     public function yui2_lib($libname) {
208         $libnames = (array)$libname;
209         foreach ($libnames as $lib) {
210             $this->yui2loader->load($lib);
211         }
212     }
214     /**
215      * Ensure that the specified YUI3 library file, and all its required dependancies,
216      * are laoded automatically on this page.
217      *
218      * @param string|array $libname the name of the YUI3 library you require. For example 'overlay'.
219      * @return void
220      */
221     public function yui3_lib($libname) {
222         if ($this->headdone) {
223             throw new coding_exception('YUI3 libraries can be preloaded by PHP only from HEAD, please use YUI autoloading instead: ', $stylesheet);
224         }
225         $libnames = (array)$libname;
226         foreach ($libnames as $lib) {
227             $this->yui3loader->load($lib);
228         }
229     }
231     /**
232      * Ensure that the specified CSS file is linked to from this page.
233      *
234      * Because stylesheet links must go in the <head> part of the HTML, you must call
235      * this function before {@link get_head_code()} is called. That normally means before
236      * the call to print_header. If you call it when it is too late, an exception
237      * will be thrown.
238      *
239      * Even if a particular style sheet is requested more than once, it will only
240      * be linked to once.
241      *
242      * @param string $stylesheet The path to the .css file, relative to
243      *      $CFG->dirroot / $CFG->wwwroot. No leading slash. For example
244      *      'mod/mymod/styles.css';
245      * @param boolean $fullurl This parameter is intended for internal use only.
246      *      (If true, $stylesheet is treaded as a full URL, not relative $CFG->wwwroot.)
247      */
248     public function css($stylesheet, $fullurl = false) {
249         global $CFG;
250         if (!$fullurl) {
251             if (!file_exists($CFG->dirroot . '/' . strtok($stylesheet, '?'))) {
252                 throw new coding_exception('Attempt to require a CSS file that does not exist.', $stylesheet);
253             }
254             $url = $CFG->httpswwwroot . '/' . $stylesheet;
255         } else {
256             $url = $stylesheet;
257         }
259         if (isset($this->linkedrequirements[$url])) {
260             // already required, ignore it
261             return;
262         } else {
263             if ($this->headdone) {
264                 throw new coding_exception('Cannot require a CSS file after &lt;head> has been printed.', $stylesheet);
265             }
266             $this->linkedrequirements[$url] = new required_css($this, $url);
267         }
268     }
270     /**
271      * Ensure that a skip link to a given target is printed at the top of the <body>.
272      *
273      * You must call this function before {@link get_top_of_body_code()}, (if not, an exception
274      * will be thrown). That normally means you must call this before the call to print_header.
275      *
276      * If you ask for a particular skip link to be printed, it is then your responsibility
277      * to ensure that the appropraite <a name="..."> tag is printed in the body of the
278      * page, so that the skip link goes somewhere.
279      *
280      * Even if a particular skip link is requested more than once, only one copy of it will be output.
281      *
282      * @param $target the name of anchor this link should go to. For example 'maincontent'.
283      * @param $linktext The text to use for the skip link. Normally get_string('skipto', 'access', ...);
284      */
285     public function skip_link_to($target, $linktext) {
286         if (!isset($this->linkedrequirements[$target])) {
287             $this->linkedrequirements[$target] = new required_skip_link($this, $target, $linktext);
288         }
289     }
291     /**
292      * Ensure that the specified JavaScript function is called from an inline script
293      * somewhere on this page.
294      *
295      * By default the call will be put in a script tag at the
296      * end of the page, since this gives best page-load performance.
297      *
298      * If you request that a particular function is called several times, then
299      * that is what will happen (unlike linking to a CSS or JS file, where only
300      * one link will be output).
301      *
302      * @param string $function the name of the JavaScritp function to call. Can
303      *      be a compound name like 'YAHOO.util.Event.addListener'. Can also be
304      *      used to create and object by using a 'function name' like 'new user_selector'.
305      * @param array $arguments and array of arguments to be passed to the function.
306      *      When generating the function call, this will be escaped using json_encode,
307      *      so passing objects and arrays should work.
308      * @return required_js_function_call The required_js_function_call object.
309      *      This allows you to control when the link to the script is output by
310      *      calling methods like {@link required_js_function_call::in_head()},
311      *      {@link required_js_function_call::at_top_of_body()},
312      *      {@link required_js_function_call::on_dom_ready()} or
313      *      {@link required_js_function_call::after_delay()} methods.
314      */
315     public function js_function_call($function, $arguments = array()) {
316         $requirement = new required_js_function_call($this, $function, $arguments);
317         $this->requiredjscode[] = $requirement;
318         return $requirement;
319     }
321     /**
322      * Make a language string available to JavaScript.
323      *
324      * All the strings will be available in a mstr object in the global namespace.
325      * So, for example, after a call to $PAGE->requires->string_for_js('course', 'moodle');
326      * then the JavaScript variable mstr.moodle.course will be 'Course', or the
327      * equivalent in the current language.
328      *
329      * The arguments to this function are just like the arguments to get_string
330      * except that $module is not optional, and there are limitations on how you
331      * use $a. Because each string is only stored once in the JavaScript (based
332      * on $identifier and $module) you cannot get the same string with two different
333      * values of $a. If you try, an exception will be thrown.
334      *
335      * If you do need the same string expanded with different $a values, then
336      * the solution is to put them in your own data structure (e.g. and array)
337      * that you pass to JavaScript with {@link data_for_js()}.
338      *
339      * @param string $identifier the desired string.
340      * @param string $module the language file to look in.
341      * @param mixed $a any extra data to add into the string (optional).
342      */
343     public function string_for_js($identifier, $module, $a = NULL) {
344         $string = get_string($identifier, $module, $a);
345         if (!$module) {
346             throw new coding_exception('The $module parameter is required for page_requirements_manager::string_for_js.');
347         }
348         if (isset($this->stringsforjs[$module][$identifier]) && $this->stringsforjs[$module][$identifier] != $string) {
349             throw new coding_exception("Attempt to re-define already required string '$identifier' " .
350                     "from lang file '$module'. Did you already ask for it with a different \$a?");
351         }
352         $this->stringsforjs[$module][$identifier] = $string;
353     }
355     /**
356      * Make an array of language strings available for JS
357      *
358      * This function calls the above function {@link string_for_js()} for each requested
359      * string in the $identifiers array that is passed to the argument for a single module
360      * passed in $module.
361      *
362      * <code>
363      * $PAGE->strings_for_js(Array('one', 'two', 'three'), 'mymod', Array('a', null, 3));
364      *
365      * // The above is identifical to calling
366      *
367      * $PAGE->string_for_js('one', 'mymod', 'a');
368      * $PAGE->string_for_js('two', 'mymod');
369      * $PAGE->string_for_js('three', 'mymod', 3);
370      * </code>
371      *
372      * @param array $identifiers An array of desired strings
373      * @param string $module The module to load for
374      * @param mixed $a This can either be a single variable that gets passed as extra
375      *         information for every string or it can be an array of mixed data where the
376      *         key for the data matches that of the identifier it is meant for.
377      *
378      */
379     public function strings_for_js($identifiers, $module, $a=NULL) {
380         foreach ($identifiers as $key => $identifier) {
381             if (is_array($a) && array_key_exists($key, $a)) {
382                 $extra = $a[$key];
383             } else {
384                 $extra = $a;
385             }
386             $this->string_for_js($identifier, $module, $extra);
387         }
388     }
390     /**
391      * Make some data from PHP available to JavaScript code.
392      *
393      * For example, if you call
394      * <pre>
395      *      $PAGE->requires->data_for_js('mydata', array('name' => 'Moodle'));
396      * </pre>
397      * then in JavsScript mydata.name will be 'Moodle'.
398      *
399      * You cannot call this function more than once with the same variable name
400      * (if you try, it will throw an exception). Your code should prepare all the
401      * date you want, and then pass it to this method. There is no way to change
402      * the value associated with a particular variable later.
403      *
404      * @param string $variable the the name of the JavaScript variable to assign the data to.
405      *      Will probably work if you use a compound name like 'mybuttons.button[1]', but this
406      *      should be considered an experimental feature.
407      * @param mixed $data The data to pass to JavaScript. This will be escaped using json_encode,
408      *      so passing objects and arrays should work.
409      * @return required_data_for_js The required_data_for_js object.
410      *      This allows you to control when the link to the script is output by
411      *      calling methods like {@link required_data_for_js::asap()},
412      *      {@link required_data_for_js::in_head()} or
413      *      {@link required_data_for_js::at_top_of_body()} methods.
414      */
415     public function data_for_js($variable, $data) {
416         if (isset($this->variablesinitialised[$variable])) {
417             throw new coding_exception("A variable called '" . $variable .
418                     "' has already been passed ot JavaScript. You cannot overwrite it.");
419         }
420         $requirement = new required_data_for_js($this, $variable, $data);
421         $this->requiredjscode[] = $requirement;
422         $this->variablesinitialised[$variable] = 1;
423         return $requirement;
424     }
426     /**
427      * Creates a YUI event handler.
428      *
429      * @param string $id The id of the DOM element that will be listening for the event
430      * @param string $event A valid DOM event (click, mousedown, change etc.)
431      * @param string $function The name of the function to call
432      * @param array  $arguments An optional array of argument parameters to pass to the function
433      * @return required_event_handler The event_handler object
434      */
435     public function event_handler($id, $event, $function, $arguments=array()) {
436         $requirement = new required_event_handler($this, $id, $event, $function, $arguments);
437         $this->requiredjscode[] = $requirement;
438         $this->yui2_lib('event');
439         return $requirement;
440     }
442     /**
443      * Get the code for the linked resources that need to appear in a particular place.
444      * @param $when one of the WHEN_... constants.
445      * @return string the HTML that should be output in that place.
446      */
447     protected function get_linked_resources_code($when) {
448         $output = '';
449         foreach ($this->linkedrequirements as $requirement) {
450             if (!$requirement->is_done() && $requirement->get_when() == $when) {
451                 $output .= $requirement->get_html();
452                 $requirement->mark_done();
453             }
454         }
455         return $output;
456     }
458     /**
459      * Get the inline JavaScript code that need to appear in a particular place.
460      * @param $when one of the WHEN_... constants.
461      * @return string the javascript that should be output in that place.
462      */
463     protected function get_javascript_code($when, $indent = '') {
464         $output = '';
465         foreach ($this->requiredjscode as $requirement) {
466             if (!$requirement->is_done() && $requirement->get_when() == $when) {
467                 $output .= $indent . $requirement->get_js_code();
468                 $requirement->mark_done();
469             }
470         }
471         return $output;
472     }
474     /**
475      * Returns basic YUI3 JS loading code.
476      * YUI3 is using autoloading of both CSS and JS code.
477      *
478      * @return string
479      */
480     protected function get_yui3lib_headcode() {
481         $code = $this->yui3loader->css() . $this->yui3loader->script();
482         // unfortunately yui loader does not produce xhtml strict code, so let's fix it for now
483         $code = str_replace('&amp;', '&', $code);
484         $code = str_replace('&', '&amp;', $code);
485         return $code;
486     }
488     /**
489      * Returns basic YUI2 JS loading code.
490      * It can be called manually at any time.
491      *
492      * @return string JS embedding code
493      */
494     public function get_yui2lib_code() {
495         // All YUI2 CSS is loaded automatically
496         if ($this->headdone) {
497             $code = $this->yui2loader->script_embed();
498         } else {
499             $code = $this->yui2loader->script();
500         }
501         $code = str_replace('&amp;', '&', $code);
502         $code = str_replace('&', '&amp;', $code);
503         return $code;
504     }
506     /**
507      * Generate any HTML that needs to go inside the <head> tag.
508      *
509      * Normally, this method is called automatically by the code that prints the
510      * <head> tag. You should not normally need to call it in your own code.
511      *
512      * @return string the HTML code to to inside the <head> tag.
513      */
514     public function get_head_code() {
515         setup_core_javascript($this);
516         $output = $this->get_yui3lib_headcode();
517         $output .= $this->get_yui2lib_code();
518         $output .= $this->get_linked_resources_code(self::WHEN_IN_HEAD);
519         $js = $this->get_javascript_code(self::WHEN_IN_HEAD);
520         $output .= ajax_generate_script_tag($js);
521         $this->headdone = true;
522         return $output;
523     }
525     /**
526      * Generate any HTML that needs to go at the start of the <body> tag.
527      *
528      * Normally, this method is called automatically by the code that prints the
529      * <head> tag. You should not normally need to call it in your own code.
530      *
531      * @return string the HTML code to go at the start of the <body> tag.
532      */
533     public function get_top_of_body_code() {
534         $output = '<div class="skiplinks">' . $this->get_linked_resources_code(self::WHEN_TOP_OF_BODY) . '</div>';
535         $js = "document.body.className += ' jsenabled';\n";
536         $js .= $this->get_javascript_code(self::WHEN_TOP_OF_BODY);
537         $output .= ajax_generate_script_tag($js);
538         $this->topofbodydone = true;
539         return $output;
540     }
542     /**
543      * Generate any HTML that needs to go at the end of the page.
544      *
545      * Normally, this method is called automatically by the code that prints the
546      * page footer. You should not normally need to call it in your own code.
547      *
548      * @return string the HTML code to to at the end of the page.
549      */
550     public function get_end_code() {
551         $output = $this->get_yui2lib_code();
552         $output .= $this->get_linked_resources_code(self::WHEN_AT_END);
554         if (!empty($this->stringsforjs)) {
555             array_unshift($this->requiredjscode, new required_data_for_js($this, 'mstr', $this->stringsforjs));
556         }
558         $js = $this->get_javascript_code(self::WHEN_AT_END);
560         $ondomreadyjs = $this->get_javascript_code(self::WHEN_ON_DOM_READY, '    ');
561         if ($ondomreadyjs) {
562             $js .= "YAHOO.util.Event.onDOMReady(function() {\n" . $ondomreadyjs . "});\n";
563         }
565         $output .= ajax_generate_script_tag($js);
567         return $output;
568     }
570     /**
571      * @return boolean Have we already output the code in the <head> tag?
572      */
573     public function is_head_done() {
574         return $this->headdone;
575     }
577     /**
578      * @return boolean Have we already output the code at the start of the <body> tag?
579      */
580     public function is_top_of_body_done() {
581         return $this->topofbodydone;
582     }
586 /**
587  * This is the base class for all sorts of requirements. just to factor out some
588  * common code.
589  *
590  * @copyright 2009 Tim Hunt
591  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
592  * @since Moodle 2.0
593  */
594 abstract class requirement_base {
595     protected $manager;
596     protected $when;
597     protected $done = false;
599     /**
600      * Constructor. Normally the class and its subclasses should not be created
601      * directly. Client code should create them via a page_requirements_manager
602      * method like ->js(...).
603      *
604      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
605      */
606     protected function __construct(page_requirements_manager $manager) {
607         $this->manager = $manager;
608     }
610     /**
611      * Mark that this requirement has been satisfied (that is, that the HTML
612      * returned by {@link get_html()} has been output.
613      * @return boolean has this requirement been satisfied yet? That is, has
614      *      that the HTML returned by {@link get_html()} has been output already.
615      */
616     public function is_done() {
617         return $this->done;
618     }
620     /**
621      * Mark that this requirement has been satisfied (that is, that the HTML
622      * returned by {@link get_html()} has been output.
623      */
624     public function mark_done() {
625         $this->done = true;
626     }
628     /**
629      * Where on the page the HTML this requirement is meant to go.
630      * @return integer One of the {@link page_requirements_manager}::WHEN_... constants.
631      */
632     public function get_when() {
633         return $this->when;
634     }
637 /**
638  * This class represents something that must be output somewhere in the HTML.
639  *
640  * Examples include links to JavaScript or CSS files. However, it should not
641  * necessarily be output immediately, we may have to wait for an appropriate time.
642  *
643  * @copyright 2009 Tim Hunt
644  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
645  * @since Moodle 2.0
646  */
647 abstract class linked_requirement extends requirement_base {
648     protected $url;
650     /**
651      * Constructor. Normally the class and its subclasses should not be created
652      * directly. Client code should create them via a page_requirements_manager
653      * method like ->js(...).
654      *
655      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
656      * @param string $url The URL of the thing we are linking to.
657      */
658     protected function __construct(page_requirements_manager $manager, $url) {
659         parent::__construct($manager);
660         $this->url = $url;
661     }
663     /**
664      * @return string the HTML needed to satisfy this requirement.
665      */
666     abstract public function get_html();
670 /**
671  * A subclass of {@link linked_requirement} to represent a requried JavaScript file.
672  *
673  * You should not create instances of this class directly. Instead you should
674  * work with a {@link page_requirements_manager} - and probably the only
675  * page_requirements_manager you will ever need is the one at $PAGE->requires.
676  *
677  * The methods {@link asap()}, {@link in_head()} and {@link at_top_of_body()}
678  * are indented to be used as a fluid API, so you can say things like
679  *     $PAGE->requires->js('mod/mymod/script.js')->in_head();
680  *
681  * However, by default JavaScript files are included at the end of the HTML.
682  * This is recommended practice because it means that the web browser will only
683  * start loading the javascript files after the rest of the page is loaded, and
684  * that gives the best performance for users.
685  *
686  * @copyright 2009 Tim Hunt
687  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
688  * @since Moodle 2.0
689  */
690 class required_js extends linked_requirement {
691     /**
692      * Constructor. Normally instances of this class should not be created
693      * directly. Client code should create them via the page_requirements_manager
694      * method {@link page_requirements_manager::js()}.
695      *
696      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
697      * @param string $url The URL of the JavaScript file we are linking to.
698      */
699     public function __construct(page_requirements_manager $manager, $url) {
700         parent::__construct($manager, $url);
701         $this->when = page_requirements_manager::WHEN_AT_END;
702     }
704     public function get_html() {
705         return ajax_get_link_to_script($this->url);
706     }
708     /**
709      * Indicate that the link to this JavaScript file should be output as soon as
710      * possible. That is, if this requirement has already been output, this method
711      * does nothing. Otherwise, if the <head> tag has not yet been printed, the link
712      * to this script will be put in <head>. Otherwise, this method returns a
713      * fragment of HTML that the caller is responsible for outputting as soon as
714      * possible. In fact, it is recommended that you only call this function from
715      * an echo statement, like:
716      * <pre>
717      *     echo $PAGE->requires->js(...)->asap();
718      * </pre>
719      *
720      * @return string The HTML required to include this JavaScript file. The caller
721      * is responsible for outputting this HTML promptly.
722      */
723     public function asap() {
724         if (!$this->manager->is_head_done()) {
725             $this->in_head();
726             return '';
727         } else {
728             return $this->now();
729         }
730     }
732     /**
733      * Return the required JavaScript immediately, so it can be included in some
734      * HTML that is being built.
735      *
736      * This is not really recommeneded. But is necessary in some legacy code that
737      * includes a .js files that does document.write.
738      *
739      * @return string The HTML for the script tag. The caller
740      * is responsible for making sure it is output.
741      */
742     public function now() {
743         if ($this->is_done()) {
744             return '';
745         }
746         $output = $this->get_html();
747         $this->mark_done();
748         return $output;
749     }
751     /**
752      * Indicate that the link to this JavaScript file should be output in the
753      * <head> section of the HTML. If it too late for this request to be
754      * satisfied, an exception is thrown.
755      */
756     public function in_head() {
757         if ($this->is_done() || $this->when <= page_requirements_manager::WHEN_IN_HEAD) {
758             return;
759         }
760         if ($this->manager->is_head_done()) {
761             throw new coding_exception('Too late to ask for a JavaScript file to be linked to from &lt;head>.');
762         }
763         $this->when = page_requirements_manager::WHEN_IN_HEAD;
764     }
766     /**
767      * Indicate that the link to this JavaScript file should be output at the top
768      * of the <body> section of the HTML. If it too late for this request to be
769      * satisfied, an exception is thrown.
770      */
771     public function at_top_of_body() {
772         if ($this->is_done() || $this->when <= page_requirements_manager::WHEN_TOP_OF_BODY) {
773             return;
774         }
775         if ($this->manager->is_top_of_body_done()) {
776             throw new coding_exception('Too late to ask for a JavaScript file to be linked to from the top of &lt;body>.');
777         }
778         $this->when = page_requirements_manager::WHEN_TOP_OF_BODY;
779     }
783 /**
784  * A subclass of {@link linked_requirement} to represent a required CSS file.
785  * Of course, all links to CSS files must go in the <head> section of the HTML.
786  *
787  * @copyright 2009 Tim Hunt
788  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
789  * @since Moodle 2.0
790  */
791 class required_css extends linked_requirement {
792     /**
793      * Constructor. Normally instances of this class should not be created directly.
794      * Client code should create them via the page_requirements_manager
795      * method {@link page_requirements_manager::css()}
796      *
797      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
798      * @param string $url The URL of the CSS file we are linking to.
799      */
800     public function __construct(page_requirements_manager $manager, $url) {
801         parent::__construct($manager, $url);
802         $this->when = page_requirements_manager::WHEN_IN_HEAD;
803     }
805     public function get_html() {
806         return '<link rel="stylesheet" type="text/css" href="' . $this->url . '" />' . "\n";;
807     }
811 /**
812  * A subclass of {@link linked_requirement} to represent a skip link.
813  * A skip link is a concept from accessibility. You have some links like
814  * 'Skip to main content' linking to an #maincontent anchor, at the start of the
815  * <body> tag, so that users using assistive technologies like screen readers
816  * can easily get to the main content without having to work their way through
817  * any navigation, blocks, etc. that comes before it in the HTML.
818  *
819  * @copyright 2009 Tim Hunt
820  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
821  * @since Moodle 2.0
822  */
823 class required_skip_link extends linked_requirement {
824     protected $linktext;
826     /**
827      * Constructor. Normally instances of this class should not be created directly.
828      * Client code should create them via the page_requirements_manager
829      * method {@link page_requirements_manager::yui2_lib()}.
830      *
831      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
832      * @param string $target the name of the anchor in the page we are linking to.
833      * @param string $linktext the test to use for the link.
834      */
835     public function __construct(page_requirements_manager $manager, $target, $linktext) {
836         parent::__construct($manager, $target);
837         $this->when = page_requirements_manager::WHEN_TOP_OF_BODY;
838         $this->linktext = $linktext;
839     }
841     public function get_html() {
842         return '<a class="skip" href="#' . $this->url . '">' . $this->linktext . "</a>\n";
843     }
847 /**
848  * This is the base class for requirements that are JavaScript code.
849  *
850  * @copyright 2009 Tim Hunt
851  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
852  * @since Moodle 2.0
853  */
854 abstract class required_js_code extends requirement_base {
856     /**
857      * Constructor.
858      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
859      */
860     protected function __construct(page_requirements_manager $manager) {
861         parent::__construct($manager);
862         $this->when = page_requirements_manager::WHEN_AT_END;
863     }
865     /**
866      * @return string the JavaScript code needed to satisfy this requirement.
867      */
868     abstract public function get_js_code();
870    /**
871      * Indicate that the link to this JavaScript file should be output as soon as
872      * possible. That is, if this requirement has already been output, this method
873      * does nothing. Otherwise, if the <head> tag has not yet been printed, the link
874      * to this script will be put in <head>. Otherwise, this method returns a
875      * fragment of HTML that the caller is responsible for outputting as soon as
876      * possible. In fact, it is recommended that you only call this function from
877      * an echo statement, like:
878      * <pre>
879      *     echo $PAGE->requires->js(...)->asap();
880      * </pre>
881      *
882      * @return string The HTML for the script tag. The caller
883      * is responsible for outputting this HTML promptly.
884      */
885     public function asap() {
886         if ($this->manager->is_head_done()) {
887             return $this->now();
888         } else {
889             $this->in_head();
890             return '';
891         }
892     }
894     /**
895      * Return the required JavaScript immediately, so it can be included in some
896      * HTML that is being built.
897      * @return string The HTML for the script tag. The caller
898      * is responsible for making sure it is output.
899      */
900     public function now() {
901         if ($this->is_done()) {
902             return '';
903         }
904         $js = $this->get_js_code();
905         $output = ajax_generate_script_tag($js);
906         $this->mark_done();
907         return $output;
908     }
910     /**
911      * Indicate that the link to this JavaScript file should be output in the
912      * <head> section of the HTML. If it too late for this request to be
913      * satisfied, an exception is thrown.
914      */
915     public function in_head() {
916         if ($this->is_done() || $this->when <= page_requirements_manager::WHEN_IN_HEAD) {
917             return;
918         }
919         if ($this->manager->is_head_done()) {
920             throw new coding_exception('Too late to ask for some JavaScript code to be output in &lt;head>.');
921         }
922         $this->when = page_requirements_manager::WHEN_IN_HEAD;
923     }
925     /**
926      * Indicate that the link to this JavaScript file should be output at the top
927      * of the <body> section of the HTML. If it too late for this request to be
928      * satisfied, an exception is thrown.
929      */
930     public function at_top_of_body() {
931         if ($this->is_done() || $this->when <= page_requirements_manager::WHEN_TOP_OF_BODY) {
932             return;
933         }
934         if ($this->manager->is_top_of_body_done()) {
935             throw new coding_exception('Too late to ask for some JavaScript code to be output at the top of &lt;body>.');
936         }
937         $this->when = page_requirements_manager::WHEN_TOP_OF_BODY;
938     }
942 /**
943  * This class represents a JavaScript function that must be called from the HTML
944  * page. By default the call will be made at the end of the page, but you can
945  * chage that using the {@link asap()}, {@link in_head()}, etc. methods.
946  *
947  * @copyright 2009 Tim Hunt
948  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
949  * @since Moodle 2.0
950  */
951 class required_js_function_call extends required_js_code {
952     protected $function;
953     protected $arguments;
954     protected $delay = 0;
956     /**
957      * Constructor. Normally instances of this class should not be created directly.
958      * Client code should create them via the page_requirements_manager
959      * method {@link page_requirements_manager::js_function_call()}.
960      *
961      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
962      * @param string $function the name of the JavaScritp function to call.
963      *      Can be a compound name like 'YAHOO.util.Event.addListener'.
964      * @param array $arguments and array of arguments to be passed to the function.
965      *      When generating the function call, this will be escaped using json_encode,
966      *      so passing objects and arrays should work.
967      */
968     public function __construct(page_requirements_manager $manager, $function, $arguments) {
969         parent::__construct($manager);
970         $this->function = $function;
971         $this->arguments = $arguments;
972     }
974     public function get_js_code() {
975         $quotedargs = array();
976         foreach ($this->arguments as $arg) {
977             $quotedargs[] = json_encode($arg);
978         }
979         $js = $this->function . '(' . implode(', ', $quotedargs) . ');';
980         if ($this->delay) {
981             $js = 'setTimeout(function() { ' . $js . ' }, ' . ($this->delay * 1000) . ');';
982         }
983         return $js . "\n";
984     }
986     /**
987      * Indicate that this function should be called in YUI's onDomReady event.
988      *
989      * Not that this is probably not necessary most of the time. Just having the
990      * function call at the end of the HTML should normally be sufficient.
991      */
992     public function on_dom_ready() {
993         if ($this->is_done() || $this->when < page_requirements_manager::WHEN_AT_END) {
994             return;
995         }
996         $this->manager->yui2_lib('event');
997         $this->when = page_requirements_manager::WHEN_ON_DOM_READY;
998     }
1000     /**
1001      * Indicate that this function should be called a certain number of seconds
1002      * after the page has finished loading. (More exactly, a number of seconds
1003      * after the onDomReady event fires.)
1004      *
1005      * @param integer $seconds the number of seconds delay.
1006      */
1007     public function after_delay($seconds) {
1008         if ($seconds) {
1009             $this->on_dom_ready();
1010         }
1011         $this->delay = $seconds;
1012     }
1016 /**
1017  * This class represents some data from PHP that needs to be made available in a
1018  * global JavaScript variable. By default the data will be output at the end of
1019  * the page, but you can chage that using the {@link asap()}, {@link in_head()}, etc. methods.
1020  *
1021  * @copyright 2009 Tim Hunt
1022  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1023  * @since Moodle 2.0
1024  */
1025 class required_data_for_js extends required_js_code {
1026     protected $variable;
1027     protected $data;
1029     /**
1030      * Constructor. Normally the class and its subclasses should not be created directly.
1031      * Client code should create them via the page_requirements_manager
1032      * method {@link page_requirements_manager::data_for_js()}.
1033      *
1034      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
1035      * @param string $variable the the name of the JavaScript variable to assign the data to.
1036      *      Will probably work if you use a compound name like 'mybuttons.button[1]', but this
1037      *      should be considered an experimental feature.
1038      * @param mixed $data The data to pass to JavaScript. This will be escaped using json_encode,
1039      *      so passing objects and arrays should work.
1040      */
1041     public function __construct(page_requirements_manager $manager, $variable, $data) {
1042         parent::__construct($manager);
1043         $this->variable = $variable;
1044         $this->data = json_encode($data);
1045         // json_encode immediately, so that if $data is an object (and therefore was
1046         // passed in by reference) we get the data at the time the call was made, and
1047         // not whatever the data happened to be when this is output.
1048     }
1050     public function get_js_code() {
1051         $prefix = 'var ';
1052         if (strpos($this->variable, '.') || strpos($this->variable, '[')) {
1053             $prefix = '';
1054         }
1055         return $prefix . $this->variable . ' = ' . $this->data . ";\n";
1056     }
1059 /**
1060  * This class represents a Javascript event handler, listening for a
1061  * specific Event to occur on a DOM element identified by a given id.
1062  * By default the data will be output at the end of the page, but you
1063  * can change that using the {@link asap()}, {@link in_head()}, etc. methods.
1064  *
1065  * @copyright 2009 Nicolas Connault
1066  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1067  * @since Moodle 2.0
1068  */
1069 class required_event_handler extends required_js_code {
1070     protected $id;
1071     protected $event;
1072     protected $function;
1073     protected $args = array();
1075     /**
1076      * Constructor. Normally the class and its subclasses should not be created directly.
1077      * Client code should create them via the page_requirements_manager
1078      * method {@link page_requirements_manager::data_for_js()}.
1079      *
1080      * @param page_requirements_manager $manager the page_requirements_manager we are associated with.
1081      * @param string $id The id of the DOM element that will be listening for the event
1082      * @param string $event A valid DOM event (click, mousedown, change etc.)
1083      * @param string $function The name of the function to call
1084      * @param array  $arguments An optional array of argument parameters to pass to the function
1085      */
1086     public function __construct(page_requirements_manager $manager, $id, $event, $function, $args=array()) {
1087         parent::__construct($manager);
1088         $this->id = $id;
1089         $this->event = $event;
1090         $this->function = $function;
1091         $this->args = $args;
1092     }
1094     public function get_js_code() {
1095         $output = "YAHOO.util.Event.addListener('$this->id', '$this->event', $this->function";
1096         if (!empty($this->args)) {
1097             $output .= ', ' . json_encode($this->args);
1098         }
1099         return $output . ");\n";
1100     }
1103 /**
1104  * Generate a script tag containing the the specified code.
1105  *
1106  * @param string $js the JavaScript code
1107  * @return string HTML, the code wrapped in <script> tags.
1108  */
1109 function ajax_generate_script_tag($js) {
1110     if ($js) {
1111         return '<script type="text/javascript">' . "\n//<![CDATA[\n" .
1112                 $js . "//]]>\n</script>\n";
1113     } else {
1114         return '';
1115     }
1119 /**
1120  * Return the HTML required to link to a JavaScript file.
1121  * @param $url the URL of a JavaScript file.
1122  * @return string the required HTML.
1123  */
1124 function ajax_get_link_to_script($url) {
1125     return '<script type="text/javascript"  src="' . $url . '"></script>' . "\n";
1129 /**
1130  * Returns whether ajax is enabled/allowed or not.
1131  */
1132 function ajaxenabled($browsers = array()) {
1134     global $CFG, $USER;
1136     if (!empty($browsers)) {
1137         $valid = false;
1138         foreach ($browsers as $brand => $version) {
1139             if (check_browser_version($brand, $version)) {
1140                 $valid = true;
1141             }
1142         }
1144         if (!$valid) {
1145             return false;
1146         }
1147     }
1149     $ie = check_browser_version('MSIE', 6.0);
1150     $ff = check_browser_version('Gecko', 20051106);
1151     $op = check_browser_version('Opera', 9.0);
1152     $sa = check_browser_version('Safari', 412);
1154     if (!$ie && !$ff && !$op && !$sa) {
1155         /** @see http://en.wikipedia.org/wiki/User_agent */
1156         // Gecko build 20051107 is what is in Firefox 1.5.
1157         // We still have issues with AJAX in other browsers.
1158         return false;
1159     }
1161     if (!empty($CFG->enableajax) && (!empty($USER->ajax) || !isloggedin())) {
1162         return true;
1163     } else {
1164         return false;
1165     }
1169 /**
1170  * Used to create view of document to be passed to JavaScript on pageload.
1171  * We use this class to pass data from PHP to JavaScript.
1172  */
1173 class jsportal {
1175     var $currentblocksection = null;
1176     var $blocks = array();
1179     /**
1180      * Takes id of block and adds it
1181      */
1182     function block_add($id, $hidden=false){
1183         $hidden_binary = 0;
1185         if ($hidden) {
1186             $hidden_binary = 1;
1187         }
1188         $this->blocks[count($this->blocks)] = array($this->currentblocksection, $id, $hidden_binary);
1189     }
1192     /**
1193      * Prints the JavaScript code needed to set up AJAX for the course.
1194      */
1195     function print_javascript($courseid, $return=false) {
1196         global $CFG, $USER, $OUTPUT, $COURSE;
1198         $blocksoutput = $output = '';
1199         for ($i=0; $i<count($this->blocks); $i++) {
1200             $blocksoutput .= "['".$this->blocks[$i][0]."',
1201                              '".$this->blocks[$i][1]."',
1202                              '".$this->blocks[$i][2]."']";
1204             if ($i != (count($this->blocks) - 1)) {
1205                 $blocksoutput .= ',';
1206             }
1207         }
1208         $output .= "<script type=\"text/javascript\">\n";
1209         $output .= "    main.portal.id = ".$courseid.";\n";
1210         $output .= "    main.portal.blocks = new Array(".$blocksoutput.");\n";
1211         $output .= "    main.portal.strings['courseformat']='".$COURSE->format."';\n";
1212         $output .= "    main.portal.strings['wwwroot']='".$CFG->wwwroot."';\n";
1213         $output .= "    main.portal.strings['marker']='".get_string('markthistopic', '', '_var_')."';\n";
1214         $output .= "    main.portal.strings['marked']='".get_string('markedthistopic', '', '_var_')."';\n";
1215         $output .= "    main.portal.numsections = ".$COURSE->numsections.";\n";
1216         $output .= "    main.portal.strings['hide']='".get_string('hide')."';\n";
1217         $output .= "    main.portal.strings['hidesection']='".get_string('hidesection', '', '_var_')."';\n";
1218         $output .= "    main.portal.strings['show']='".get_string('show')."';\n";
1219         $output .= "    main.portal.strings['delete']='".get_string('delete')."';\n";
1220         $output .= "    main.portal.strings['move']='".get_string('move')."';\n";
1221         $output .= "    main.portal.strings['movesection']='".get_string('movesection', '', '_var_')."';\n";
1222         $output .= "    main.portal.strings['moveleft']='".get_string('moveleft')."';\n";
1223         $output .= "    main.portal.strings['moveright']='".get_string('moveright')."';\n";
1224         $output .= "    main.portal.strings['update']='".get_string('update')."';\n";
1225         $output .= "    main.portal.strings['groupsnone']='".get_string('groupsnone')."';\n";
1226         $output .= "    main.portal.strings['groupsseparate']='".get_string('groupsseparate')."';\n";
1227         $output .= "    main.portal.strings['groupsvisible']='".get_string('groupsvisible')."';\n";
1228         $output .= "    main.portal.strings['clicktochange']='".get_string('clicktochange')."';\n";
1229         $output .= "    main.portal.strings['deletecheck']='".get_string('deletecheck','','_var_')."';\n";
1230         $output .= "    main.portal.strings['resource']='".get_string('resource')."';\n";
1231         $output .= "    main.portal.strings['activity']='".get_string('activity')."';\n";
1232         $output .= "    main.portal.strings['sesskey']='".sesskey()."';\n";
1233         $output .= "    main.portal.icons['spacerimg']='".$OUTPUT->pix_url('spaces')."';\n";
1234         $output .= "    main.portal.icons['marker']='".$OUTPUT->pix_url('i/marker')."';\n";
1235         $output .= "    main.portal.icons['ihide']='".$OUTPUT->pix_url('i/hide')."';\n";
1236         $output .= "    main.portal.icons['move_2d']='".$OUTPUT->pix_url('i/move_2d')."';\n";
1237         $output .= "    main.portal.icons['show']='".$OUTPUT->pix_url('t/show')."';\n";
1238         $output .= "    main.portal.icons['hide']='".$OUTPUT->pix_url('t/hide')."';\n";
1239         $output .= "    main.portal.icons['delete']='".$OUTPUT->pix_url('t/delete')."';\n";
1240         $output .= "    main.portal.icons['groupn']='".$OUTPUT->pix_url('t/groupn')."';\n";
1241         $output .= "    main.portal.icons['groups']='".$OUTPUT->pix_url('t/groups')."';\n";
1242         $output .= "    main.portal.icons['groupv']='".$OUTPUT->pix_url('t/groupv')."';\n";
1243         if (right_to_left()) {
1244             $output .= "    main.portal.icons['backwards']='".$OUTPUT->pix_url('t/right')."';\n";
1245             $output .= "    main.portal.icons['forwards']='".$OUTPUT->pix_url('t/left')."';\n";
1246         } else {
1247             $output .= "    main.portal.icons['backwards']='".$OUTPUT->pix_url('t/left')."';\n";
1248             $output .= "    main.portal.icons['forwards']='".$OUTPUT->pix_url('t/right')."';\n";
1249         }
1251         $output .= "    onloadobj.load();\n";
1252         $output .= "    main.process_blocks();\n";
1253         $output .= "</script>";
1254         if ($return) {
1255             return $output;
1256         } else {
1257             echo $output;
1258         }
1259     }