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