MDL-25886 method to convert aliased fields returned by user_picture::fields() back...
[moodle.git] / lib / outputcomponents.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/>.
18 /**
19  * Classes representing HTML elements, used by $OUTPUT methods
20  *
21  * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
22  * for an overview.
23  *
24  * @package    core
25  * @subpackage lib
26  * @copyright  2009 Tim Hunt
27  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28  */
30 defined('MOODLE_INTERNAL') || die();
32 /**
33  * Interface marking other classes as suitable for renderer_base::render()
34  * @author 2010 Petr Skoda (skodak) info@skodak.org
35  */
36 interface renderable {
37     // intentionally empty
38 }
40 /**
41  * Data structure representing a file picker.
42  *
43  * @copyright 2010 Dongsheng Cai
44  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45  * @since     Moodle 2.0
46  */
47 class file_picker implements renderable {
48     public $options;
49     public function __construct(stdClass $options) {
50         global $CFG, $USER, $PAGE;
51         require_once($CFG->dirroot. '/repository/lib.php');
52         $defaults = array(
53             'accepted_types'=>'*',
54             'return_types'=>FILE_INTERNAL,
55             'env' => 'filepicker',
56             'client_id' => uniqid(),
57             'itemid' => 0,
58             'maxbytes'=>-1,
59             'maxfiles'=>1,
60         );
61         foreach ($defaults as $key=>$value) {
62             if (empty($options->$key)) {
63                 $options->$key = $value;
64             }
65         }
67         $options->currentfile = '';
68         if (!empty($options->itemid)) {
69             $fs = get_file_storage();
70             $usercontext = get_context_instance(CONTEXT_USER, $USER->id);
71             if (empty($options->filename)) {
72                 if ($files = $fs->get_area_files($usercontext->id, 'user', 'draft', $options->itemid, 'id DESC', false)) {
73                     $file = reset($files);
74                 }
75             } else {
76                 $file = $fs->get_file($usercontext->id, 'user', 'draft', $options->itemid, $options->filepath, $options->filename);
77             }
78             if (!empty($file)) {
79                 $options->currentfile = html_writer::link(moodle_url::make_draftfile_url($file->get_itemid(), $file->get_filepath(), $file->get_filename()), $file->get_filename());
80             }
81         }
83         // initilise options, getting files in root path
84         $this->options = initialise_filepicker($options);
86         // copying other options
87         foreach ($options as $name=>$value) {
88             if (!isset($this->options->$name)) {
89                 $this->options->$name = $value;
90             }
91         }
92     }
93 }
95 /**
96  * Data structure representing a user picture.
97  *
98  * @copyright 2009 Nicolas Connault, 2010 Petr Skoda
99  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
100  * @since     Moodle 2.0
101  */
102 class user_picture implements renderable {
103     /**
104      * @var array List of mandatory fields in user record here. (do not include TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE)
105      */
106     protected static $fields = array('id', 'picture', 'firstname', 'lastname', 'imagealt', 'email');
108     /**
109      * @var object $user A user object with at least fields all columns specified in $fields array constant set.
110      */
111     public $user;
112     /**
113      * @var int $courseid The course id. Used when constructing the link to the user's profile,
114      * page course id used if not specified.
115      */
116     public $courseid;
117     /**
118      * @var bool $link add course profile link to image
119      */
120     public $link = true;
121     /**
122      * @var int $size Size in pixels. Special values are (true/1 = 100px) and (false/0 = 35px) for backward compatibility
123      */
124     public $size = 35;
125     /**
126      * @var boolean $alttext add non-blank alt-text to the image.
127      * Default true, set to false when image alt just duplicates text in screenreaders.
128      */
129     public $alttext = true;
130     /**
131      * @var boolean $popup Whether or not to open the link in a popup window.
132      */
133     public $popup = false;
134     /**
135      * @var string Image class attribute
136      */
137     public $class = 'userpicture';
139     /**
140      * User picture constructor.
141      *
142      * @param object $user user record with at least id, picture, imagealt, firstname and lastname set.
143      * @param array $options such as link, size, link, ...
144      */
145     public function __construct(stdClass $user) {
146         global $DB;
148         if (empty($user->id)) {
149             throw new coding_exception('User id is required when printing user avatar image.');
150         }
152         // only touch the DB if we are missing data and complain loudly...
153         $needrec = false;
154         foreach (self::$fields as $field) {
155             if (!array_key_exists($field, $user)) {
156                 $needrec = true;
157                 debugging('Missing '.$field.' property in $user object, this is a performance problem that needs to be fixed by a developer. '
158                           .'Please use user_picture::fields() to get the full list of required fields.', DEBUG_DEVELOPER);
159                 break;
160             }
161         }
163         if ($needrec) {
164             $this->user = $DB->get_record('user', array('id'=>$user->id), self::fields(), MUST_EXIST);
165         } else {
166             $this->user = clone($user);
167         }
168     }
170     /**
171      * Returns a list of required user fields, useful when fetching required user info from db.
172      *
173      * In some cases we have to fetch the user data together with some other information,
174      * the idalias is useful there because the id would otherwise override the main
175      * id of the result record. Please note it has to be converted back to id before rendering.
176      *
177      * @param string $tableprefix name of database table prefix in query
178      * @param array $extrafields extra fields to be included in result (do not include TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE)
179      * @param string $idalias alias of id field
180      * @param string $fieldprefix prefix to add to all columns in their aliases, does not apply to 'id'
181      * @return string
182      */
183     public static function fields($tableprefix = '', array $extrafields = NULL, $idalias = 'id', $fieldprefix = '') {
184         if (!$tableprefix and !$extrafields and !$idalias) {
185             return implode(',', self::$fields);
186         }
187         if ($tableprefix) {
188             $tableprefix .= '.';
189         }
190         $fields = array();
191         foreach (self::$fields as $field) {
192             if ($field === 'id' and $idalias and $idalias !== 'id') {
193                 $fields[$field] = "$tableprefix$field AS $idalias";
194             } else {
195                 if ($fieldprefix and $field !== 'id') {
196                     $fields[$field] = "$tableprefix$field AS $fieldprefix$field";
197                 } else {
198                     $fields[$field] = "$tableprefix$field";
199                 }
200             }
201         }
202         // add extra fields if not already there
203         if ($extrafields) {
204             foreach ($extrafields as $e) {
205                 if ($e === 'id' or isset($fields[$e])) {
206                     continue;
207                 }
208                 if ($fieldprefix) {
209                     $fields[$e] = "$tableprefix$e AS $fieldprefix$e";
210                 } else {
211                     $fields[$e] = "$tableprefix$e";
212                 }
213             }
214         }
215         return implode(',', $fields);
216     }
218     /**
219      * Extract the aliased user fields from a given record
220      *
221      * Given a record that was previously obtained using {@link self::fields()} with aliases,
222      * this method extracts user related unaliased fields.
223      *
224      * @param stdClass $record containing user picture fields
225      * @param array $extrafields extra fields included in the $record
226      * @param string $idalias alias of the id field
227      * @param string $fieldprefix prefix added to all columns in their aliases, does not apply to 'id'
228      * @return stdClass object with unaliased user fields
229      */
230     public static function unalias(stdClass $record, array $extrafields=null, $idalias='id', $fieldprefix='') {
232         if (empty($idalias)) {
233             $idalias = 'id';
234         }
236         $return = new stdClass();
238         foreach (self::$fields as $field) {
239             if ($field === 'id') {
240                 if (isset($record->{$idalias})) {
241                     $return->id = $record->{$idalias};
242                 }
243             } else {
244                 if (isset($record->{$fieldprefix.$field})) {
245                     $return->{$field} = $record->{$fieldprefix.$field};
246                 }
247             }
248         }
249         // add extra fields if not already there
250         if ($extrafields) {
251             foreach ($extrafields as $e) {
252                 if ($e === 'id' or isset($return->{$e})) {
253                     continue;
254                 }
255                 $return->{$e} = $record->{$fieldprefix.$e};
256             }
257         }
259         return $return;
260     }
263 /**
264  * Data structure representing a help icon.
265  *
266  * @copyright 2009 Nicolas Connault, 2010 Petr Skoda
267  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
268  * @since     Moodle 2.0
269  */
270 class old_help_icon implements renderable {
271     /**
272      * @var string $helpidentifier lang pack identifier
273      */
274     public $helpidentifier;
275     /**
276      * @var string $title A descriptive text for title tooltip
277      */
278     public $title = null;
279     /**
280      * @var string $component Component name, the same as in get_string()
281      */
282     public $component = 'moodle';
283     /**
284      * @var string $linktext Extra descriptive text next to the icon
285      */
286     public $linktext = null;
288     /**
289      * Constructor: sets up the other components in case they are needed
290      * @param string $helpidentifier  The keyword that defines a help page
291      * @param string $title A descriptive text for accessibility only
292      * @param string $component
293      * @param bool $linktext add extra text to icon
294      * @return void
295      */
296     public function __construct($helpidentifier, $title, $component = 'moodle') {
297         if (empty($title)) {
298             throw new coding_exception('A help_icon object requires a $text parameter');
299         }
300         if (empty($helpidentifier)) {
301             throw new coding_exception('A help_icon object requires a $helpidentifier parameter');
302         }
304         $this->helpidentifier  = $helpidentifier;
305         $this->title           = $title;
306         $this->component       = $component;
307     }
310 /**
311  * Data structure representing a help icon.
312  *
313  * @copyright 2010 Petr Skoda (info@skodak.org)
314  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
315  * @since     Moodle 2.0
316  */
317 class help_icon implements renderable {
318     /**
319      * @var string $identifier lang pack identifier (without the "_help" suffix),
320      *    both get_string($identifier, $component) and get_string($identifier.'_help', $component)
321      *    must exist.
322      */
323     public $identifier;
324     /**
325      * @var string $component Component name, the same as in get_string()
326      */
327     public $component;
328     /**
329      * @var string $linktext Extra descriptive text next to the icon
330      */
331     public $linktext = null;
333     /**
334      * Constructor
335      * @param string $identifier  string for help page title,
336      *  string with _help suffix is used for the actual help text.
337      *  string with _link suffix is used to create a link to further info (if it exists)
338      * @param string $component
339      */
340     public function __construct($identifier, $component) {
341         $this->identifier = $identifier;
342         $this->component  = $component;
343     }
345     /**
346      * Verifies that both help strings exists, shows debug warnings if not
347      */
348     public function diag_strings() {
349         $sm = get_string_manager();
350         if (!$sm->string_exists($this->identifier, $this->component)) {
351             debugging("Help title string does not exist: [$this->identifier, $this->component]");
352         }
353         if (!$sm->string_exists($this->identifier.'_help', $this->component)) {
354             debugging("Help contents string does not exist: [{$this->identifier}_help, $this->component]");
355         }
356     }
360 /**
361  * Data structure representing an icon.
362  *
363  * @copyright 2010 Petr Skoda
364  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
365  * @since     Moodle 2.0
366  */
367 class pix_icon implements renderable {
368     var $pix;
369     var $component;
370     var $attributes = array();
372     /**
373      * Constructor
374      * @param string $pix short icon name
375      * @param string $component component name
376      * @param array $attributes html attributes
377      */
378     public function __construct($pix, $alt, $component='moodle', array $attributes = null) {
379         $this->pix        = $pix;
380         $this->component  = $component;
381         $this->attributes = (array)$attributes;
383         $this->attributes['alt'] = $alt;
384         if (empty($this->attributes['class'])) {
385             $this->attributes['class'] = 'smallicon';
386         }
387         if (!isset($this->attributes['title'])) {
388             $this->attributes['title'] = $this->attributes['alt'];
389         }
390     }
393 /**
394  * Data structure representing an emoticon image
395  *
396  * @since     Moodle 2.0
397  */
398 class pix_emoticon extends pix_icon implements renderable {
400     /**
401      * Constructor
402      * @param string $pix short icon name
403      * @param string $alt alternative text
404      * @param string $component emoticon image provider
405      * @param array $attributes explicit HTML attributes
406      */
407     public function __construct($pix, $alt, $component = 'moodle', array $attributes = array()) {
408         if (empty($attributes['class'])) {
409             $attributes['class'] = 'emoticon';
410         }
411         parent::__construct($pix, $alt, $component, $attributes);
412     }
415 /**
416  * Data structure representing a simple form with only one button.
417  *
418  * @copyright 2009 Petr Skoda
419  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
420  * @since     Moodle 2.0
421  */
422 class single_button implements renderable {
423     /**
424      * Target url
425      * @var moodle_url
426      */
427     var $url;
428     /**
429      * Button label
430      * @var string
431      */
432     var $label;
433     /**
434      * Form submit method
435      * @var string post or get
436      */
437     var $method = 'post';
438     /**
439      * Wrapping div class
440      * @var string
441      * */
442     var $class = 'singlebutton';
443     /**
444      * True if button disabled, false if normal
445      * @var boolean
446      */
447     var $disabled = false;
448     /**
449      * Button tooltip
450      * @var string
451      */
452     var $tooltip = null;
453     /**
454      * Form id
455      * @var string
456      */
457     var $formid;
458     /**
459      * List of attached actions
460      * @var array of component_action
461      */
462     var $actions = array();
464     /**
465      * Constructor
466      * @param string|moodle_url $url
467      * @param string $label button text
468      * @param string $method get or post submit method
469      */
470     public function __construct(moodle_url $url, $label, $method='post') {
471         $this->url    = clone($url);
472         $this->label  = $label;
473         $this->method = $method;
474     }
476     /**
477      * Shortcut for adding a JS confirm dialog when the button is clicked.
478      * The message must be a yes/no question.
479      * @param string $message The yes/no confirmation question. If "Yes" is clicked, the original action will occur.
480      * @return void
481      */
482     public function add_confirm_action($confirmmessage) {
483         $this->add_action(new component_action('click', 'M.util.show_confirm_dialog', array('message' => $confirmmessage)));
484     }
486     /**
487      * Add action to the button.
488      * @param component_action $action
489      * @return void
490      */
491     public function add_action(component_action $action) {
492         $this->actions[] = $action;
493     }
497 /**
498  * Simple form with just one select field that gets submitted automatically.
499  * If JS not enabled small go button is printed too.
500  *
501  * @copyright 2009 Petr Skoda
502  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
503  * @since     Moodle 2.0
504  */
505 class single_select implements renderable {
506     /**
507      * Target url - includes hidden fields
508      * @var moodle_url
509      */
510     var $url;
511     /**
512      * Name of the select element.
513      * @var string
514      */
515     var $name;
516     /**
517      * @var array $options associative array value=>label ex.:
518      *              array(1=>'One, 2=>Two)
519      *              it is also possible to specify optgroup as complex label array ex.:
520      *                array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
521      *                array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
522      */
523     var $options;
524     /**
525      * Selected option
526      * @var string
527      */
528     var $selected;
529     /**
530      * Nothing selected
531      * @var array
532      */
533     var $nothing;
534     /**
535      * Extra select field attributes
536      * @var array
537      */
538     var $attributes = array();
539     /**
540      * Button label
541      * @var string
542      */
543     var $label = '';
544     /**
545      * Form submit method
546      * @var string post or get
547      */
548     var $method = 'get';
549     /**
550      * Wrapping div class
551      * @var string
552      * */
553     var $class = 'singleselect';
554     /**
555      * True if button disabled, false if normal
556      * @var boolean
557      */
558     var $disabled = false;
559     /**
560      * Button tooltip
561      * @var string
562      */
563     var $tooltip = null;
564     /**
565      * Form id
566      * @var string
567      */
568     var $formid = null;
569     /**
570      * List of attached actions
571      * @var array of component_action
572      */
573     var $helpicon = null;
574     /**
575      * Constructor
576      * @param moodle_url $url form action target, includes hidden fields
577      * @param string $name name of selection field - the changing parameter in url
578      * @param array $options list of options
579      * @param string $selected selected element
580      * @param array $nothing
581      * @param string $formid
582      */
583     public function __construct(moodle_url $url, $name, array $options, $selected='', $nothing=array(''=>'choosedots'), $formid=null) {
584         $this->url      = $url;
585         $this->name     = $name;
586         $this->options  = $options;
587         $this->selected = $selected;
588         $this->nothing  = $nothing;
589         $this->formid   = $formid;
590     }
592     /**
593      * Shortcut for adding a JS confirm dialog when the button is clicked.
594      * The message must be a yes/no question.
595      * @param string $message The yes/no confirmation question. If "Yes" is clicked, the original action will occur.
596      * @return void
597      */
598     public function add_confirm_action($confirmmessage) {
599         $this->add_action(new component_action('submit', 'M.util.show_confirm_dialog', array('message' => $confirmmessage)));
600     }
602     /**
603      * Add action to the button.
604      * @param component_action $action
605      * @return void
606      */
607     public function add_action(component_action $action) {
608         $this->actions[] = $action;
609     }
611     /**
612      * Adds help icon.
613      * @param string $page  The keyword that defines a help page
614      * @param string $title A descriptive text for accessibility only
615      * @param string $component
616      * @param bool $linktext add extra text to icon
617      * @return void
618      */
619     public function set_old_help_icon($helppage, $title, $component = 'moodle') {
620         $this->helpicon = new old_help_icon($helppage, $title, $component);
621     }
623     /**
624      * Adds help icon.
625      * @param string $identifier The keyword that defines a help page
626      * @param string $component
627      * @param bool $linktext add extra text to icon
628      * @return void
629      */
630     public function set_help_icon($identifier, $component = 'moodle') {
631         $this->helpicon = new help_icon($identifier, $component);
632     }
634     /**
635      * Sets select's label
636      * @param string $label
637      * @return void
638      */
639     public function set_label($label) {
640         $this->label = $label;
641     }
645 /**
646  * Simple URL selection widget description.
647  * @copyright 2009 Petr Skoda
648  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
649  * @since     Moodle 2.0
650  */
651 class url_select implements renderable {
652     /**
653      * @var array $urls associative array value=>label ex.:
654      *              array(1=>'One, 2=>Two)
655      *              it is also possible to specify optgroup as complex label array ex.:
656      *                array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
657      *                array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
658      */
659     var $urls;
660     /**
661      * Selected option
662      * @var string
663      */
664     var $selected;
665     /**
666      * Nothing selected
667      * @var array
668      */
669     var $nothing;
670     /**
671      * Extra select field attributes
672      * @var array
673      */
674     var $attributes = array();
675     /**
676      * Button label
677      * @var string
678      */
679     var $label = '';
680     /**
681      * Wrapping div class
682      * @var string
683      * */
684     var $class = 'urlselect';
685     /**
686      * True if button disabled, false if normal
687      * @var boolean
688      */
689     var $disabled = false;
690     /**
691      * Button tooltip
692      * @var string
693      */
694     var $tooltip = null;
695     /**
696      * Form id
697      * @var string
698      */
699     var $formid = null;
700     /**
701      * List of attached actions
702      * @var array of component_action
703      */
704     var $helpicon = null;
705     /**
706      * @var string If set, makes button visible with given name for button
707      */
708     var $showbutton = null;
709     /**
710      * Constructor
711      * @param array $urls list of options
712      * @param string $selected selected element
713      * @param array $nothing
714      * @param string $formid
715      * @param string $showbutton Set to text of button if it should be visible
716      *   or null if it should be hidden (hidden version always has text 'go')
717      */
718     public function __construct(array $urls, $selected='', $nothing=array(''=>'choosedots'),
719             $formid=null, $showbutton=null) {
720         $this->urls       = $urls;
721         $this->selected   = $selected;
722         $this->nothing    = $nothing;
723         $this->formid     = $formid;
724         $this->showbutton = $showbutton;
725     }
727     /**
728      * Adds help icon.
729      * @param string $page  The keyword that defines a help page
730      * @param string $title A descriptive text for accessibility only
731      * @param string $component
732      * @param bool $linktext add extra text to icon
733      * @return void
734      */
735     public function set_old_help_icon($helppage, $title, $component = 'moodle') {
736         $this->helpicon = new old_help_icon($helppage, $title, $component);
737     }
739     /**
740      * Adds help icon.
741      * @param string $identifier The keyword that defines a help page
742      * @param string $component
743      * @param bool $linktext add extra text to icon
744      * @return void
745      */
746     public function set_help_icon($identifier, $component = 'moodle') {
747         $this->helpicon = new help_icon($identifier, $component);
748     }
750     /**
751      * Sets select's label
752      * @param string $label
753      * @return void
754      */
755     public function set_label($label) {
756         $this->label = $label;
757     }
761 /**
762  * Data structure describing html link with special action attached.
763  * @copyright 2010 Petr Skoda
764  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
765  * @since     Moodle 2.0
766  */
767 class action_link implements renderable {
768     /**
769      * Href url
770      * @var moodle_url
771      */
772     var $url;
773     /**
774      * Link text
775      * @var string HTML fragment
776      */
777     var $text;
778     /**
779      * HTML attributes
780      * @var array
781      */
782     var $attributes;
783     /**
784      * List of actions attached to link
785      * @var array of component_action
786      */
787     var $actions;
789     /**
790      * Constructor
791      * @param string|moodle_url $url
792      * @param string $text HTML fragment
793      * @param component_action $action
794      * @param array $attributes associative array of html link attributes + disabled
795      */
796     public function __construct(moodle_url $url, $text, component_action $action=null, array $attributes=null) {
797         $this->url       = clone($url);
798         $this->text      = $text;
799         $this->attributes = (array)$attributes;
800         if ($action) {
801             $this->add_action($action);
802         }
803     }
805     /**
806      * Add action to the link.
807      * @param component_action $action
808      * @return void
809      */
810     public function add_action(component_action $action) {
811         $this->actions[] = $action;
812     }
814     public function add_class($class) {
815         if (empty($this->attributes['class'])) {
816             $this->attributes['class'] = $class;
817         } else {
818             $this->attributes['class'] .= ' ' . $class;
819         }
820     }
823 // ==== HTML writer and helper classes, will be probably moved elsewhere ======
825 /**
826  * Simple html output class
827  * @copyright 2009 Tim Hunt, 2010 Petr Skoda
828  */
829 class html_writer {
830     /**
831      * Outputs a tag with attributes and contents
832      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
833      * @param string $contents What goes between the opening and closing tags
834      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
835      * @return string HTML fragment
836      */
837     public static function tag($tagname, $contents, array $attributes = null) {
838         return self::start_tag($tagname, $attributes) . $contents . self::end_tag($tagname);
839     }
841     /**
842      * Outputs an opening tag with attributes
843      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
844      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
845      * @return string HTML fragment
846      */
847     public static function start_tag($tagname, array $attributes = null) {
848         return '<' . $tagname . self::attributes($attributes) . '>';
849     }
851     /**
852      * Outputs a closing tag
853      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
854      * @return string HTML fragment
855      */
856     public static function end_tag($tagname) {
857         return '</' . $tagname . '>';
858     }
860     /**
861      * Outputs an empty tag with attributes
862      * @param string $tagname The name of tag ('input', 'img', 'br' etc.)
863      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
864      * @return string HTML fragment
865      */
866     public static function empty_tag($tagname, array $attributes = null) {
867         return '<' . $tagname . self::attributes($attributes) . ' />';
868     }
870     /**
871      * Outputs a tag, but only if the contents are not empty
872      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
873      * @param string $contents What goes between the opening and closing tags
874      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
875      * @return string HTML fragment
876      */
877     public static function nonempty_tag($tagname, $contents, array $attributes = null) {
878         if ($contents === '' || is_null($contents)) {
879             return '';
880         }
881         return self::tag($tagname, $contents, $attributes);
882     }
884     /**
885      * Outputs a HTML attribute and value
886      * @param string $name The name of the attribute ('src', 'href', 'class' etc.)
887      * @param string $value The value of the attribute. The value will be escaped with {@link s()}
888      * @return string HTML fragment
889      */
890     public static function attribute($name, $value) {
891         if (is_array($value)) {
892             debugging("Passed an array for the HTML attribute $name", DEBUG_DEVELOPER);
893         }
894         if ($value instanceof moodle_url) {
895             return ' ' . $name . '="' . $value->out() . '"';
896         }
898         // special case, we do not want these in output
899         if ($value === null) {
900             return '';
901         }
903         // no sloppy trimming here!
904         return ' ' . $name . '="' . s($value) . '"';
905     }
907     /**
908      * Outputs a list of HTML attributes and values
909      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
910      *       The values will be escaped with {@link s()}
911      * @return string HTML fragment
912      */
913     public static function attributes(array $attributes = null) {
914         $attributes = (array)$attributes;
915         $output = '';
916         foreach ($attributes as $name => $value) {
917             $output .= self::attribute($name, $value);
918         }
919         return $output;
920     }
922     /**
923      * Generates random html element id.
924      * @param string $base
925      * @return string
926      */
927     public static function random_id($base='random') {
928         return uniqid($base);
929     }
931     /**
932      * Generates a simple html link
933      * @param string|moodle_url $url
934      * @param string $text link txt
935      * @param array $attributes extra html attributes
936      * @return string HTML fragment
937      */
938     public static function link($url, $text, array $attributes = null) {
939         $attributes = (array)$attributes;
940         $attributes['href']  = $url;
941         return self::tag('a', $text, $attributes);
942     }
944     /**
945      * generates a simple checkbox with optional label
946      * @param string $name
947      * @param string $value
948      * @param bool $checked
949      * @param string $label
950      * @param array $attributes
951      * @return string html fragment
952      */
953     public static function checkbox($name, $value, $checked = true, $label = '', array $attributes = null) {
954         $attributes = (array)$attributes;
955         $output = '';
957         if ($label !== '' and !is_null($label)) {
958             if (empty($attributes['id'])) {
959                 $attributes['id'] = self::random_id('checkbox_');
960             }
961         }
962         $attributes['type']    = 'checkbox';
963         $attributes['value']   = $value;
964         $attributes['name']    = $name;
965         $attributes['checked'] = $checked ? 'checked' : null;
967         $output .= self::empty_tag('input', $attributes);
969         if ($label !== '' and !is_null($label)) {
970             $output .= self::tag('label', $label, array('for'=>$attributes['id']));
971         }
973         return $output;
974     }
976     /**
977      * Generates a simple select yes/no form field
978      * @param string $name name of select element
979      * @param bool $selected
980      * @param array $attributes - html select element attributes
981      * @return string HRML fragment
982      */
983     public static function select_yes_no($name, $selected=true, array $attributes = null) {
984         $options = array('1'=>get_string('yes'), '0'=>get_string('no'));
985         return self::select($options, $name, $selected, null, $attributes);
986     }
988     /**
989      * Generates a simple select form field
990      * @param array $options associative array value=>label ex.:
991      *                array(1=>'One, 2=>Two)
992      *              it is also possible to specify optgroup as complex label array ex.:
993      *                array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
994      *                array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
995      * @param string $name name of select element
996      * @param string|array $selected value or array of values depending on multiple attribute
997      * @param array|bool $nothing, add nothing selected option, or false of not added
998      * @param array $attributes - html select element attributes
999      * @return string HTML fragment
1000      */
1001     public static function select(array $options, $name, $selected = '', $nothing = array(''=>'choosedots'), array $attributes = null) {
1002         $attributes = (array)$attributes;
1003         if (is_array($nothing)) {
1004             foreach ($nothing as $k=>$v) {
1005                 if ($v === 'choose' or $v === 'choosedots') {
1006                     $nothing[$k] = get_string('choosedots');
1007                 }
1008             }
1009             $options = $nothing + $options; // keep keys, do not override
1011         } else if (is_string($nothing) and $nothing !== '') {
1012             // BC
1013             $options = array(''=>$nothing) + $options;
1014         }
1016         // we may accept more values if multiple attribute specified
1017         $selected = (array)$selected;
1018         foreach ($selected as $k=>$v) {
1019             $selected[$k] = (string)$v;
1020         }
1022         if (!isset($attributes['id'])) {
1023             $id = 'menu'.$name;
1024             // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading
1025             $id = str_replace('[', '', $id);
1026             $id = str_replace(']', '', $id);
1027             $attributes['id'] = $id;
1028         }
1030         if (!isset($attributes['class'])) {
1031             $class = 'menu'.$name;
1032             // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading
1033             $class = str_replace('[', '', $class);
1034             $class = str_replace(']', '', $class);
1035             $attributes['class'] = $class;
1036         }
1037         $attributes['class'] = 'select ' . $attributes['class']; /// Add 'select' selector always
1039         $attributes['name'] = $name;
1041         $output = '';
1042         foreach ($options as $value=>$label) {
1043             if (is_array($label)) {
1044                 // ignore key, it just has to be unique
1045                 $output .= self::select_optgroup(key($label), current($label), $selected);
1046             } else {
1047                 $output .= self::select_option($label, $value, $selected);
1048             }
1049         }
1050         return self::tag('select', $output, $attributes);
1051     }
1053     private static function select_option($label, $value, array $selected) {
1054         $attributes = array();
1055         $value = (string)$value;
1056         if (in_array($value, $selected, true)) {
1057             $attributes['selected'] = 'selected';
1058         }
1059         $attributes['value'] = $value;
1060         return self::tag('option', $label, $attributes);
1061     }
1063     private static function select_optgroup($groupname, $options, array $selected) {
1064         if (empty($options)) {
1065             return '';
1066         }
1067         $attributes = array('label'=>$groupname);
1068         $output = '';
1069         foreach ($options as $value=>$label) {
1070             $output .= self::select_option($label, $value, $selected);
1071         }
1072         return self::tag('optgroup', $output, $attributes);
1073     }
1075     /**
1076      * This is a shortcut for making an hour selector menu.
1077      * @param string $type The type of selector (years, months, days, hours, minutes)
1078      * @param string $name fieldname
1079      * @param int $currenttime A default timestamp in GMT
1080      * @param int $step minute spacing
1081      * @param array $attributes - html select element attributes
1082      * @return HTML fragment
1083      */
1084     public static function select_time($type, $name, $currenttime=0, $step=5, array $attributes=null) {
1085         if (!$currenttime) {
1086             $currenttime = time();
1087         }
1088         $currentdate = usergetdate($currenttime);
1089         $userdatetype = $type;
1090         $timeunits = array();
1092         switch ($type) {
1093             case 'years':
1094                 for ($i=1970; $i<=2020; $i++) {
1095                     $timeunits[$i] = $i;
1096                 }
1097                 $userdatetype = 'year';
1098                 break;
1099             case 'months':
1100                 for ($i=1; $i<=12; $i++) {
1101                     $timeunits[$i] = userdate(gmmktime(12,0,0,$i,15,2000), "%B");
1102                 }
1103                 $userdatetype = 'month';
1104                 $currentdate['month'] = $currentdate['mon'];
1105                 break;
1106             case 'days':
1107                 for ($i=1; $i<=31; $i++) {
1108                     $timeunits[$i] = $i;
1109                 }
1110                 $userdatetype = 'mday';
1111                 break;
1112             case 'hours':
1113                 for ($i=0; $i<=23; $i++) {
1114                     $timeunits[$i] = sprintf("%02d",$i);
1115                 }
1116                 break;
1117             case 'minutes':
1118                 if ($step != 1) {
1119                     $currentdate['minutes'] = ceil($currentdate['minutes']/$step)*$step;
1120                 }
1122                 for ($i=0; $i<=59; $i+=$step) {
1123                     $timeunits[$i] = sprintf("%02d",$i);
1124                 }
1125                 break;
1126             default:
1127                 throw new coding_exception("Time type $type is not supported by html_writer::select_time().");
1128         }
1130         if (empty($attributes['id'])) {
1131             $attributes['id'] = self::random_id('ts_');
1132         }
1133         $timerselector = self::select($timeunits, $name, $currentdate[$userdatetype], null, array('id'=>$attributes['id']));
1134         $label = self::tag('label', get_string(substr($type, 0, -1), 'form'), array('for'=>$attributes['id'], 'class'=>'accesshide'));
1136         return $label.$timerselector;
1137     }
1139     /**
1140      * Shortcut for quick making of lists
1141      * @param array $items
1142      * @param string $tag ul or ol
1143      * @param array $attributes
1144      * @return string
1145      */
1146     public static function alist(array $items, array $attributes = null, $tag = 'ul') {
1147         //note: 'list' is a reserved keyword ;-)
1149         $output = '';
1151         foreach ($items as $item) {
1152             $output .= html_writer::start_tag('li') . "\n";
1153             $output .= $item . "\n";
1154             $output .= html_writer::end_tag('li') . "\n";
1155         }
1157         return html_writer::tag($tag, $output, $attributes);
1158     }
1160     /**
1161      * Returns hidden input fields created from url parameters.
1162      * @param moodle_url $url
1163      * @param array $exclude list of excluded parameters
1164      * @return string HTML fragment
1165      */
1166     public static function input_hidden_params(moodle_url $url, array $exclude = null) {
1167         $exclude = (array)$exclude;
1168         $params = $url->params();
1169         foreach ($exclude as $key) {
1170             unset($params[$key]);
1171         }
1173         $output = '';
1174         foreach ($params as $key => $value) {
1175             $attributes = array('type'=>'hidden', 'name'=>$key, 'value'=>$value);
1176             $output .= self::empty_tag('input', $attributes)."\n";
1177         }
1178         return $output;
1179     }
1181     /**
1182      * Generate a script tag containing the the specified code.
1183      *
1184      * @param string $js the JavaScript code
1185          * @param moodle_url|string optional url of the external script, $code ignored if specified
1186      * @return string HTML, the code wrapped in <script> tags.
1187      */
1188     public static function script($jscode, $url=null) {
1189         if ($jscode) {
1190             $attributes = array('type'=>'text/javascript');
1191             return self::tag('script', "\n//<![CDATA[\n$jscode\n//]]>\n", $attributes) . "\n";
1193         } else if ($url) {
1194             $attributes = array('type'=>'text/javascript', 'src'=>$url);
1195             return self::tag('script', '', $attributes) . "\n";
1197         } else {
1198             return '';
1199         }
1200     }
1202     /**
1203      * Renders HTML table
1204      *
1205      * This method may modify the passed instance by adding some default properties if they are not set yet.
1206      * If this is not what you want, you should make a full clone of your data before passing them to this
1207      * method. In most cases this is not an issue at all so we do not clone by default for performance
1208      * and memory consumption reasons.
1209      *
1210      * @param html_table $table data to be rendered
1211      * @return string HTML code
1212      */
1213     public static function table(html_table $table) {
1214         // prepare table data and populate missing properties with reasonable defaults
1215         if (!empty($table->align)) {
1216             foreach ($table->align as $key => $aa) {
1217                 if ($aa) {
1218                     $table->align[$key] = 'text-align:'. fix_align_rtl($aa) .';';  // Fix for RTL languages
1219                 } else {
1220                     $table->align[$key] = null;
1221                 }
1222             }
1223         }
1224         if (!empty($table->size)) {
1225             foreach ($table->size as $key => $ss) {
1226                 if ($ss) {
1227                     $table->size[$key] = 'width:'. $ss .';';
1228                 } else {
1229                     $table->size[$key] = null;
1230                 }
1231             }
1232         }
1233         if (!empty($table->wrap)) {
1234             foreach ($table->wrap as $key => $ww) {
1235                 if ($ww) {
1236                     $table->wrap[$key] = 'white-space:nowrap;';
1237                 } else {
1238                     $table->wrap[$key] = '';
1239                 }
1240             }
1241         }
1242         if (!empty($table->head)) {
1243             foreach ($table->head as $key => $val) {
1244                 if (!isset($table->align[$key])) {
1245                     $table->align[$key] = null;
1246                 }
1247                 if (!isset($table->size[$key])) {
1248                     $table->size[$key] = null;
1249                 }
1250                 if (!isset($table->wrap[$key])) {
1251                     $table->wrap[$key] = null;
1252                 }
1254             }
1255         }
1256         if (empty($table->attributes['class'])) {
1257             $table->attributes['class'] = 'generaltable';
1258         }
1259         if (!empty($table->tablealign)) {
1260             $table->attributes['class'] .= ' boxalign' . $table->tablealign;
1261         }
1263         // explicitly assigned properties override those defined via $table->attributes
1264         $table->attributes['class'] = trim($table->attributes['class']);
1265         $attributes = array_merge($table->attributes, array(
1266                 'id'            => $table->id,
1267                 'width'         => $table->width,
1268                 'summary'       => $table->summary,
1269                 'cellpadding'   => $table->cellpadding,
1270                 'cellspacing'   => $table->cellspacing,
1271             ));
1272         $output = html_writer::start_tag('table', $attributes) . "\n";
1274         $countcols = 0;
1276         if (!empty($table->head)) {
1277             $countcols = count($table->head);
1279             $output .= html_writer::start_tag('thead', array()) . "\n";
1280             $output .= html_writer::start_tag('tr', array()) . "\n";
1281             $keys = array_keys($table->head);
1282             $lastkey = end($keys);
1284             foreach ($table->head as $key => $heading) {
1285                 // Convert plain string headings into html_table_cell objects
1286                 if (!($heading instanceof html_table_cell)) {
1287                     $headingtext = $heading;
1288                     $heading = new html_table_cell();
1289                     $heading->text = $headingtext;
1290                     $heading->header = true;
1291                 }
1293                 if ($heading->header !== false) {
1294                     $heading->header = true;
1295                 }
1297                 if ($heading->header && empty($heading->scope)) {
1298                     $heading->scope = 'col';
1299                 }
1301                 $heading->attributes['class'] .= ' header c' . $key;
1302                 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
1303                     $heading->colspan = $table->headspan[$key];
1304                     $countcols += $table->headspan[$key] - 1;
1305                 }
1307                 if ($key == $lastkey) {
1308                     $heading->attributes['class'] .= ' lastcol';
1309                 }
1310                 if (isset($table->colclasses[$key])) {
1311                     $heading->attributes['class'] .= ' ' . $table->colclasses[$key];
1312                 }
1313                 $heading->attributes['class'] = trim($heading->attributes['class']);
1314                 $attributes = array_merge($heading->attributes, array(
1315                         'style'     => $table->align[$key] . $table->size[$key] . $heading->style,
1316                         'scope'     => $heading->scope,
1317                         'colspan'   => $heading->colspan,
1318                     ));
1320                 $tagtype = 'td';
1321                 if ($heading->header === true) {
1322                     $tagtype = 'th';
1323                 }
1324                 $output .= html_writer::tag($tagtype, $heading->text, $attributes) . "\n";
1325             }
1326             $output .= html_writer::end_tag('tr') . "\n";
1327             $output .= html_writer::end_tag('thead') . "\n";
1329             if (empty($table->data)) {
1330                 // For valid XHTML strict every table must contain either a valid tr
1331                 // or a valid tbody... both of which must contain a valid td
1332                 $output .= html_writer::start_tag('tbody', array('class' => 'empty'));
1333                 $output .= html_writer::tag('tr', html_writer::tag('td', '', array('colspan'=>count($table->head))));
1334                 $output .= html_writer::end_tag('tbody');
1335             }
1336         }
1338         if (!empty($table->data)) {
1339             $oddeven    = 1;
1340             $keys       = array_keys($table->data);
1341             $lastrowkey = end($keys);
1342             $output .= html_writer::start_tag('tbody', array());
1344             foreach ($table->data as $key => $row) {
1345                 if (($row === 'hr') && ($countcols)) {
1346                     $output .= html_writer::tag('td', html_writer::tag('div', '', array('class' => 'tabledivider')), array('colspan' => $countcols));
1347                 } else {
1348                     // Convert array rows to html_table_rows and cell strings to html_table_cell objects
1349                     if (!($row instanceof html_table_row)) {
1350                         $newrow = new html_table_row();
1352                         foreach ($row as $item) {
1353                             $cell = new html_table_cell();
1354                             $cell->text = $item;
1355                             $newrow->cells[] = $cell;
1356                         }
1357                         $row = $newrow;
1358                     }
1360                     $oddeven = $oddeven ? 0 : 1;
1361                     if (isset($table->rowclasses[$key])) {
1362                         $row->attributes['class'] .= ' ' . $table->rowclasses[$key];
1363                     }
1365                     $row->attributes['class'] .= ' r' . $oddeven;
1366                     if ($key == $lastrowkey) {
1367                         $row->attributes['class'] .= ' lastrow';
1368                     }
1370                     $output .= html_writer::start_tag('tr', array('class' => trim($row->attributes['class']), 'style' => $row->style, 'id' => $row->id)) . "\n";
1371                     $keys2 = array_keys($row->cells);
1372                     $lastkey = end($keys2);
1374                     $gotlastkey = false; //flag for sanity checking
1375                     foreach ($row->cells as $key => $cell) {
1376                         if ($gotlastkey) {
1377                             //This should never happen. Why do we have a cell after the last cell?
1378                             mtrace("A cell with key ($key) was found after the last key ($lastkey)");
1379                         }
1381                         if (!($cell instanceof html_table_cell)) {
1382                             $mycell = new html_table_cell();
1383                             $mycell->text = $cell;
1384                             $cell = $mycell;
1385                         }
1387                         if (($cell->header === true) && empty($cell->scope)) {
1388                             $cell->scope = 'row';
1389                         }
1391                         if (isset($table->colclasses[$key])) {
1392                             $cell->attributes['class'] .= ' ' . $table->colclasses[$key];
1393                         }
1395                         $cell->attributes['class'] .= ' cell c' . $key;
1396                         if ($key == $lastkey) {
1397                             $cell->attributes['class'] .= ' lastcol';
1398                             $gotlastkey = true;
1399                         }
1400                         $tdstyle = '';
1401                         $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
1402                         $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
1403                         $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
1404                         $cell->attributes['class'] = trim($cell->attributes['class']);
1405                         $tdattributes = array_merge($cell->attributes, array(
1406                                 'style' => $tdstyle . $cell->style,
1407                                 'colspan' => $cell->colspan,
1408                                 'rowspan' => $cell->rowspan,
1409                                 'id' => $cell->id,
1410                                 'abbr' => $cell->abbr,
1411                                 'scope' => $cell->scope,
1412                             ));
1413                         $tagtype = 'td';
1414                         if ($cell->header === true) {
1415                             $tagtype = 'th';
1416                         }
1417                         $output .= html_writer::tag($tagtype, $cell->text, $tdattributes) . "\n";
1418                     }
1419                 }
1420                 $output .= html_writer::end_tag('tr') . "\n";
1421             }
1422             $output .= html_writer::end_tag('tbody') . "\n";
1423         }
1424         $output .= html_writer::end_tag('table') . "\n";
1426         return $output;
1427     }
1429     /**
1430      * Renders form element label
1431      *
1432      * By default, the label is suffixed with a label separator defined in the
1433      * current language pack (colon by default in the English lang pack).
1434      * Adding the colon can be explicitly disabled if needed. Label separators
1435      * are put outside the label tag itself so they are not read by
1436      * screenreaders (accessibility).
1437      *
1438      * Parameter $for explicitly associates the label with a form control. When
1439      * set, the value of this attribute must be the same as the value of
1440      * the id attribute of the form control in the same document. When null,
1441      * the label being defined is associated with the control inside the label
1442      * element.
1443      *
1444      * @param string $text content of the label tag
1445      * @param string|null $for id of the element this label is associated with, null for no association
1446      * @param bool $colonize add label separator (colon) to the label text, if it is not there yet
1447      * @param array $attributes to be inserted in the tab, for example array('accesskey' => 'a')
1448      * @return string HTML of the label element
1449      */
1450     public static function label($text, $for, $colonize=true, array $attributes=array()) {
1451         if (!is_null($for)) {
1452             $attributes = array_merge($attributes, array('for' => $for));
1453         }
1454         $text = trim($text);
1455         $label = self::tag('label', $text, $attributes);
1457         /*
1458         // TODO $colonize disabled for now yet - see MDL-12192 for details
1459         if (!empty($text) and $colonize) {
1460             // the $text may end with the colon already, though it is bad string definition style
1461             $colon = get_string('labelsep', 'langconfig');
1462             if (!empty($colon)) {
1463                 $trimmed = trim($colon);
1464                 if ((substr($text, -strlen($trimmed)) == $trimmed) or (substr($text, -1) == ':')) {
1465                     //debugging('The label text should not end with colon or other label separator,
1466                     //           please fix the string definition.', DEBUG_DEVELOPER);
1467                 } else {
1468                     $label .= $colon;
1469                 }
1470             }
1471         }
1472         */
1474         return $label;
1475     }
1478 // ==== JS writer and helper classes, will be probably moved elsewhere ======
1480 /**
1481  * Simple javascript output class
1482  * @copyright 2010 Petr Skoda
1483  */
1484 class js_writer {
1485     /**
1486      * Returns javascript code calling the function
1487      * @param string $function function name, can be complex like Y.Event.purgeElement
1488      * @param array $arguments parameters
1489      * @param int $delay execution delay in seconds
1490      * @return string JS code fragment
1491      */
1492     public static function function_call($function, array $arguments = null, $delay=0) {
1493         if ($arguments) {
1494             $arguments = array_map('json_encode', $arguments);
1495             $arguments = implode(', ', $arguments);
1496         } else {
1497             $arguments = '';
1498         }
1499         $js = "$function($arguments);";
1501         if ($delay) {
1502             $delay = $delay * 1000; // in miliseconds
1503             $js = "setTimeout(function() { $js }, $delay);";
1504         }
1505         return $js . "\n";
1506     }
1508     /**
1509      * Special function which adds Y as first argument of fucntion call.
1510      * @param string $function
1511      * @param array $extraarguments
1512      * @return string
1513      */
1514     public static function function_call_with_Y($function, array $extraarguments = null) {
1515         if ($extraarguments) {
1516             $extraarguments = array_map('json_encode', $extraarguments);
1517             $arguments = 'Y, ' . implode(', ', $extraarguments);
1518         } else {
1519             $arguments = 'Y';
1520         }
1521         return "$function($arguments);\n";
1522     }
1524     /**
1525      * Returns JavaScript code to initialise a new object
1526      * @param string|null $var If it is null then no var is assigned the new object
1527      * @param string $class
1528      * @param array $arguments
1529      * @param array $requirements
1530      * @param int $delay
1531      * @return string
1532      */
1533     public static function object_init($var, $class, array $arguments = null, array $requirements = null, $delay=0) {
1534         if (is_array($arguments)) {
1535             $arguments = array_map('json_encode', $arguments);
1536             $arguments = implode(', ', $arguments);
1537         }
1539         if ($var === null) {
1540             $js = "new $class(Y, $arguments);";
1541         } else if (strpos($var, '.')!==false) {
1542             $js = "$var = new $class(Y, $arguments);";
1543         } else {
1544             $js = "var $var = new $class(Y, $arguments);";
1545         }
1547         if ($delay) {
1548             $delay = $delay * 1000; // in miliseconds
1549             $js = "setTimeout(function() { $js }, $delay);";
1550         }
1552         if (count($requirements) > 0) {
1553             $requirements = implode("', '", $requirements);
1554             $js = "Y.use('$requirements', function(Y){ $js });";
1555         }
1556         return $js."\n";
1557     }
1559     /**
1560      * Returns code setting value to variable
1561      * @param string $name
1562      * @param mixed $value json serialised value
1563      * @param bool $usevar add var definition, ignored for nested properties
1564      * @return string JS code fragment
1565      */
1566     public static function set_variable($name, $value, $usevar=true) {
1567         $output = '';
1569         if ($usevar) {
1570             if (strpos($name, '.')) {
1571                 $output .= '';
1572             } else {
1573                 $output .= 'var ';
1574             }
1575         }
1577         $output .= "$name = ".json_encode($value).";";
1579         return $output;
1580     }
1582     /**
1583      * Writes event handler attaching code
1584      * @param mixed $selector standard YUI selector for elements, may be array or string, element id is in the form "#idvalue"
1585      * @param string $event A valid DOM event (click, mousedown, change etc.)
1586      * @param string $function The name of the function to call
1587      * @param array  $arguments An optional array of argument parameters to pass to the function
1588      * @return string JS code fragment
1589      */
1590     public static function event_handler($selector, $event, $function, array $arguments = null) {
1591         $selector = json_encode($selector);
1592         $output = "Y.on('$event', $function, $selector, null";
1593         if (!empty($arguments)) {
1594             $output .= ', ' . json_encode($arguments);
1595         }
1596         return $output . ");\n";
1597     }
1600 /**
1601  * Holds all the information required to render a <table> by {@see core_renderer::table()}
1602  *
1603  * Example of usage:
1604  * $t = new html_table();
1605  * ... // set various properties of the object $t as described below
1606  * echo html_writer::table($t);
1607  *
1608  * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
1609  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1610  * @since     Moodle 2.0
1611  */
1612 class html_table {
1613     /**
1614      * @var string value to use for the id attribute of the table
1615      */
1616     public $id = null;
1617     /**
1618      * @var array attributes of HTML attributes for the <table> element
1619      */
1620     public $attributes = array();
1621     /**
1622      * For more control over the rendering of the headers, an array of html_table_cell objects
1623      * can be passed instead of an array of strings.
1624      * @var array of headings. The n-th array item is used as a heading of the n-th column.
1625      *
1626      * Example of usage:
1627      * $t->head = array('Student', 'Grade');
1628      */
1629     public $head;
1630     /**
1631      * @var array can be used to make a heading span multiple columns
1632      *
1633      * Example of usage:
1634      * $t->headspan = array(2,1);
1635      *
1636      * In this example, {@see html_table:$data} is supposed to have three columns. For the first two columns,
1637      * the same heading is used. Therefore, {@see html_table::$head} should consist of two items.
1638      */
1639     public $headspan;
1640     /**
1641      * @var array of column alignments. The value is used as CSS 'text-align' property. Therefore, possible
1642      * values are 'left', 'right', 'center' and 'justify'. Specify 'right' or 'left' from the perspective
1643      * of a left-to-right (LTR) language. For RTL, the values are flipped automatically.
1644      *
1645      * Examples of usage:
1646      * $t->align = array(null, 'right');
1647      * or
1648      * $t->align[1] = 'right';
1649      *
1650      */
1651     public $align;
1652     /**
1653      * @var array of column sizes. The value is used as CSS 'size' property.
1654      *
1655      * Examples of usage:
1656      * $t->size = array('50%', '50%');
1657      * or
1658      * $t->size[1] = '120px';
1659      */
1660     public $size;
1661     /**
1662      * @var array of wrapping information. The only possible value is 'nowrap' that sets the
1663      * CSS property 'white-space' to the value 'nowrap' in the given column.
1664      *
1665      * Example of usage:
1666      * $t->wrap = array(null, 'nowrap');
1667      */
1668     public $wrap;
1669     /**
1670      * @var array of arrays or html_table_row objects containing the data. Alternatively, if you have
1671      * $head specified, the string 'hr' (for horizontal ruler) can be used
1672      * instead of an array of cells data resulting in a divider rendered.
1673      *
1674      * Example of usage with array of arrays:
1675      * $row1 = array('Harry Potter', '76 %');
1676      * $row2 = array('Hermione Granger', '100 %');
1677      * $t->data = array($row1, $row2);
1678      *
1679      * Example with array of html_table_row objects: (used for more fine-grained control)
1680      * $cell1 = new html_table_cell();
1681      * $cell1->text = 'Harry Potter';
1682      * $cell1->colspan = 2;
1683      * $row1 = new html_table_row();
1684      * $row1->cells[] = $cell1;
1685      * $cell2 = new html_table_cell();
1686      * $cell2->text = 'Hermione Granger';
1687      * $cell3 = new html_table_cell();
1688      * $cell3->text = '100 %';
1689      * $row2 = new html_table_row();
1690      * $row2->cells = array($cell2, $cell3);
1691      * $t->data = array($row1, $row2);
1692      */
1693     public $data;
1694     /**
1695      * @var string width of the table, percentage of the page preferred. Defaults to 80%
1696      * @deprecated since Moodle 2.0. Styling should be in the CSS.
1697      */
1698     public $width = null;
1699     /**
1700      * @var string alignment the whole table. Can be 'right', 'left' or 'center' (default).
1701      * @deprecated since Moodle 2.0. Styling should be in the CSS.
1702      */
1703     public $tablealign = null;
1704     /**
1705      * @var int padding on each cell, in pixels
1706      * @deprecated since Moodle 2.0. Styling should be in the CSS.
1707      */
1708     public $cellpadding = null;
1709     /**
1710      * @var int spacing between cells, in pixels
1711      * @deprecated since Moodle 2.0. Styling should be in the CSS.
1712      */
1713     public $cellspacing = null;
1714     /**
1715      * @var array classes to add to particular rows, space-separated string.
1716      * Classes 'r0' or 'r1' are added automatically for every odd or even row,
1717      * respectively. Class 'lastrow' is added automatically for the last row
1718      * in the table.
1719      *
1720      * Example of usage:
1721      * $t->rowclasses[9] = 'tenth'
1722      */
1723     public $rowclasses;
1724     /**
1725      * @var array classes to add to every cell in a particular column,
1726      * space-separated string. Class 'cell' is added automatically by the renderer.
1727      * Classes 'c0' or 'c1' are added automatically for every odd or even column,
1728      * respectively. Class 'lastcol' is added automatically for all last cells
1729      * in a row.
1730      *
1731      * Example of usage:
1732      * $t->colclasses = array(null, 'grade');
1733      */
1734     public $colclasses;
1735     /**
1736      * @var string description of the contents for screen readers.
1737      */
1738     public $summary;
1740     /**
1741      * Constructor
1742      */
1743     public function __construct() {
1744         $this->attributes['class'] = '';
1745     }
1749 /**
1750  * Component representing a table row.
1751  *
1752  * @copyright 2009 Nicolas Connault
1753  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1754  * @since     Moodle 2.0
1755  */
1756 class html_table_row {
1757     /**
1758      * @var string value to use for the id attribute of the row
1759      */
1760     public $id = null;
1761     /**
1762      * @var array $cells Array of html_table_cell objects
1763      */
1764     public $cells = array();
1765     /**
1766      * @var string $style value to use for the style attribute of the table row
1767      */
1768     public $style = null;
1769     /**
1770      * @var array attributes of additional HTML attributes for the <tr> element
1771      */
1772     public $attributes = array();
1774     /**
1775      * Constructor
1776      * @param array $cells
1777      */
1778     public function __construct(array $cells=null) {
1779         $this->attributes['class'] = '';
1780         $cells = (array)$cells;
1781         foreach ($cells as $cell) {
1782             if ($cell instanceof html_table_cell) {
1783                 $this->cells[] = $cell;
1784             } else {
1785                 $this->cells[] = new html_table_cell($cell);
1786             }
1787         }
1788     }
1792 /**
1793  * Component representing a table cell.
1794  *
1795  * @copyright 2009 Nicolas Connault
1796  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1797  * @since     Moodle 2.0
1798  */
1799 class html_table_cell {
1800     /**
1801      * @var string value to use for the id attribute of the cell
1802      */
1803     public $id = null;
1804     /**
1805      * @var string $text The contents of the cell
1806      */
1807     public $text;
1808     /**
1809      * @var string $abbr Abbreviated version of the contents of the cell
1810      */
1811     public $abbr = null;
1812     /**
1813      * @var int $colspan Number of columns this cell should span
1814      */
1815     public $colspan = null;
1816     /**
1817      * @var int $rowspan Number of rows this cell should span
1818      */
1819     public $rowspan = null;
1820     /**
1821      * @var string $scope Defines a way to associate header cells and data cells in a table
1822      */
1823     public $scope = null;
1824     /**
1825      * @var boolean $header Whether or not this cell is a header cell
1826      */
1827     public $header = null;
1828     /**
1829      * @var string $style value to use for the style attribute of the table cell
1830      */
1831     public $style = null;
1832     /**
1833      * @var array attributes of additional HTML attributes for the <td> element
1834      */
1835     public $attributes = array();
1837     public function __construct($text = null) {
1838         $this->text = $text;
1839         $this->attributes['class'] = '';
1840     }
1844 /// Complex components aggregating simpler components
1847 /**
1848  * Component representing a paging bar.
1849  *
1850  * @copyright 2009 Nicolas Connault
1851  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1852  * @since     Moodle 2.0
1853  */
1854 class paging_bar implements renderable {
1855     /**
1856      * @var int $maxdisplay The maximum number of pagelinks to display
1857      */
1858     public $maxdisplay = 18;
1859     /**
1860      * @var int $totalcount post or get
1861      */
1862     public $totalcount;
1863     /**
1864      * @var int $page The page you are currently viewing
1865      */
1866     public $page;
1867     /**
1868      * @var int $perpage The number of entries that should be shown per page
1869      */
1870     public $perpage;
1871     /**
1872      * @var string $baseurl If this  is a string then it is the url which will be appended with $pagevar, an equals sign and the page number.
1873      *      If this is a moodle_url object then the pagevar param will be replaced by the page no, for each page.
1874      */
1875     public $baseurl;
1876     /**
1877      * @var string $pagevar This is the variable name that you use for the page number in your code (ie. 'tablepage', 'blogpage', etc)
1878      */
1879     public $pagevar;
1880     /**
1881      * @var string $previouslink A HTML link representing the "previous" page
1882      */
1883     public $previouslink = null;
1884     /**
1885      * @var tring $nextlink A HTML link representing the "next" page
1886      */
1887     public $nextlink = null;
1888     /**
1889      * @var tring $firstlink A HTML link representing the first page
1890      */
1891     public $firstlink = null;
1892     /**
1893      * @var tring $lastlink A HTML link representing the last page
1894      */
1895     public $lastlink = null;
1896     /**
1897      * @var array $pagelinks An array of strings. One of them is just a string: the current page
1898      */
1899     public $pagelinks = array();
1901     /**
1902      * Constructor paging_bar with only the required params.
1903      *
1904      * @param int $totalcount The total number of entries available to be paged through
1905      * @param int $page The page you are currently viewing
1906      * @param int $perpage The number of entries that should be shown per page
1907      * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
1908      * @param string $pagevar name of page parameter that holds the page number
1909      */
1910     public function __construct($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
1911         $this->totalcount = $totalcount;
1912         $this->page       = $page;
1913         $this->perpage    = $perpage;
1914         $this->baseurl    = $baseurl;
1915         $this->pagevar    = $pagevar;
1916     }
1918     /**
1919      * @return void
1920      */
1921     public function prepare(renderer_base $output, moodle_page $page, $target) {
1922         if (!isset($this->totalcount) || is_null($this->totalcount)) {
1923             throw new coding_exception('paging_bar requires a totalcount value.');
1924         }
1925         if (!isset($this->page) || is_null($this->page)) {
1926             throw new coding_exception('paging_bar requires a page value.');
1927         }
1928         if (empty($this->perpage)) {
1929             throw new coding_exception('paging_bar requires a perpage value.');
1930         }
1931         if (empty($this->baseurl)) {
1932             throw new coding_exception('paging_bar requires a baseurl value.');
1933         }
1935         if ($this->totalcount > $this->perpage) {
1936             $pagenum = $this->page - 1;
1938             if ($this->page > 0) {
1939                 $this->previouslink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('previous'), array('class'=>'previous'));
1940             }
1942             if ($this->perpage > 0) {
1943                 $lastpage = ceil($this->totalcount / $this->perpage);
1944             } else {
1945                 $lastpage = 1;
1946             }
1948             if ($this->page > 15) {
1949                 $startpage = $this->page - 10;
1951                 $this->firstlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>0)), '1', array('class'=>'first'));
1952             } else {
1953                 $startpage = 0;
1954             }
1956             $currpage = $startpage;
1957             $displaycount = $displaypage = 0;
1959             while ($displaycount < $this->maxdisplay and $currpage < $lastpage) {
1960                 $displaypage = $currpage + 1;
1962                 if ($this->page == $currpage) {
1963                     $this->pagelinks[] = $displaypage;
1964                 } else {
1965                     $pagelink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$currpage)), $displaypage);
1966                     $this->pagelinks[] = $pagelink;
1967                 }
1969                 $displaycount++;
1970                 $currpage++;
1971             }
1973             if ($currpage < $lastpage) {
1974                 $lastpageactual = $lastpage - 1;
1975                 $this->lastlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$lastpageactual)), $lastpage, array('class'=>'last'));
1976             }
1978             $pagenum = $this->page + 1;
1980             if ($pagenum != $displaypage) {
1981                 $this->nextlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('next'), array('class'=>'next'));
1982             }
1983         }
1984     }
1988 /**
1989  * This class represents how a block appears on a page.
1990  *
1991  * During output, each block instance is asked to return a block_contents object,
1992  * those are then passed to the $OUTPUT->block function for display.
1993  *
1994  * {@link $contents} should probably be generated using a moodle_block_..._renderer.
1995  *
1996  * Other block-like things that need to appear on the page, for example the
1997  * add new block UI, are also represented as block_contents objects.
1998  *
1999  * @copyright 2009 Tim Hunt
2000  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2001  * @since     Moodle 2.0
2002  */
2003 class block_contents {
2004     /** @var int used to set $skipid. */
2005     protected static $idcounter = 1;
2007     const NOT_HIDEABLE = 0;
2008     const VISIBLE = 1;
2009     const HIDDEN = 2;
2011     /**
2012      * @var integer $skipid All the blocks (or things that look like blocks)
2013      * printed on a page are given a unique number that can be used to construct
2014      * id="" attributes. This is set automatically be the {@link prepare()} method.
2015      * Do not try to set it manually.
2016      */
2017     public $skipid;
2019     /**
2020      * @var integer If this is the contents of a real block, this should be set to
2021      * the block_instance.id. Otherwise this should be set to 0.
2022      */
2023     public $blockinstanceid = 0;
2025     /**
2026      * @var integer if this is a real block instance, and there is a corresponding
2027      * block_position.id for the block on this page, this should be set to that id.
2028      * Otherwise it should be 0.
2029      */
2030     public $blockpositionid = 0;
2032     /**
2033      * @param array $attributes an array of attribute => value pairs that are put on the
2034      * outer div of this block. {@link $id} and {@link $classes} attributes should be set separately.
2035      */
2036     public $attributes;
2038     /**
2039      * @param string $title The title of this block. If this came from user input,
2040      * it should already have had format_string() processing done on it. This will
2041      * be output inside <h2> tags. Please do not cause invalid XHTML.
2042      */
2043     public $title = '';
2045     /**
2046      * @param string $content HTML for the content
2047      */
2048     public $content = '';
2050     /**
2051      * @param array $list an alternative to $content, it you want a list of things with optional icons.
2052      */
2053     public $footer = '';
2055     /**
2056      * Any small print that should appear under the block to explain to the
2057      * teacher about the block, for example 'This is a sticky block that was
2058      * added in the system context.'
2059      * @var string
2060      */
2061     public $annotation = '';
2063     /**
2064      * @var integer one of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether
2065      * the user can toggle whether this block is visible.
2066      */
2067     public $collapsible = self::NOT_HIDEABLE;
2069     /**
2070      * A (possibly empty) array of editing controls. Each element of this array
2071      * should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption).
2072      * $icon is the icon name. Fed to $OUTPUT->pix_url.
2073      * @var array
2074      */
2075     public $controls = array();
2078     /**
2079      * Create new instance of block content
2080      * @param array $attributes
2081      */
2082     public function __construct(array $attributes=null) {
2083         $this->skipid = self::$idcounter;
2084         self::$idcounter += 1;
2086         if ($attributes) {
2087             // standard block
2088             $this->attributes = $attributes;
2089         } else {
2090             // simple "fake" blocks used in some modules and "Add new block" block
2091             $this->attributes = array('class'=>'block');
2092         }
2093     }
2095     /**
2096      * Add html class to block
2097      * @param string $class
2098      * @return void
2099      */
2100     public function add_class($class) {
2101         $this->attributes['class'] .= ' '.$class;
2102     }
2106 /**
2107  * This class represents a target for where a block can go when it is being moved.
2108  *
2109  * This needs to be rendered as a form with the given hidden from fields, and
2110  * clicking anywhere in the form should submit it. The form action should be
2111  * $PAGE->url.
2112  *
2113  * @copyright 2009 Tim Hunt
2114  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2115  * @since     Moodle 2.0
2116  */
2117 class block_move_target {
2118     /**
2119      * Move url
2120      * @var moodle_url
2121      */
2122     public $url;
2123     /**
2124      * label
2125      * @var string
2126      */
2127     public $text;
2129     /**
2130      * Constructor
2131      * @param string $text
2132      * @param moodle_url $url
2133      */
2134     public function __construct($text, moodle_url $url) {
2135         $this->text = $text;
2136         $this->url  = $url;
2137     }
2140 /**
2141  * Custom menu item
2142  *
2143  * This class is used to represent one item within a custom menu that may or may
2144  * not have children.
2145  *
2146  * @copyright 2010 Sam Hemelryk
2147  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2148  * @since     Moodle 2.0
2149  */
2150 class custom_menu_item implements renderable {
2151     /**
2152      * The text to show for the item
2153      * @var string
2154      */
2155     protected $text;
2156     /**
2157      * The link to give the icon if it has no children
2158      * @var moodle_url
2159      */
2160     protected $url;
2161     /**
2162      * A title to apply to the item. By default the text
2163      * @var string
2164      */
2165     protected $title;
2166     /**
2167      * A sort order for the item, not necessary if you order things in the CFG var
2168      * @var int
2169      */
2170     protected $sort;
2171     /**
2172      * A reference to the parent for this item or NULL if it is a top level item
2173      * @var custom_menu_item
2174      */
2175     protected $parent;
2176     /**
2177      * A array in which to store children this item has.
2178      * @var array
2179      */
2180     protected $children = array();
2181     /**
2182      * A reference to the sort var of the last child that was added
2183      * @var int
2184      */
2185     protected $lastsort = 0;
2186     /**
2187      * Constructs the new custom menu item
2188      *
2189      * @param string $text
2190      * @param moodle_url $url A moodle url to apply as the link for this item [Optional]
2191      * @param string $title A title to apply to this item [Optional]
2192      * @param int $sort A sort or to use if we need to sort differently [Optional]
2193      * @param custom_menu_item $parent A reference to the parent custom_menu_item this child
2194      *        belongs to, only if the child has a parent. [Optional]
2195      */
2196     public function __construct($text, moodle_url $url=null, $title=null, $sort = null, custom_menu_item $parent=null) {
2197         $this->text = $text;
2198         $this->url = $url;
2199         $this->title = $title;
2200         $this->sort = (int)$sort;
2201         $this->parent = $parent;
2202     }
2204     /**
2205      * Adds a custom menu item as a child of this node given its properties.
2206      *
2207      * @param string $text
2208      * @param moodle_url $url
2209      * @param string $title
2210      * @param int $sort
2211      * @return custom_menu_item
2212      */
2213     public function add($text, moodle_url $url=null, $title=null, $sort = null) {
2214         $key = count($this->children);
2215         if (empty($sort)) {
2216             $sort = $this->lastsort + 1;
2217         }
2218         $this->children[$key] = new custom_menu_item($text, $url, $title, $sort, $this);
2219         $this->lastsort = (int)$sort;
2220         return $this->children[$key];
2221     }
2222     /**
2223      * Returns the text for this item
2224      * @return string
2225      */
2226     public function get_text() {
2227         return $this->text;
2228     }
2229     /**
2230      * Returns the url for this item
2231      * @return moodle_url
2232      */
2233     public function get_url() {
2234         return $this->url;
2235     }
2236     /**
2237      * Returns the title for this item
2238      * @return string
2239      */
2240     public function get_title() {
2241         return $this->title;
2242     }
2243     /**
2244      * Sorts and returns the children for this item
2245      * @return array
2246      */
2247     public function get_children() {
2248         $this->sort();
2249         return $this->children;
2250     }
2251     /**
2252      * Gets the sort order for this child
2253      * @return int
2254      */
2255     public function get_sort_order() {
2256         return $this->sort;
2257     }
2258     /**
2259      * Gets the parent this child belong to
2260      * @return custom_menu_item
2261      */
2262     public function get_parent() {
2263         return $this->parent;
2264     }
2265     /**
2266      * Sorts the children this item has
2267      */
2268     public function sort() {
2269         usort($this->children, array('custom_menu','sort_custom_menu_items'));
2270     }
2271     /**
2272      * Returns true if this item has any children
2273      * @return bool
2274      */
2275     public function has_children() {
2276         return (count($this->children) > 0);
2277     }
2279     /**
2280      * Sets the text for the node
2281      * @param string $text
2282      */
2283     public function set_text($text) {
2284         $this->text = (string)$text;
2285     }
2287     /**
2288      * Sets the title for the node
2289      * @param string $title
2290      */
2291     public function set_title($title) {
2292         $this->title = (string)$title;
2293     }
2295     /**
2296      * Sets the url for the node
2297      * @param moodle_url $url
2298      */
2299     public function set_url(moodle_url $url) {
2300         $this->url = $url;
2301     }
2304 /**
2305  * Custom menu class
2306  *
2307  * This class is used to operate a custom menu that can be rendered for the page.
2308  * The custom menu is built using $CFG->custommenuitems and is a structured collection
2309  * of custom_menu_item nodes that can be rendered by the core renderer.
2310  *
2311  * To configure the custom menu:
2312  *     Settings: Administration > Appearance > Themes > Theme settings
2313  *
2314  * @copyright 2010 Sam Hemelryk
2315  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2316  * @since     Moodle 2.0
2317  */
2318 class custom_menu extends custom_menu_item {
2319     /**
2320      * Creates the custom menu
2321      * @param string $text Sets the text for this custom menu, never gets used and is optional
2322      */
2323     public function __construct($text='base') {
2324         global $CFG;
2325         parent::__construct($text);
2326         if (!empty($CFG->custommenuitems)) {
2327             $this->override_children(self::convert_text_to_menu_nodes($CFG->custommenuitems));
2328         }
2329     }
2331     /**
2332      * Overrides the children of this custom menu. Useful when getting children
2333      * from $CFG->custommenuitems
2334      */
2335     public function override_children(array $children) {
2336         $this->children = array();
2337         foreach ($children as $child) {
2338             if ($child instanceof custom_menu_item) {
2339                 $this->children[] = $child;
2340             }
2341         }
2342     }
2344     /**
2345      * Converts a string into a structured array of custom_menu_items which can
2346      * then be added to a custom menu.
2347      *
2348      * Structure:
2349      *     text|url|title
2350      * The number of hyphens at the start determines the depth of the item
2351      *
2352      * Example structure:
2353      *     First level first item|http://www.moodle.com/
2354      *     -Second level first item|http://www.moodle.com/partners/
2355      *     -Second level second item|http://www.moodle.com/hq/
2356      *     --Third level first item|http://www.moodle.com/jobs/
2357      *     -Second level third item|http://www.moodle.com/development/
2358      *     First level second item|http://www.moodle.com/feedback/
2359      *     First level third item
2360      *
2361      * @static
2362      * @param string $text
2363      * @return array
2364      */
2365     public static function convert_text_to_menu_nodes($text) {
2366         $lines = explode("\n", $text);
2367         $children = array();
2368         $lastchild = null;
2369         $lastdepth = null;
2370         $lastsort = 0;
2371         foreach ($lines as $line) {
2372             $line = trim($line);
2373             $bits = explode('|', $line ,4);    // name|url|title|sort
2374             if (!array_key_exists(0, $bits) || empty($bits[0])) {
2375                 // Every item must have a name to be valid
2376                 continue;
2377             } else {
2378                 $bits[0] = ltrim($bits[0],'-');
2379             }
2380             if (!array_key_exists(1, $bits)) {
2381                 // Set the url to null
2382                 $bits[1] = null;
2383             } else {
2384                 // Make sure the url is a moodle url
2385                 $bits[1] = new moodle_url(trim($bits[1]));
2386             }
2387             if (!array_key_exists(2, $bits)) {
2388                 // Set the title to null seeing as there isn't one
2389                 $bits[2] = $bits[0];
2390             }
2391             // Set an incremental sort order to keep it simple.
2392             $bits[3] = $lastsort;
2393             $lastsort = $bits[3]+1;
2394             if (preg_match('/^(\-*)/', $line, $match) && $lastchild != null && $lastdepth !== null) {
2395                 $depth = strlen($match[1]);
2396                 if ($depth < $lastdepth) {
2397                     $difference = $lastdepth - $depth;
2398                     if ($lastdepth > 1 && $lastdepth != $difference) {
2399                         $tempchild = $lastchild->get_parent();
2400                         for ($i =0; $i < $difference; $i++) {
2401                             $tempchild = $tempchild->get_parent();
2402                         }
2403                         $lastchild = $tempchild->add($bits[0], $bits[1], $bits[2], $bits[3]);
2404                     } else {
2405                         $depth = 0;
2406                         $lastchild = new custom_menu_item($bits[0], $bits[1], $bits[2], $bits[3]);
2407                         $children[] = $lastchild;
2408                     }
2409                 } else if ($depth > $lastdepth) {
2410                     $depth = $lastdepth + 1;
2411                     $lastchild = $lastchild->add($bits[0], $bits[1], $bits[2], $bits[3]);
2412                 } else {
2413                     if ($depth == 0) {
2414                         $lastchild = new custom_menu_item($bits[0], $bits[1], $bits[2], $bits[3]);
2415                         $children[] = $lastchild;
2416                     } else {
2417                         $lastchild = $lastchild->get_parent()->add($bits[0], $bits[1], $bits[2], $bits[3]);
2418                     }
2419                 }
2420             } else {
2421                 $depth = 0;
2422                 $lastchild = new custom_menu_item($bits[0], $bits[1], $bits[2], $bits[3]);
2423                 $children[] = $lastchild;
2424             }
2425             $lastdepth = $depth;
2426         }
2427         return $children;
2428     }
2430     /**
2431      * Sorts two custom menu items
2432      *
2433      * This function is designed to be used with the usort method
2434      *     usort($this->children, array('custom_menu','sort_custom_menu_items'));
2435      *
2436      * @param custom_menu_item $itema
2437      * @param custom_menu_item $itemb
2438      * @return int
2439      */
2440     public static function sort_custom_menu_items(custom_menu_item $itema, custom_menu_item $itemb) {
2441         $itema = $itema->get_sort_order();
2442         $itemb = $itemb->get_sort_order();
2443         if ($itema == $itemb) {
2444             return 0;
2445         }
2446         return ($itema > $itemb) ? +1 : -1;
2447     }