MDL-21400 finalising JS api - removing ->on_dom_ready (now bool param in js() and...
[moodle.git] / lib / ajax / ajaxlib.php
CommitLineData
f72f94a2 1<?php
b2330db6 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 *
22 * @package moodlecore
23 * @copyright 2009 Tim Hunt
24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
27
b2330db6 28/**
29 * This class tracks all the things that are needed by the current page.
30 *
31 * Normally, the only instance of this class you will need to work with is the
32 * one accessible via $PAGE->requires.
33 *
34 * Typical useage would be
9ca13950 35 * <pre>
593f9b87
PS
36 * $PAGE->requires->init_js_call('M.mod_forum.init_view');
37 * </pre>
38 *
39 * It also supports obsoleted coding style withouth YUI3 modules.
40 * <pre>
9dec75db
PS
41 * $PAGE->requires->css('/mod/mymod/userstyles.php?id='.$id); // not overriddable via themes!
42 * $PAGE->requires->js('/mod/mymod/script.js');
60409fe1 43 * $PAGE->requires->js('/mod/mymod/small_but_urgent.js', true);
593f9b87 44 * $PAGE->requires->js_function_call('init_mymod', array($data), true);
9ca13950 45 * </pre>
b2330db6 46 *
2c144fc3 47 * There are some natural restrictions on some methods. For example, {@link css()}
48 * can only be called before the <head> tag is output. See the comments on the
49 * individual methods for details.
50 *
593f9b87 51 * @copyright 2009 Tim Hunt, 2010 Petr Skoda
b2330db6 52 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5c5418fe 53 * @since Moodle 2.0
b2330db6 54 */
55class page_requirements_manager {
cd9729e4
PS
56 /** List of string available from JS */
57 protected $stringsforjs = array();
58 /** List of JS variables to be initialised */
59 protected $jsinitvariables = array('head'=>array(), 'footer'=>array());
60409fe1
PS
60 /** Included JS scripts */
61 protected $jsincludes = array('head'=>array(), 'footer'=>array());
593f9b87
PS
62 /** List of needed function calls */
63 protected $jscalls = array('normal'=>array(), 'ondomready'=>array());
f5de8ee7
PS
64 /**
65 * List of skip links, those are needed for accessibility reasons
66 * @var array
67 */
68 protected $skiplinks = array();
08fac734
PS
69 /**
70 * Javascript code used for initialisation of page, it shoudl be relatively small
71 * @var array
72 */
73 protected $jsinitcode = array();
c0467479
PS
74 /**
75 * Theme sheets, initialised only from core_renderer
76 * @var array of moodle_url
77 */
e50b4c89 78 protected $cssthemeurls = array();
c0467479
PS
79 /**
80 * List of custom theme sheets, these are strongly discouraged!
81 * Useful mostly only for CSS submitted by teachers that is not part of the theme.
82 * @var array of moodle_url
83 */
e50b4c89 84 protected $cssurls = array();
227255b8
PS
85 /**
86 * List of requested event handlers
87 * @var array
88 */
89 protected $eventhandlers = array();
e50b4c89
PS
90 /**
91 * Extra modules
92 * @var array
93 */
94 protected $extramodules = array();
593f9b87 95 /** Flag indicated head stuff already printed */
b2330db6 96 protected $headdone = false;
593f9b87 97 /** Flag indicating top of body already printed */
b2330db6 98 protected $topofbodydone = false;
99
945f19f7 100 /** YUI PHPLoader instance responsible for YUI2 loading from PHP only */
985d1d1d 101 protected $yui2loader;
945f19f7
PS
102 /** YUI PHPLoader instance responsible for YUI3 loading from PHP only */
103 protected $yui3loader;
104 /** YUI PHPLoader instance responsible for YUI3 loading from javascript */
e50b4c89 105 protected $M_yui_loader;
1be3ec80 106 /** some config vars exposed in JS, please no secret stuff there */
9598d578 107 protected $M_cfg;
4c907141
PS
108
109 /**
110 * Page requirements constructor.
111 */
112 public function __construct() {
113 global $CFG;
945f19f7 114
4c907141
PS
115 require_once("$CFG->libdir/yui/phploader/phploader/loader.php");
116
117 $this->yui3loader = new YAHOO_util_Loader($CFG->yui3version);
985d1d1d 118 $this->yui2loader = new YAHOO_util_Loader($CFG->yui2version);
4c907141
PS
119
120 // set up some loader options
4c907141
PS
121 if (debugging('', DEBUG_DEVELOPER)) {
122 $this->yui3loader->filter = YUI_DEBUG; // alternatively we could use just YUI_RAW here
985d1d1d 123 $this->yui2loader->filter = YUI_DEBUG; // alternatively we could use just YUI_RAW here
4c907141
PS
124 } else {
125 $this->yui3loader->filter = null;
985d1d1d 126 $this->yui2loader->filter = null;
95fbe49b 127 }
4c907141
PS
128 if (!empty($CFG->useexternalyui)) {
129 $this->yui3loader->base = 'http://yui.yahooapis.com/' . $CFG->yui3version . '/build/';
985d1d1d 130 $this->yui2loader->base = 'http://yui.yahooapis.com/' . $CFG->yui2version . '/build/';
945f19f7
PS
131 $this->yui3loader->comboBase = 'http://yui.yahooapis.com/combo?';
132 $this->yui2loader->comboBase = 'http://yui.yahooapis.com/combo?';
4c907141 133 } else {
aa42314d
PS
134 $this->yui3loader->base = $CFG->httpswwwroot . '/lib/yui/'. $CFG->yui3version . '/build/';
135 $this->yui2loader->base = $CFG->httpswwwroot . '/lib/yui/'. $CFG->yui2version . '/build/';
945f19f7
PS
136 $this->yui3loader->comboBase = $CFG->httpswwwroot . '/theme/yui_combo.php?';
137 $this->yui2loader->comboBase = $CFG->httpswwwroot . '/theme/yui_combo.php?';
4c907141
PS
138 }
139
945f19f7 140 // enable combo loader? this significantly helps with caching and performance!
aa42314d
PS
141 $this->yui3loader->combine = !empty($CFG->yuicomboloading);
142 $this->yui2loader->combine = !empty($CFG->yuicomboloading);
945f19f7 143
2212eb7a 144 // set up JS YUI loader helper object
e50b4c89
PS
145 $this->M_yui_loader = new stdClass();
146 $this->M_yui_loader->base = $this->yui3loader->base;
147 $this->M_yui_loader->comboBase = $this->yui3loader->comboBase;
148 $this->M_yui_loader->combine = $this->yui3loader->combine;
149 $this->M_yui_loader->filter = ($this->yui3loader->filter == YUI_DEBUG) ? 'debug' : '';
150 $this->M_yui_loader->insertBefore = 'firstthemesheet';
151 $this->M_yui_loader->modules = array();
c8870e68 152 $this->add_yui2_modules(); // adds loading info for YUI2
4c508047 153 $this->js_module($this->find_module('core_filepicker'));
781bd8ae 154 $this->js_module($this->find_module('core_dock'));
e3f4e3ca
PS
155
156 // YUI3 init code
157 $libs = array('cssreset', 'cssbase', 'cssfonts', 'cssgrids', 'node', 'loader'); // full CSS reset + basic libs
158 foreach ($libs as $lib) {
159 $this->yui3loader->load($lib);
160 }
c8870e68
PS
161 }
162
163 /**
164 * This method adds yui2 modules into the yui3 JS loader-
165 * @return void
166 */
167 protected function add_yui2_modules() {
168 //note: this function is definitely not perfect, because
169 // it adds tons of markup into each page, but it can be
170 // abstracted into separate JS file with proper headers
171 global $CFG;
172
173 $GLOBALS['yui_current'] = array();
174 require($CFG->libdir.'/yui/phploader/lib/meta/config_'.$CFG->yui2version.'.php');
175 $info = $GLOBALS['yui_current'];
176 unset($GLOBALS['yui_current']);
177
178 if (empty($CFG->yuicomboloading)) {
179 $urlbase = $this->yui2loader->base;
180 } else {
181 $urlbase = $this->yui2loader->comboBase.$CFG->yui2version.'/build/';
182 }
183
184 $modules = array();
185 $ignored = array(); // list of CSS modules that are not needed
186 foreach ($info['moduleInfo'] as $name => $module) {
187 if ($module['type'] === 'css') {
188 $ignored[$name] = true;
189 } else {
190 $modules['yui2-'.$name] = $module;
191 }
192 }
193 foreach ($modules as $name=>$module) {
194 $module['fullpath'] = $urlbase.$module['path']; // fix path to point to correct location
195 unset($module['path']);
196 foreach(array('requires', 'optional', 'supersedes') as $fixme) {
197 if (!empty($module[$fixme])) {
198 $fixed = false;
199 foreach ($module[$fixme] as $key=>$dep) {
200 if (isset($ignored[$dep])) {
201 unset($module[$fixme][$key]);
202 $fixed = true;
203 } else {
204 $module[$fixme][$key] = 'yui2-'.$dep;
205 }
206 }
207 if ($fixed) {
208 $module[$fixme] = array_merge($module[$fixme]); // fix keys
209 }
210 }
211 }
e50b4c89 212 $this->M_yui_loader->modules[$name] = $module;
c8870e68 213 }
945f19f7
PS
214 }
215
216 /**
217 * Initialise with the bits of JavaScript that every Moodle page should have.
218 *
219 * @param moodle_page $page
220 * @param core_renderer $output
221 */
227255b8 222 protected function init_requirements_data(moodle_page $page, core_renderer $renderer) {
945f19f7
PS
223 global $CFG;
224
225 // JavaScript should always work with $CFG->httpswwwroot rather than $CFG->wwwroot.
226 // Otherwise, in some situations, users will get warnings about insecure content
227 // on sercure pages from their web browser.
228
9598d578 229 $this->M_cfg = array(
945f19f7
PS
230 'wwwroot' => $CFG->httpswwwroot, // Yes, really. See above.
231 'sesskey' => sesskey(),
227255b8 232 'loadingicon' => $renderer->pix_url('i/loading_small', 'moodle')->out(false),
945f19f7
PS
233 'themerev' => theme_get_revision(),
234 'theme' => $page->theme->name,
945f19f7
PS
235 );
236 if (debugging('', DEBUG_DEVELOPER)) {
9598d578 237 $this->M_cfg['developerdebug'] = true;
945f19f7 238 }
945f19f7 239
945f19f7
PS
240 if (debugging('', DEBUG_DEVELOPER)) {
241 $this->yui2_lib('logger');
242 }
243
7b42e81a 244 // accessibility stuff
945f19f7
PS
245 $this->skip_link_to('maincontent', get_string('tocontent', 'access'));
246
88515081 247 // For now include YUI2, this will be removed before beta.
2574fae6
PS
248 $this->yui2_lib('dom'); // needs to be migrated to YUI3 before we release 2.0
249 $this->yui2_lib('container'); // needs to be migrated to YUI3 before we release 2.0
250 $this->yui2_lib('connection'); // needs to be migrated to YUI3 before we release 2.0
251
945f19f7
PS
252 $this->string_for_js('confirmation', 'admin');
253 $this->string_for_js('cancel', 'moodle');
254 $this->string_for_js('yes', 'moodle');
255 $this->js_function_call('init_help_icons');
4c907141
PS
256 }
257
b2330db6 258 /**
2c144fc3 259 * Ensure that the specified JavaScript file is linked to from this page.
260 *
c66a13b2
PS
261 * NOTE: This function is to be used in rare cases only, please store your JS in module.js file
262 * and use $PAGE->requires->js_init_call() instead.
263 *
2c144fc3 264 * By default the link is put at the end of the page, since this gives best page-load performance.
b2330db6 265 *
266 * Even if a particular script is requested more than once, it will only be linked
267 * to once.
268 *
c66a13b2
PS
269 * @param string|moodle_url $url The path to the .js file, relative to $CFG->dirroot / $CFG->wwwroot.
270 * For example '/mod/mymod/customscripts.js'; use moodle_url for external scripts
60409fe1
PS
271 * @param bool $inhead initialise in head
272 * @return void
b2330db6 273 */
60409fe1 274 public function js($url, $inhead=false) {
c66a13b2 275 $url = $this->js_fix_url($url);
60409fe1
PS
276 $where = $inhead ? 'head' : 'footer';
277 $this->jsincludes[$where][$url->out()] = $url;
b2330db6 278 }
279
280 /**
4c907141 281 * Ensure that the specified YUI2 library file, and all its required dependancies,
2c144fc3 282 * are linked to from this page.
283 *
284 * By default the link is put at the end of the page, since this gives best page-load
285 * performance. Optional dependencies are not loaded automatically - if you want
286 * them you will need to load them first with other calls to this method.
b2330db6 287 *
b2330db6 288 * Even if a particular library is requested more than once (perhaps as a dependancy
289 * of other libraries) it will only be linked to once.
290 *
985d1d1d 291 * The library is leaded as soon as possible, if $OUTPUT->header() not used yet it
945f19f7 292 * is put into the page header, otherwise it is loaded in the page footer.
985d1d1d
PS
293 *
294 * @param string|array $libname the name of the YUI2 library you require. For example 'autocomplete'.
295 * @return void
b2330db6 296 */
f44b10ed 297 public function yui2_lib($libname) {
985d1d1d
PS
298 $libnames = (array)$libname;
299 foreach ($libnames as $lib) {
300 $this->yui2loader->load($lib);
b2330db6 301 }
b2330db6 302 }
303
c8870e68 304 /**
2b8c3f8c
PS
305 * Returns the actual url through which a script is served.
306 * @param moodle_url|string $url full moodle url, or shortened path to script
307 * @return moodle_url
c8870e68 308 */
c66a13b2 309 protected function js_fix_url($url) {
c8870e68
PS
310 global $CFG;
311
2b8c3f8c
PS
312 if ($url instanceof moodle_url) {
313 return $url;
c66a13b2
PS
314 } else if (strpos($url, '/') === 0) {
315 if (debugging()) {
316 // check file existence only when in debug mode
317 if (!file_exists($CFG->dirroot . strtok($url, '?'))) {
318 throw new coding_exception('Attept to require a JavaScript file that does not exist.', $url);
319 }
320 }
2b8c3f8c
PS
321 //return new moodle_url($CFG->httpswwwroot.'/lib/javascript.php', array('file'=>$url, 'rev'=>$CFG->jsrev));
322 return new moodle_url($CFG->httpswwwroot.$url);
c66a13b2
PS
323 } else {
324 throw new coding_exception('Invalid JS url, it has to be shortened url starting with / or moodle_url instance.', $url);
c8870e68 325 }
2b8c3f8c 326 }
c8870e68 327
2b8c3f8c
PS
328 /**
329 * Find out if JS modulke present and return details.
330 * @param string $name name of module, ex: core_group, mod_forum
331 * @return array description of module or null if not found
332 */
333 protected function find_module($name) {
334 global $CFG;
335
336 $module = null;
337
338 if (strpos($name, 'core_') === 0) {
339 // must be some core stuff
340 if ($name === 'core_filepicker') {
4c508047 341 $module = array('name'=>$name, 'fullpath'=>'/repository/filepicker.js', 'requires' => array('base', 'node', 'json', 'async-queue', 'io', 'yui2-button', 'yui2-container', 'yui2-layout', 'yui2-menu', 'yui2-treeview'));
2b8c3f8c
PS
342 } else if($name === 'core_comment') {
343 $module = array('name'=>$name, 'fullpath'=>'/comment/comment.js', 'requires' => array('base', 'io', 'node', 'json', 'yui2-animation'));
344 } else if($name === 'core_role') {
345 $module = array('name'=>$name, 'fullpath'=>'/admin/roles/module.js');
76c0123b
PS
346 } else if($name === 'core_completion') {
347 $module = array('name'=>$name, 'fullpath'=>'/course/completion.js');
53fc3e70
SH
348 } else if ($name === 'core_dock') {
349 $module = array('name'=>'core_dock', 'fullpath'=>'/blocks/dock.js', 'requires'=>array('base', 'cookie', 'dom', 'io', 'node', 'event-custom'));
350 } else if ($name === 'core_calendar') {
351 $module = array('name'=>'core_calendar', 'fullpath'=>'/calendar/calendar.js', 'requires'=>array('dom', 'event', 'node', 'yui2-container','event-mouseenter'));
4abd5b9b
PS
352 } else if ($name === 'core_message') {
353 $module = array('name'=>'core_message', 'fullpath'=>'/message/module.js');
354 } else if ($name === 'core_flashdetect') {
355 $module = array('name'=>'core_flashdetect', 'fullpath'=>'/lib/flashdetect/flashdetect.js', 'requires'=>array('io'));
781bd8ae
PS
356 } else if ($name === 'core_dock') {
357 $module = array('name'=>'core_dock', 'fullpath'=>'/blocks/dock.js', 'requires'=>array('base', 'cookie', 'dom', 'io', 'node', 'event-custom'));
2b8c3f8c
PS
358 }
359 } else {
360 if ($dir = get_component_directory($name, false)) {
361 if (file_exists("$CFG->dirroot/$dir/module.js")) {
76418c14 362 $module = array('name'=>$name, 'fullpath'=>"/$dir/module.js", 'requires' => array());
3b01539c
PS
363 }
364 }
c8870e68 365 }
2b8c3f8c
PS
366 return $module;
367 }
368
369 /**
370 * Append YUI3 module to default YUI3 JS loader.
371 * The structure of module array is described at http://developer.yahoo.com/yui/3/yui/:
372 * @param string|array $module name of module (details are autodetected), or full module specification as array
373 * @return void
374 */
375 public function js_module($module) {
376 global $CFG;
c8870e68 377
3b01539c 378 if (empty($module)) {
2b8c3f8c
PS
379 throw new coding_exception('Missing YUI3 module name or full description.');
380 }
381
382 if (is_string($module)) {
ebfb8f13 383 $module = $this->find_module($module);
2b8c3f8c
PS
384 }
385
386 if (empty($module) or empty($module['name']) or empty($module['fullpath'])) {
3b01539c 387 throw new coding_exception('Missing YUI3 module details.');
e50b4c89
PS
388 }
389
593f9b87 390 $module['fullpath'] = $this->js_fix_url($module['fullpath'])->out(false);
2b8c3f8c 391
e50b4c89 392 if ($this->headdone) {
2b8c3f8c 393 $this->extramodules[$module['name']] = $module;
e50b4c89 394 } else {
2b8c3f8c 395 $this->M_yui_loader->modules[$module['name']] = $module;
e50b4c89 396 }
c8870e68
PS
397 }
398
b2330db6 399 /**
2c144fc3 400 * Ensure that the specified CSS file is linked to from this page.
401 *
402 * Because stylesheet links must go in the <head> part of the HTML, you must call
403 * this function before {@link get_head_code()} is called. That normally means before
b2330db6 404 * the call to print_header. If you call it when it is too late, an exception
405 * will be thrown.
406 *
407 * Even if a particular style sheet is requested more than once, it will only
408 * be linked to once.
409 *
c0467479
PS
410 * Please note sue of this feature is strongly discouraged,
411 * it is suitable only for places where CSS is submitted directly by teachers.
412 * (Students must not be allowed to submit any external CSS because it may
413 * contain embedded javascript!). Example of correct use is mod/data.
414 *
415 * @param string $stylesheet The path to the .css file, relative to $CFG->wwwroot.
416 * For example:
417 * $PAGE->requires->css('mod/data/css.php?d='.$data->id);
b2330db6 418 */
c0467479 419 public function css($stylesheet) {
b2330db6 420 global $CFG;
c0467479
PS
421
422 if ($this->headdone) {
423 throw new coding_exception('Cannot require a CSS file after &lt;head> has been printed.', $stylesheet);
9f509643 424 }
b2330db6 425
c0467479
PS
426 if ($stylesheet instanceof moodle_url) {
427 // ok
428 } else if (strpos($stylesheet, '/') === 0) {
429 $stylesheet = new moodle_url($CFG->httpswwwroot.$stylesheet);
b2330db6 430 } else {
c0467479 431 throw new coding_exception('Invalid stylesheet parameter.', $stylesheet);
b2330db6 432 }
c0467479 433
e50b4c89 434 $this->cssurls[$stylesheet->out()] = $stylesheet; // overrides
b2330db6 435 }
436
2212eb7a
PS
437 /**
438 * Add theme stylkesheet to page - do not use from plugin code,
c0467479 439 * this should be called only from the core renderer!
2212eb7a
PS
440 * @param moodle_url $stylesheet
441 * @return void
442 */
c0467479 443 public function css_theme(moodle_url $stylesheet) {
e50b4c89 444 $this->cssthemeurls[] = $stylesheet;
2212eb7a
PS
445 }
446
b2330db6 447 /**
2c144fc3 448 * Ensure that a skip link to a given target is printed at the top of the <body>.
449 *
450 * You must call this function before {@link get_top_of_body_code()}, (if not, an exception
b2330db6 451 * will be thrown). That normally means you must call this before the call to print_header.
452 *
453 * If you ask for a particular skip link to be printed, it is then your responsibility
2c144fc3 454 * to ensure that the appropraite <a name="..."> tag is printed in the body of the
b2330db6 455 * page, so that the skip link goes somewhere.
456 *
457 * Even if a particular skip link is requested more than once, only one copy of it will be output.
458 *
459 * @param $target the name of anchor this link should go to. For example 'maincontent'.
460 * @param $linktext The text to use for the skip link. Normally get_string('skipto', 'access', ...);
461 */
462 public function skip_link_to($target, $linktext) {
f5de8ee7
PS
463 if ($this->topofbodydone) {
464 debugging('Page header already printed, can not add skip links any more, code needs to be fixed.');
465 return;
b2330db6 466 }
f5de8ee7 467 $this->skiplinks[$target] = $linktext;
b2330db6 468 }
469
470 /**
593f9b87 471 * !!!DEPRECATED!!! please use js_init_call() if possible
b2330db6 472 * Ensure that the specified JavaScript function is called from an inline script
2c144fc3 473 * somewhere on this page.
474 *
475 * By default the call will be put in a script tag at the
08fac734
PS
476 * end of the page after initialising Y instance, since this gives best page-load
477 * performance and allows you to use YUI3 library.
b2330db6 478 *
479 * If you request that a particular function is called several times, then
480 * that is what will happen (unlike linking to a CSS or JS file, where only
481 * one link will be output).
482 *
08fac734
PS
483 * The main benefit of the mehtod is the automatic encoding of all function parameters.
484 *
b2330db6 485 * @param string $function the name of the JavaScritp function to call. Can
08fac734 486 * be a compound name like 'Y.Event.purgeElement'. Can also be
cf615522 487 * used to create and object by using a 'function name' like 'new user_selector'.
b2330db6 488 * @param array $arguments and array of arguments to be passed to the function.
489 * When generating the function call, this will be escaped using json_encode,
490 * so passing objects and arrays should work.
593f9b87
PS
491 * @param bool $ondomready
492 * @param int $delay
493 * @return void
b2330db6 494 */
593f9b87
PS
495 public function js_function_call($function, array $arguments = null, $ondomready = false, $delay = 0) {
496 $where = $ondomready ? 'ondomready' : 'normal';
497 $this->jscalls[$where][] = array($function, $arguments, $delay);
b2330db6 498 }
499
3b01539c
PS
500 /**
501 * Ensure that the specified JavaScript function is called from an inline script
502 * from page footer.
503 *
504 * @param string $function the name of the JavaScritp function to with init code,
505 * usually something like 'M.mod_mymodule.init'
506 * @param array $extraarguments and array of arguments to be passed to the function.
507 * The first argument is always the YUI3 Y instance with all required dependencies
508 * already loaded.
3b01539c 509 * @param bool $ondomready wait for dom ready (helps with some IE problems when modifying DOM)
4c508047 510 * @param array $module JS module specification array
3b01539c
PS
511 * @return void
512 */
4c508047 513 public function js_init_call($function, array $extraarguments = null, $ondomready = false, array $module = null) {
3b01539c 514 $jscode = js_writer::function_call_with_Y($function, $extraarguments);
4c508047
PS
515 if (!$module) {
516 // detect module automatically
517 if (preg_match('/M\.([a-z0-9]+_[^\.]+)/', $function, $matches)) {
518 $module = $this->find_module($matches[1]);
519 }
2b8c3f8c
PS
520 }
521
522 $this->js_init_code($jscode, $ondomready, $module);
3b01539c
PS
523 }
524
08fac734
PS
525 /**
526 * Add short static javascript code fragment to page footer.
527 * This is intended primarily for loading of js modules and initialising page layout.
528 * Ideally the JS code fragment should be stored in plugin renderer so that themes
529 * may override it.
08fac734 530 * @param string $jscode
3b01539c 531 * @param bool $ondomready wait for dom ready (helps with some IE problems when modifying DOM)
2b8c3f8c 532 * @param array $module JS module specification array
08fac734
PS
533 * @return void
534 */
2b8c3f8c 535 public function js_init_code($jscode, $ondomready = false, array $module = null) {
3b01539c
PS
536 $jscode = trim($jscode, " ;\n"). ';';
537
538 if ($module) {
2b8c3f8c
PS
539 $this->js_module($module);
540 $modulename = $module['name'];
3b01539c
PS
541 $jscode = "Y.use('$modulename', function(Y) { $jscode });";
542 }
543
544 if ($ondomready) {
545 $jscode = "Y.on('domready', function() { $jscode });";
546 }
547
548 $this->jsinitcode[] = $jscode;
08fac734
PS
549 }
550
b2330db6 551 /**
2c144fc3 552 * Make a language string available to JavaScript.
117bd748 553 *
2c144fc3 554 * All the strings will be available in a mstr object in the global namespace.
555 * So, for example, after a call to $PAGE->requires->string_for_js('course', 'moodle');
b2330db6 556 * then the JavaScript variable mstr.moodle.course will be 'Course', or the
557 * equivalent in the current language.
558 *
559 * The arguments to this function are just like the arguments to get_string
ef502357 560 * except that $component is not optional, and there are limitations on how you
b2330db6 561 * use $a. Because each string is only stored once in the JavaScript (based
562 * on $identifier and $module) you cannot get the same string with two different
563 * values of $a. If you try, an exception will be thrown.
564 *
565 * If you do need the same string expanded with different $a values, then
566 * the solution is to put them in your own data structure (e.g. and array)
2c144fc3 567 * that you pass to JavaScript with {@link data_for_js()}.
b2330db6 568 *
569 * @param string $identifier the desired string.
570 * @param string $module the language file to look in.
571 * @param mixed $a any extra data to add into the string (optional).
572 */
ef502357
PS
573 public function string_for_js($identifier, $component, $a = NULL) {
574 $string = get_string($identifier, $component, $a);
575 if (!$component) {
2c144fc3 576 throw new coding_exception('The $module parameter is required for page_requirements_manager::string_for_js.');
b2330db6 577 }
ef502357 578 if (isset($this->stringsforjs[$component][$identifier]) && $this->stringsforjs[$component][$identifier] != $string) {
b2330db6 579 throw new coding_exception("Attempt to re-define already required string '$identifier' " .
ef502357 580 "from lang file '$component'. Did you already ask for it with a different \$a?");
b2330db6 581 }
ef502357 582 $this->stringsforjs[$component][$identifier] = $string;
b2330db6 583 }
584
747b85cb 585 /**
586 * Make an array of language strings available for JS
587 *
588 * This function calls the above function {@link string_for_js()} for each requested
589 * string in the $identifiers array that is passed to the argument for a single module
590 * passed in $module.
591 *
592 * <code>
593 * $PAGE->strings_for_js(Array('one', 'two', 'three'), 'mymod', Array('a', null, 3));
594 *
595 * // The above is identifical to calling
596 *
597 * $PAGE->string_for_js('one', 'mymod', 'a');
598 * $PAGE->string_for_js('two', 'mymod');
599 * $PAGE->string_for_js('three', 'mymod', 3);
600 * </code>
601 *
602 * @param array $identifiers An array of desired strings
ef502357 603 * @param string $component The module to load for
747b85cb 604 * @param mixed $a This can either be a single variable that gets passed as extra
605 * information for every string or it can be an array of mixed data where the
606 * key for the data matches that of the identifier it is meant for.
607 *
608 */
ef502357 609 public function strings_for_js($identifiers, $component, $a=NULL) {
747b85cb 610 foreach ($identifiers as $key => $identifier) {
611 if (is_array($a) && array_key_exists($key, $a)) {
612 $extra = $a[$key];
613 } else {
614 $extra = $a;
615 }
ef502357 616 $this->string_for_js($identifier, $component, $extra);
747b85cb 617 }
618 }
619
b2330db6 620 /**
5a197e91
PS
621 * !!!!!!DEPRECATED!!!!!! please use js_init_call() for everything now.
622 *
2c144fc3 623 * Make some data from PHP available to JavaScript code.
117bd748 624 *
2c144fc3 625 * For example, if you call
626 * <pre>
b2330db6 627 * $PAGE->requires->data_for_js('mydata', array('name' => 'Moodle'));
2c144fc3 628 * </pre>
b2330db6 629 * then in JavsScript mydata.name will be 'Moodle'.
b2330db6 630 * @param string $variable the the name of the JavaScript variable to assign the data to.
631 * Will probably work if you use a compound name like 'mybuttons.button[1]', but this
632 * should be considered an experimental feature.
633 * @param mixed $data The data to pass to JavaScript. This will be escaped using json_encode,
634 * so passing objects and arrays should work.
cd9729e4
PS
635 * @param bool $inhead initialise in head
636 * @return void
b2330db6 637 */
cd9729e4
PS
638 public function data_for_js($variable, $data, $inhead=false) {
639 $where = $inhead ? 'head' : 'footer';
640 $this->jsinitvariables[$where][] = array($variable, $data);
b2330db6 641 }
117bd748 642
647df7e3 643 /**
644 * Creates a YUI event handler.
645 *
d96d8f03 646 * @param mixed $selector standard YUI selector for elemnts, may be array or string, element id is in the form "#idvalue"
647df7e3 647 * @param string $event A valid DOM event (click, mousedown, change etc.)
648 * @param string $function The name of the function to call
649 * @param array $arguments An optional array of argument parameters to pass to the function
227255b8 650 * @return void
647df7e3 651 */
d96d8f03 652 public function event_handler($selector, $event, $function, array $arguments = null) {
227255b8
PS
653 $this->eventhandlers[] = array('selector'=>$selector, 'event'=>$event, 'function'=>$function, 'arguments'=>$arguments);
654 }
655
656 /**
657 * Returns code needed for registering of event handlers.
658 * @return string JS code
659 */
660 protected function get_event_handler_code() {
661 $output = '';
662 foreach ($this->eventhandlers as $h) {
663 $output .= js_writer::event_handler($h['selector'], $h['event'], $h['function'], $h['arguments']);
664 }
665 return $output;
647df7e3 666 }
b2330db6 667
b2330db6 668 /**
2c144fc3 669 * Get the inline JavaScript code that need to appear in a particular place.
593f9b87 670 * @return bool $ondomready
b2330db6 671 */
593f9b87
PS
672 protected function get_javascript_code($ondomready) {
673 $where = $ondomready ? 'ondomready' : 'normal';
b2330db6 674 $output = '';
593f9b87
PS
675 if ($this->jscalls[$where]) {
676 foreach ($this->jscalls[$where] as $data) {
677 $output = js_writer::function_call($data[0], $data[1], $data[2]);
678 }
679 if (!empty($ondomready)) {
680 $output = " Y.on('domready', function() {\n$output\n });";
b2330db6 681 }
682 }
683 return $output;
684 }
685
08fac734
PS
686 /**
687 * Returns js code to be executed when Y is available.
688 * @return unknown_type
689 */
690 protected function get_javascript_init_code() {
1ce15fda 691 if (count($this->jsinitcode)) {
3b01539c 692 return implode("\n", $this->jsinitcode) . "\n";
1ce15fda
SH
693 }
694 return '';
08fac734
PS
695 }
696
4c907141
PS
697 /**
698 * Returns basic YUI3 JS loading code.
985d1d1d 699 * YUI3 is using autoloading of both CSS and JS code.
4c907141 700 *
c724f835
PS
701 * Major benefit of this compared to standard js/csss loader is much improved
702 * caching, better browser cache utilisation, much fewer http requests.
703 *
4c907141
PS
704 * @return string
705 */
985d1d1d 706 protected function get_yui3lib_headcode() {
636365f8
PS
707 $code = $this->yui3loader->css() . $this->yui3loader->script();
708 // unfortunately yui loader does not produce xhtml strict code, so let's fix it for now
709 $code = str_replace('&amp;', '&', $code);
710 $code = str_replace('&', '&amp;', $code);
711 return $code;
985d1d1d
PS
712 }
713
714 /**
715 * Returns basic YUI2 JS loading code.
716 * It can be called manually at any time.
c724f835
PS
717 * If called manually the result needs to be output using echo().
718 *
719 * Major benefit of this compared to standard js loader is much improved
720 * caching, better browser cache utilisation, much fewer http requests.
721 *
722 * All YUI2 CSS is loaded automatically.
985d1d1d
PS
723 *
724 * @return string JS embedding code
725 */
726 public function get_yui2lib_code() {
c724f835
PS
727 global $CFG;
728
985d1d1d 729 if ($this->headdone) {
636365f8 730 $code = $this->yui2loader->script_embed();
985d1d1d 731 } else {
636365f8 732 $code = $this->yui2loader->script();
c724f835
PS
733 if ($this->yui2loader->combine) {
734 $skinurl = $this->yui2loader->comboBase . $CFG->yui2version . '/build/assets/skins/sam/skin.css';
735 } else {
736 $skinurl = $this->yui2loader->base . 'assets/skins/sam/skin.css';
737 }
738 // please note this is a temporary hack until we fully migrate to later YUI3 that has all the widgets
c0467479
PS
739 $attributes = array('rel'=>'stylesheet', 'type'=>'text/css', 'href'=>$skinurl);
740 $code .= "\n" . html_writer::empty_tag('link', $attributes) . "\n";
985d1d1d 741 }
636365f8
PS
742 $code = str_replace('&amp;', '&', $code);
743 $code = str_replace('&', '&amp;', $code);
744 return $code;
4c907141
PS
745 }
746
2212eb7a
PS
747 /**
748 * Returns html tags needed for inclusion of theme CSS
749 * @return string
750 */
c0467479
PS
751 protected function get_css_code() {
752 // First of all the theme CSS, then any custom CSS
753 // Please note custom CSS is strongly discouraged,
754 // because it can not be overridden by themes!
755 // It is suitable only for things like mod/data which accepts CSS from teachers.
756
2212eb7a 757 $code = '';
c0467479
PS
758 $attributes = array('id'=>'firstthemesheet', 'rel'=>'stylesheet', 'type'=>'text/css');
759
e50b4c89 760 $urls = $this->cssthemeurls + $this->cssurls;
c0467479
PS
761
762 foreach ($urls as $url) {
763 $attributes['href'] = $url;
764 $code .= html_writer::empty_tag('link', $attributes) . "\n";
765 // this id is needed in first sheet only so that theme may override YUI sheets laoded on the fly
766 unset($attributes['id']);
2212eb7a 767 }
c0467479 768
2212eb7a
PS
769 return $code;
770 }
771
e50b4c89
PS
772 /**
773 * Adds extra modules specified after printing of page header
774 */
775 protected function get_extra_modules_code() {
776 if (empty($this->extramodules)) {
777 return '';
778 }
7b42e81a 779 return html_writer::script(js_writer::function_call('M.yui.add_module', array($this->extramodules)));
e50b4c89
PS
780 }
781
b2330db6 782 /**
2c144fc3 783 * Generate any HTML that needs to go inside the <head> tag.
784 *
785 * Normally, this method is called automatically by the code that prints the
786 * <head> tag. You should not normally need to call it in your own code.
b2330db6 787 *
2c144fc3 788 * @return string the HTML code to to inside the <head> tag.
b2330db6 789 */
227255b8 790 public function get_head_code(moodle_page $page, core_renderer $renderer) {
e50b4c89
PS
791 global $CFG;
792
945f19f7
PS
793 // note: the $page and $output are not stored here because it would
794 // create circular references in memory which prevents garbage collection
227255b8 795 $this->init_requirements_data($page, $renderer);
77774f6a 796
c0467479 797 // yui3 JS and CSS is always loaded first - it is cached in browser
985d1d1d 798 $output = $this->get_yui3lib_headcode();
77774f6a
PS
799
800 // BC: load basic YUI2 for now, all yui2 things should be loaded via Y.use('yui2-oldmodulename')
985d1d1d 801 $output .= $this->get_yui2lib_code();
77774f6a
PS
802
803 // now theme CSS + custom CSS in this specific order
c0467479 804 $output .= $this->get_css_code();
77774f6a 805
e50b4c89
PS
806 // set up global YUI3 loader object - this should contain all code needed by plugins
807 // note: in JavaScript just use "YUI(M.yui.loader).use('overlay', function(Y) { .... });"
808 // this needs to be done before including any other script
9598d578
PS
809 $js = "var M = {}; M.yui = {}; ";
810 $js .= js_writer::set_variable('M.yui.loader', $this->M_yui_loader, false) . "\n";
811 $js .= js_writer::set_variable('M.cfg', $this->M_cfg, false);
e50b4c89
PS
812 $output .= html_writer::script($js);
813
814 // link our main JS file, all core stuff should be there
815 $output .= html_writer::script('', $CFG->httpswwwroot.'/lib/javascript-static.js');
816
cd9729e4
PS
817 // add variables
818 if ($this->jsinitvariables['head']) {
819 $js = '';
820 foreach ($this->jsinitvariables['head'] as $data) {
821 list($var, $value) = $data;
822 $js .= js_writer::set_variable($var, $value, true);
823 }
824 $output .= html_writer::script($js);
825 }
826
77774f6a 827 // all the other linked things from HEAD - there should be as few as possible
60409fe1
PS
828 if ($this->jsincludes['head']) {
829 foreach ($this->jsincludes['head'] as $url) {
830 $output .= html_writer::script('', $url);
831 }
832 }
77774f6a 833
77774f6a 834 // mark head sending done, it is not possible to anything there
b2330db6 835 $this->headdone = true;
77774f6a 836
b2330db6 837 return $output;
838 }
839
840 /**
2c144fc3 841 * Generate any HTML that needs to go at the start of the <body> tag.
842 *
843 * Normally, this method is called automatically by the code that prints the
844 * <head> tag. You should not normally need to call it in your own code.
b2330db6 845 *
2c144fc3 846 * @return string the HTML code to go at the start of the <body> tag.
b2330db6 847 */
848 public function get_top_of_body_code() {
f5de8ee7
PS
849 // first the skip links
850 $links = '';
851 $attributes = array('class'=>'skip');
852 foreach ($this->skiplinks as $url => $text) {
853 $attributes['href'] = '#' . $url;
854 $links .= html_writer::tag('a', $attributes, $text);
855 }
856 $output = html_writer::tag('div', array('class'=>'skiplinks'), $links) . "\n";
857
858 // then the clever trick for hiding of things not needed when JS works
859 $output .= html_writer::script("document.body.className += ' jsenabled';") . "\n";
b2330db6 860 $this->topofbodydone = true;
861 return $output;
862 }
863
864 /**
865 * Generate any HTML that needs to go at the end of the page.
866 *
2c144fc3 867 * Normally, this method is called automatically by the code that prints the
868 * page footer. You should not normally need to call it in your own code.
869 *
b2330db6 870 * @return string the HTML code to to at the end of the page.
871 */
872 public function get_end_code() {
9d897331 873 global $CFG;
e50b4c89
PS
874 // add other requested modules
875 $output = $this->get_extra_modules_code();
c8870e68 876
e50b4c89
PS
877 // add missing YUI2 YUI - to be removed once we convert everything to YUI3!
878 $output .= $this->get_yui2lib_code();
c8870e68 879
60409fe1
PS
880 // all the other linked scripts - there should be as few as possible
881 if ($this->jsincludes['footer']) {
882 foreach ($this->jsincludes['footer'] as $url) {
883 $output .= html_writer::script('', $url);
884 }
885 }
b2330db6 886
cd9729e4 887 // add all needed strings
cf615522 888 if (!empty($this->stringsforjs)) {
cd9729e4
PS
889 $output .= html_writer::script(js_writer::set_variable('mstr', $this->stringsforjs));
890 }
891
892 // add variables
893 if ($this->jsinitvariables['footer']) {
894 $js = '';
895 foreach ($this->jsinitvariables['footer'] as $data) {
896 list($var, $value) = $data;
897 $js .= js_writer::set_variable($var, $value, true);
898 }
899 $output .= html_writer::script($js);
cf615522 900 }
901
593f9b87
PS
902 $inyuijs = $this->get_javascript_code(false);
903 $ondomreadyjs = $this->get_javascript_code(true);
08fac734 904 $jsinit = $this->get_javascript_init_code();
227255b8 905 $handlersjs = $this->get_event_handler_code();
1ce15fda 906
227255b8
PS
907 // the global Y can be used only after it is fully loaded, that means
908 // from code executed from the following block
593f9b87 909 $js = "YUI(M.yui.loader).use('node', function(Y) {\n{$inyuijs}{$ondomreadyjs}{$jsinit}{$handlersjs}\n});";
b2330db6 910
77774f6a 911 $output .= html_writer::script($js);
b2330db6 912
913 return $output;
914 }
915
916 /**
2c144fc3 917 * @return boolean Have we already output the code in the <head> tag?
b2330db6 918 */
919 public function is_head_done() {
920 return $this->headdone;
921 }
922
923 /**
2c144fc3 924 * @return boolean Have we already output the code at the start of the <body> tag?
b2330db6 925 */
926 public function is_top_of_body_done() {
927 return $this->topofbodydone;
928 }
929}
930
931
88c5092a 932/**
933 * Returns whether ajax is enabled/allowed or not.
934 */
c2a9fc91 935function ajaxenabled($browsers = array()) {
88c5092a 936
937 global $CFG, $USER;
483f3067 938
c2a9fc91 939 if (!empty($browsers)) {
940 $valid = false;
941 foreach ($browsers as $brand => $version) {
942 if (check_browser_version($brand, $version)) {
943 $valid = true;
483f3067 944 }
c2a9fc91 945 }
483f3067 946
c2a9fc91 947 if (!$valid) {
948 return false;
949 }
950 }
483f3067 951
d499142e 952 $ie = check_browser_version('MSIE', 6.0);
953 $ff = check_browser_version('Gecko', 20051106);
954 $op = check_browser_version('Opera', 9.0);
955 $sa = check_browser_version('Safari', 412);
956
957 if (!$ie && !$ff && !$op && !$sa) {
958 /** @see http://en.wikipedia.org/wiki/User_agent */
483f3067 959 // Gecko build 20051107 is what is in Firefox 1.5.
88c5092a 960 // We still have issues with AJAX in other browsers.
961 return false;
962 }
963
2f11bfc0 964 if (!empty($CFG->enableajax) && (!empty($USER->ajax) || !isloggedin())) {
88c5092a 965 return true;
966 } else {
967 return false;
968 }
969}
9bb74178 970
35b974da 971
972/**
2469f7ea 973 * Used to create view of document to be passed to JavaScript on pageload.
974 * We use this class to pass data from PHP to JavaScript.
35b974da 975 */
4fc45e1d 976class jsportal {
9bb74178 977
0a0bb380 978 var $currentblocksection = null;
9bb74178 979 var $blocks = array();
0a0bb380 980
9bb74178 981
35b974da 982 /**
983 * Takes id of block and adds it
984 */
2469f7ea 985 function block_add($id, $hidden=false){
0a0bb380 986 $hidden_binary = 0;
9bb74178 987
988 if ($hidden) {
989 $hidden_binary = 1;
990 }
35b974da 991 $this->blocks[count($this->blocks)] = array($this->currentblocksection, $id, $hidden_binary);
0a0bb380 992 }
9bb74178 993
994
2469f7ea 995 /**
996 * Prints the JavaScript code needed to set up AJAX for the course.
997 */
998 function print_javascript($courseid, $return=false) {
ddc66060 999 global $CFG, $USER, $OUTPUT, $COURSE;
9bb74178 1000
d4df8fdc 1001 $blocksoutput = $output = '';
35b974da 1002 for ($i=0; $i<count($this->blocks); $i++) {
2469f7ea 1003 $blocksoutput .= "['".$this->blocks[$i][0]."',
1004 '".$this->blocks[$i][1]."',
1005 '".$this->blocks[$i][2]."']";
1006
1007 if ($i != (count($this->blocks) - 1)) {
35b974da 1008 $blocksoutput .= ',';
9bb74178 1009 }
1010 }
32f0b38a 1011 $output .= "<script type=\"text/javascript\">\n";
72d28452 1012 $output .= " main.portal.id = ".$courseid.";\n";
2469f7ea 1013 $output .= " main.portal.blocks = new Array(".$blocksoutput.");\n";
ddc66060 1014 $output .= " main.portal.strings['courseformat']='".$COURSE->format."';\n";
2469f7ea 1015 $output .= " main.portal.strings['wwwroot']='".$CFG->wwwroot."';\n";
f8eaeffa 1016 $output .= " main.portal.strings['marker']='".get_string('markthistopic', '', '_var_')."';\n";
1017 $output .= " main.portal.strings['marked']='".get_string('markedthistopic', '', '_var_')."';\n";
b1b4c2d7 1018 $output .= " main.portal.numsections = ".$COURSE->numsections.";\n";
f8eaeffa 1019 $output .= " main.portal.strings['hide']='".get_string('hide')."';\n";
1020 $output .= " main.portal.strings['hidesection']='".get_string('hidesection', '', '_var_')."';\n";
1021 $output .= " main.portal.strings['show']='".get_string('show')."';\n";
1022 $output .= " main.portal.strings['delete']='".get_string('delete')."';\n";
d2a11d46 1023 $output .= " main.portal.strings['move']='".get_string('move')."';\n";
f8eaeffa 1024 $output .= " main.portal.strings['movesection']='".get_string('movesection', '', '_var_')."';\n";
d2a11d46 1025 $output .= " main.portal.strings['moveleft']='".get_string('moveleft')."';\n";
1026 $output .= " main.portal.strings['moveright']='".get_string('moveright')."';\n";
4fc45e1d 1027 $output .= " main.portal.strings['update']='".get_string('update')."';\n";
d2a11d46 1028 $output .= " main.portal.strings['groupsnone']='".get_string('groupsnone')."';\n";
1029 $output .= " main.portal.strings['groupsseparate']='".get_string('groupsseparate')."';\n";
1030 $output .= " main.portal.strings['groupsvisible']='".get_string('groupsvisible')."';\n";
1031 $output .= " main.portal.strings['clicktochange']='".get_string('clicktochange')."';\n";
2469f7ea 1032 $output .= " main.portal.strings['deletecheck']='".get_string('deletecheck','','_var_')."';\n";
1033 $output .= " main.portal.strings['resource']='".get_string('resource')."';\n";
1034 $output .= " main.portal.strings['activity']='".get_string('activity')."';\n";
d4a1fcaf 1035 $output .= " main.portal.strings['sesskey']='".sesskey()."';\n";
b5d0cafc
PS
1036 $output .= " main.portal.icons['spacerimg']='".$OUTPUT->pix_url('spaces')."';\n";
1037 $output .= " main.portal.icons['marker']='".$OUTPUT->pix_url('i/marker')."';\n";
1038 $output .= " main.portal.icons['ihide']='".$OUTPUT->pix_url('i/hide')."';\n";
1039 $output .= " main.portal.icons['move_2d']='".$OUTPUT->pix_url('i/move_2d')."';\n";
1040 $output .= " main.portal.icons['show']='".$OUTPUT->pix_url('t/show')."';\n";
1041 $output .= " main.portal.icons['hide']='".$OUTPUT->pix_url('t/hide')."';\n";
1042 $output .= " main.portal.icons['delete']='".$OUTPUT->pix_url('t/delete')."';\n";
1043 $output .= " main.portal.icons['groupn']='".$OUTPUT->pix_url('t/groupn')."';\n";
1044 $output .= " main.portal.icons['groups']='".$OUTPUT->pix_url('t/groups')."';\n";
1045 $output .= " main.portal.icons['groupv']='".$OUTPUT->pix_url('t/groupv')."';\n";
ddedf979 1046 if (right_to_left()) {
b5d0cafc
PS
1047 $output .= " main.portal.icons['backwards']='".$OUTPUT->pix_url('t/right')."';\n";
1048 $output .= " main.portal.icons['forwards']='".$OUTPUT->pix_url('t/left')."';\n";
ddedf979 1049 } else {
b5d0cafc
PS
1050 $output .= " main.portal.icons['backwards']='".$OUTPUT->pix_url('t/left')."';\n";
1051 $output .= " main.portal.icons['forwards']='".$OUTPUT->pix_url('t/right')."';\n";
ddedf979 1052 }
1053
2469f7ea 1054 $output .= " onloadobj.load();\n";
4fc45e1d 1055 $output .= " main.process_blocks();\n";
35b974da 1056 $output .= "</script>";
2469f7ea 1057 if ($return) {
1058 return $output;
1059 } else {
1060 echo $output;
1061 }
0a0bb380 1062 }
9bb74178 1063
2469f7ea 1064}