Merge branch 'MDL-62680-master' of git://github.com/damyon/moodle
[moodle.git] / lib / outputcomponents.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Classes representing HTML elements, used by $OUTPUT methods
19  *
20  * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
21  * for an overview.
22  *
23  * @package core
24  * @category output
25  * @copyright 2009 Tim Hunt
26  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 /**
32  * Interface marking other classes as suitable for renderer_base::render()
33  *
34  * @copyright 2010 Petr Skoda (skodak) info@skodak.org
35  * @package core
36  * @category output
37  */
38 interface renderable {
39     // intentionally empty
40 }
42 /**
43  * Interface marking other classes having the ability to export their data for use by templates.
44  *
45  * @copyright 2015 Damyon Wiese
46  * @package core
47  * @category output
48  * @since 2.9
49  */
50 interface templatable {
52     /**
53      * Function to export the renderer data in a format that is suitable for a
54      * mustache template. This means:
55      * 1. No complex types - only stdClass, array, int, string, float, bool
56      * 2. Any additional info that is required for the template is pre-calculated (e.g. capability checks).
57      *
58      * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
59      * @return stdClass|array
60      */
61     public function export_for_template(renderer_base $output);
62 }
64 /**
65  * Data structure representing a file picker.
66  *
67  * @copyright 2010 Dongsheng Cai
68  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
69  * @since Moodle 2.0
70  * @package core
71  * @category output
72  */
73 class file_picker implements renderable {
75     /**
76      * @var stdClass An object containing options for the file picker
77      */
78     public $options;
80     /**
81      * Constructs a file picker object.
82      *
83      * The following are possible options for the filepicker:
84      *    - accepted_types  (*)
85      *    - return_types    (FILE_INTERNAL)
86      *    - env             (filepicker)
87      *    - client_id       (uniqid)
88      *    - itemid          (0)
89      *    - maxbytes        (-1)
90      *    - maxfiles        (1)
91      *    - buttonname      (false)
92      *
93      * @param stdClass $options An object containing options for the file picker.
94      */
95     public function __construct(stdClass $options) {
96         global $CFG, $USER, $PAGE;
97         require_once($CFG->dirroot. '/repository/lib.php');
98         $defaults = array(
99             'accepted_types'=>'*',
100             'return_types'=>FILE_INTERNAL,
101             'env' => 'filepicker',
102             'client_id' => uniqid(),
103             'itemid' => 0,
104             'maxbytes'=>-1,
105             'maxfiles'=>1,
106             'buttonname'=>false
107         );
108         foreach ($defaults as $key=>$value) {
109             if (empty($options->$key)) {
110                 $options->$key = $value;
111             }
112         }
114         $options->currentfile = '';
115         if (!empty($options->itemid)) {
116             $fs = get_file_storage();
117             $usercontext = context_user::instance($USER->id);
118             if (empty($options->filename)) {
119                 if ($files = $fs->get_area_files($usercontext->id, 'user', 'draft', $options->itemid, 'id DESC', false)) {
120                     $file = reset($files);
121                 }
122             } else {
123                 $file = $fs->get_file($usercontext->id, 'user', 'draft', $options->itemid, $options->filepath, $options->filename);
124             }
125             if (!empty($file)) {
126                 $options->currentfile = html_writer::link(moodle_url::make_draftfile_url($file->get_itemid(), $file->get_filepath(), $file->get_filename()), $file->get_filename());
127             }
128         }
130         // initilise options, getting files in root path
131         $this->options = initialise_filepicker($options);
133         // copying other options
134         foreach ($options as $name=>$value) {
135             if (!isset($this->options->$name)) {
136                 $this->options->$name = $value;
137             }
138         }
139     }
142 /**
143  * Data structure representing a user picture.
144  *
145  * @copyright 2009 Nicolas Connault, 2010 Petr Skoda
146  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
147  * @since Modle 2.0
148  * @package core
149  * @category output
150  */
151 class user_picture implements renderable {
152     /**
153      * @var array List of mandatory fields in user record here. (do not include
154      * TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE)
155      */
156     protected static $fields = array('id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic',
157             'middlename', 'alternatename', 'imagealt', 'email');
159     /**
160      * @var stdClass A user object with at least fields all columns specified
161      * in $fields array constant set.
162      */
163     public $user;
165     /**
166      * @var int The course id. Used when constructing the link to the user's
167      * profile, page course id used if not specified.
168      */
169     public $courseid;
171     /**
172      * @var bool Add course profile link to image
173      */
174     public $link = true;
176     /**
177      * @var int Size in pixels. Special values are (true/1 = 100px) and
178      * (false/0 = 35px)
179      * for backward compatibility.
180      */
181     public $size = 35;
183     /**
184      * @var bool Add non-blank alt-text to the image.
185      * Default true, set to false when image alt just duplicates text in screenreaders.
186      */
187     public $alttext = true;
189     /**
190      * @var bool Whether or not to open the link in a popup window.
191      */
192     public $popup = false;
194     /**
195      * @var string Image class attribute
196      */
197     public $class = 'userpicture';
199     /**
200      * @var bool Whether to be visible to screen readers.
201      */
202     public $visibletoscreenreaders = true;
204     /**
205      * @var bool Whether to include the fullname in the user picture link.
206      */
207     public $includefullname = false;
209     /**
210      * @var bool Include user authentication token.
211      */
212     public $includetoken = false;
214     /**
215      * User picture constructor.
216      *
217      * @param stdClass $user user record with at least id, picture, imagealt, firstname and lastname set.
218      *                 It is recommended to add also contextid of the user for performance reasons.
219      */
220     public function __construct(stdClass $user) {
221         global $DB;
223         if (empty($user->id)) {
224             throw new coding_exception('User id is required when printing user avatar image.');
225         }
227         // only touch the DB if we are missing data and complain loudly...
228         $needrec = false;
229         foreach (self::$fields as $field) {
230             if (!array_key_exists($field, $user)) {
231                 $needrec = true;
232                 debugging('Missing '.$field.' property in $user object, this is a performance problem that needs to be fixed by a developer. '
233                           .'Please use user_picture::fields() to get the full list of required fields.', DEBUG_DEVELOPER);
234                 break;
235             }
236         }
238         if ($needrec) {
239             $this->user = $DB->get_record('user', array('id'=>$user->id), self::fields(), MUST_EXIST);
240         } else {
241             $this->user = clone($user);
242         }
243     }
245     /**
246      * Returns a list of required user fields, useful when fetching required user info from db.
247      *
248      * In some cases we have to fetch the user data together with some other information,
249      * the idalias is useful there because the id would otherwise override the main
250      * id of the result record. Please note it has to be converted back to id before rendering.
251      *
252      * @param string $tableprefix name of database table prefix in query
253      * @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)
254      * @param string $idalias alias of id field
255      * @param string $fieldprefix prefix to add to all columns in their aliases, does not apply to 'id'
256      * @return string
257      */
258     public static function fields($tableprefix = '', array $extrafields = NULL, $idalias = 'id', $fieldprefix = '') {
259         if (!$tableprefix and !$extrafields and !$idalias) {
260             return implode(',', self::$fields);
261         }
262         if ($tableprefix) {
263             $tableprefix .= '.';
264         }
265         foreach (self::$fields as $field) {
266             if ($field === 'id' and $idalias and $idalias !== 'id') {
267                 $fields[$field] = "$tableprefix$field AS $idalias";
268             } else {
269                 if ($fieldprefix and $field !== 'id') {
270                     $fields[$field] = "$tableprefix$field AS $fieldprefix$field";
271                 } else {
272                     $fields[$field] = "$tableprefix$field";
273                 }
274             }
275         }
276         // add extra fields if not already there
277         if ($extrafields) {
278             foreach ($extrafields as $e) {
279                 if ($e === 'id' or isset($fields[$e])) {
280                     continue;
281                 }
282                 if ($fieldprefix) {
283                     $fields[$e] = "$tableprefix$e AS $fieldprefix$e";
284                 } else {
285                     $fields[$e] = "$tableprefix$e";
286                 }
287             }
288         }
289         return implode(',', $fields);
290     }
292     /**
293      * Extract the aliased user fields from a given record
294      *
295      * Given a record that was previously obtained using {@link self::fields()} with aliases,
296      * this method extracts user related unaliased fields.
297      *
298      * @param stdClass $record containing user picture fields
299      * @param array $extrafields extra fields included in the $record
300      * @param string $idalias alias of the id field
301      * @param string $fieldprefix prefix added to all columns in their aliases, does not apply to 'id'
302      * @return stdClass object with unaliased user fields
303      */
304     public static function unalias(stdClass $record, array $extrafields = null, $idalias = 'id', $fieldprefix = '') {
306         if (empty($idalias)) {
307             $idalias = 'id';
308         }
310         $return = new stdClass();
312         foreach (self::$fields as $field) {
313             if ($field === 'id') {
314                 if (property_exists($record, $idalias)) {
315                     $return->id = $record->{$idalias};
316                 }
317             } else {
318                 if (property_exists($record, $fieldprefix.$field)) {
319                     $return->{$field} = $record->{$fieldprefix.$field};
320                 }
321             }
322         }
323         // add extra fields if not already there
324         if ($extrafields) {
325             foreach ($extrafields as $e) {
326                 if ($e === 'id' or property_exists($return, $e)) {
327                     continue;
328                 }
329                 $return->{$e} = $record->{$fieldprefix.$e};
330             }
331         }
333         return $return;
334     }
336     /**
337      * Works out the URL for the users picture.
338      *
339      * This method is recommended as it avoids costly redirects of user pictures
340      * if requests are made for non-existent files etc.
341      *
342      * @param moodle_page $page
343      * @param renderer_base $renderer
344      * @return moodle_url
345      */
346     public function get_url(moodle_page $page, renderer_base $renderer = null) {
347         global $CFG;
349         if (is_null($renderer)) {
350             $renderer = $page->get_renderer('core');
351         }
353         // Sort out the filename and size. Size is only required for the gravatar
354         // implementation presently.
355         if (empty($this->size)) {
356             $filename = 'f2';
357             $size = 35;
358         } else if ($this->size === true or $this->size == 1) {
359             $filename = 'f1';
360             $size = 100;
361         } else if ($this->size > 100) {
362             $filename = 'f3';
363             $size = (int)$this->size;
364         } else if ($this->size >= 50) {
365             $filename = 'f1';
366             $size = (int)$this->size;
367         } else {
368             $filename = 'f2';
369             $size = (int)$this->size;
370         }
372         $defaulturl = $renderer->image_url('u/'.$filename); // default image
374         if ((!empty($CFG->forcelogin) and !isloggedin()) ||
375             (!empty($CFG->forceloginforprofileimage) && (!isloggedin() || isguestuser()))) {
376             // Protect images if login required and not logged in;
377             // also if login is required for profile images and is not logged in or guest
378             // do not use require_login() because it is expensive and not suitable here anyway.
379             return $defaulturl;
380         }
382         // First try to detect deleted users - but do not read from database for performance reasons!
383         if (!empty($this->user->deleted) or strpos($this->user->email, '@') === false) {
384             // All deleted users should have email replaced by md5 hash,
385             // all active users are expected to have valid email.
386             return $defaulturl;
387         }
389         // Did the user upload a picture?
390         if ($this->user->picture > 0) {
391             if (!empty($this->user->contextid)) {
392                 $contextid = $this->user->contextid;
393             } else {
394                 $context = context_user::instance($this->user->id, IGNORE_MISSING);
395                 if (!$context) {
396                     // This must be an incorrectly deleted user, all other users have context.
397                     return $defaulturl;
398                 }
399                 $contextid = $context->id;
400             }
402             $path = '/';
403             if (clean_param($page->theme->name, PARAM_THEME) == $page->theme->name) {
404                 // We append the theme name to the file path if we have it so that
405                 // in the circumstance that the profile picture is not available
406                 // when the user actually requests it they still get the profile
407                 // picture for the correct theme.
408                 $path .= $page->theme->name.'/';
409             }
410             // Set the image URL to the URL for the uploaded file and return.
411             $url = moodle_url::make_pluginfile_url(
412                     $contextid, 'user', 'icon', null, $path, $filename, false, $this->includetoken);
413             $url->param('rev', $this->user->picture);
414             return $url;
415         }
417         if ($this->user->picture == 0 and !empty($CFG->enablegravatar)) {
418             // Normalise the size variable to acceptable bounds
419             if ($size < 1 || $size > 512) {
420                 $size = 35;
421             }
422             // Hash the users email address
423             $md5 = md5(strtolower(trim($this->user->email)));
424             // Build a gravatar URL with what we know.
426             // Find the best default image URL we can (MDL-35669)
427             if (empty($CFG->gravatardefaulturl)) {
428                 $absoluteimagepath = $page->theme->resolve_image_location('u/'.$filename, 'core');
429                 if (strpos($absoluteimagepath, $CFG->dirroot) === 0) {
430                     $gravatardefault = $CFG->wwwroot . substr($absoluteimagepath, strlen($CFG->dirroot));
431                 } else {
432                     $gravatardefault = $CFG->wwwroot . '/pix/u/' . $filename . '.png';
433                 }
434             } else {
435                 $gravatardefault = $CFG->gravatardefaulturl;
436             }
438             // If the currently requested page is https then we'll return an
439             // https gravatar page.
440             if (is_https()) {
441                 return new moodle_url("https://secure.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault));
442             } else {
443                 return new moodle_url("http://www.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault));
444             }
445         }
447         return $defaulturl;
448     }
451 /**
452  * Data structure representing a help icon.
453  *
454  * @copyright 2010 Petr Skoda (info@skodak.org)
455  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
456  * @since Moodle 2.0
457  * @package core
458  * @category output
459  */
460 class help_icon implements renderable, templatable {
462     /**
463      * @var string lang pack identifier (without the "_help" suffix),
464      * both get_string($identifier, $component) and get_string($identifier.'_help', $component)
465      * must exist.
466      */
467     public $identifier;
469     /**
470      * @var string Component name, the same as in get_string()
471      */
472     public $component;
474     /**
475      * @var string Extra descriptive text next to the icon
476      */
477     public $linktext = null;
479     /**
480      * Constructor
481      *
482      * @param string $identifier string for help page title,
483      *  string with _help suffix is used for the actual help text.
484      *  string with _link suffix is used to create a link to further info (if it exists)
485      * @param string $component
486      */
487     public function __construct($identifier, $component) {
488         $this->identifier = $identifier;
489         $this->component  = $component;
490     }
492     /**
493      * Verifies that both help strings exists, shows debug warnings if not
494      */
495     public function diag_strings() {
496         $sm = get_string_manager();
497         if (!$sm->string_exists($this->identifier, $this->component)) {
498             debugging("Help title string does not exist: [$this->identifier, $this->component]");
499         }
500         if (!$sm->string_exists($this->identifier.'_help', $this->component)) {
501             debugging("Help contents string does not exist: [{$this->identifier}_help, $this->component]");
502         }
503     }
505     /**
506      * Export this data so it can be used as the context for a mustache template.
507      *
508      * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
509      * @return array
510      */
511     public function export_for_template(renderer_base $output) {
512         global $CFG;
514         $title = get_string($this->identifier, $this->component);
516         if (empty($this->linktext)) {
517             $alt = get_string('helpprefix2', '', trim($title, ". \t"));
518         } else {
519             $alt = get_string('helpwiththis');
520         }
522         $data = get_formatted_help_string($this->identifier, $this->component, false);
524         $data->alt = $alt;
525         $data->icon = (new pix_icon('help', $alt, 'core', ['class' => 'iconhelp']))->export_for_template($output);
526         $data->linktext = $this->linktext;
527         $data->title = get_string('helpprefix2', '', trim($title, ". \t"));
529         $options = [
530             'component' => $this->component,
531             'identifier' => $this->identifier,
532             'lang' => current_language()
533         ];
535         // Debugging feature lets you display string identifier and component.
536         if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
537             $options['strings'] = 1;
538         }
540         $data->url = (new moodle_url('/help.php', $options))->out(false);
541         $data->ltr = !right_to_left();
542         return $data;
543     }
547 /**
548  * Data structure representing an icon font.
549  *
550  * @copyright 2016 Damyon Wiese
551  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
552  * @package core
553  * @category output
554  */
555 class pix_icon_font implements templatable {
557     /**
558      * @var pix_icon $pixicon The original icon.
559      */
560     private $pixicon = null;
562     /**
563      * @var string $key The mapped key.
564      */
565     private $key;
567     /**
568      * @var bool $mapped The icon could not be mapped.
569      */
570     private $mapped;
572     /**
573      * Constructor
574      *
575      * @param pix_icon $pixicon The original icon
576      */
577     public function __construct(pix_icon $pixicon) {
578         global $PAGE;
580         $this->pixicon = $pixicon;
581         $this->mapped = false;
582         $iconsystem = \core\output\icon_system::instance();
584         $this->key = $iconsystem->remap_icon_name($pixicon->pix, $pixicon->component);
585         if (!empty($this->key)) {
586             $this->mapped = true;
587         }
588     }
590     /**
591      * Return true if this pix_icon was successfully mapped to an icon font.
592      *
593      * @return bool
594      */
595     public function is_mapped() {
596         return $this->mapped;
597     }
599     /**
600      * Export this data so it can be used as the context for a mustache template.
601      *
602      * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
603      * @return array
604      */
605     public function export_for_template(renderer_base $output) {
607         $pixdata = $this->pixicon->export_for_template($output);
609         $title = isset($this->pixicon->attributes['title']) ? $this->pixicon->attributes['title'] : '';
610         $alt = isset($this->pixicon->attributes['alt']) ? $this->pixicon->attributes['alt'] : '';
611         if (empty($title)) {
612             $title = $alt;
613         }
614         $data = array(
615             'extraclasses' => $pixdata['extraclasses'],
616             'title' => $title,
617             'alt' => $alt,
618             'key' => $this->key
619         );
621         return $data;
622     }
625 /**
626  * Data structure representing an icon subtype.
627  *
628  * @copyright 2016 Damyon Wiese
629  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
630  * @package core
631  * @category output
632  */
633 class pix_icon_fontawesome extends pix_icon_font {
637 /**
638  * Data structure representing an icon.
639  *
640  * @copyright 2010 Petr Skoda
641  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
642  * @since Moodle 2.0
643  * @package core
644  * @category output
645  */
646 class pix_icon implements renderable, templatable {
648     /**
649      * @var string The icon name
650      */
651     var $pix;
653     /**
654      * @var string The component the icon belongs to.
655      */
656     var $component;
658     /**
659      * @var array An array of attributes to use on the icon
660      */
661     var $attributes = array();
663     /**
664      * Constructor
665      *
666      * @param string $pix short icon name
667      * @param string $alt The alt text to use for the icon
668      * @param string $component component name
669      * @param array $attributes html attributes
670      */
671     public function __construct($pix, $alt, $component='moodle', array $attributes = null) {
672         global $PAGE;
674         $this->pix = $pix;
675         $this->component  = $component;
676         $this->attributes = (array)$attributes;
678         if (empty($this->attributes['class'])) {
679             $this->attributes['class'] = '';
680         }
682         // Set an additional class for big icons so that they can be styled properly.
683         if (substr($pix, 0, 2) === 'b/') {
684             $this->attributes['class'] .= ' iconsize-big';
685         }
687         // If the alt is empty, don't place it in the attributes, otherwise it will override parent alt text.
688         if (!is_null($alt)) {
689             $this->attributes['alt'] = $alt;
691             // If there is no title, set it to the attribute.
692             if (!isset($this->attributes['title'])) {
693                 $this->attributes['title'] = $this->attributes['alt'];
694             }
695         } else {
696             unset($this->attributes['alt']);
697         }
699         if (empty($this->attributes['title'])) {
700             // Remove the title attribute if empty, we probably want to use the parent node's title
701             // and some browsers might overwrite it with an empty title.
702             unset($this->attributes['title']);
703         }
705         // Hide icons from screen readers that have no alt.
706         if (empty($this->attributes['alt'])) {
707             $this->attributes['aria-hidden'] = 'true';
708         }
709     }
711     /**
712      * Export this data so it can be used as the context for a mustache template.
713      *
714      * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
715      * @return array
716      */
717     public function export_for_template(renderer_base $output) {
718         $attributes = $this->attributes;
719         $extraclasses = '';
721         foreach ($attributes as $key => $item) {
722             if ($key == 'class') {
723                 $extraclasses = $item;
724                 unset($attributes[$key]);
725                 break;
726             }
727         }
729         $attributes['src'] = $output->image_url($this->pix, $this->component)->out(false);
730         $templatecontext = array();
731         foreach ($attributes as $name => $value) {
732             $templatecontext[] = array('name' => $name, 'value' => $value);
733         }
734         $title = isset($attributes['title']) ? $attributes['title'] : '';
735         if (empty($title)) {
736             $title = isset($attributes['alt']) ? $attributes['alt'] : '';
737         }
738         $data = array(
739             'attributes' => $templatecontext,
740             'extraclasses' => $extraclasses
741         );
743         return $data;
744     }
746     /**
747      * Much simpler version of export that will produce the data required to render this pix with the
748      * pix helper in a mustache tag.
749      *
750      * @return array
751      */
752     public function export_for_pix() {
753         $title = isset($this->attributes['title']) ? $this->attributes['title'] : '';
754         if (empty($title)) {
755             $title = isset($this->attributes['alt']) ? $this->attributes['alt'] : '';
756         }
757         return [
758             'key' => $this->pix,
759             'component' => $this->component,
760             'title' => $title
761         ];
762     }
765 /**
766  * Data structure representing an activity icon.
767  *
768  * The difference is that activity icons will always render with the standard icon system (no font icons).
769  *
770  * @copyright 2017 Damyon Wiese
771  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
772  * @package core
773  */
774 class image_icon extends pix_icon {
777 /**
778  * Data structure representing an emoticon image
779  *
780  * @copyright 2010 David Mudrak
781  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
782  * @since Moodle 2.0
783  * @package core
784  * @category output
785  */
786 class pix_emoticon extends pix_icon implements renderable {
788     /**
789      * Constructor
790      * @param string $pix short icon name
791      * @param string $alt alternative text
792      * @param string $component emoticon image provider
793      * @param array $attributes explicit HTML attributes
794      */
795     public function __construct($pix, $alt, $component = 'moodle', array $attributes = array()) {
796         if (empty($attributes['class'])) {
797             $attributes['class'] = 'emoticon';
798         }
799         parent::__construct($pix, $alt, $component, $attributes);
800     }
803 /**
804  * Data structure representing a simple form with only one button.
805  *
806  * @copyright 2009 Petr Skoda
807  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
808  * @since Moodle 2.0
809  * @package core
810  * @category output
811  */
812 class single_button implements renderable {
814     /**
815      * @var moodle_url Target url
816      */
817     public $url;
819     /**
820      * @var string Button label
821      */
822     public $label;
824     /**
825      * @var string Form submit method post or get
826      */
827     public $method = 'post';
829     /**
830      * @var string Wrapping div class
831      */
832     public $class = 'singlebutton';
834     /**
835      * @var bool True if button is primary button. Used for styling.
836      */
837     public $primary = false;
839     /**
840      * @var bool True if button disabled, false if normal
841      */
842     public $disabled = false;
844     /**
845      * @var string Button tooltip
846      */
847     public $tooltip = null;
849     /**
850      * @var string Form id
851      */
852     public $formid;
854     /**
855      * @var array List of attached actions
856      */
857     public $actions = array();
859     /**
860      * @var array $params URL Params
861      */
862     public $params;
864     /**
865      * @var string Action id
866      */
867     public $actionid;
869     /**
870      * Constructor
871      * @param moodle_url $url
872      * @param string $label button text
873      * @param string $method get or post submit method
874      */
875     public function __construct(moodle_url $url, $label, $method='post', $primary=false) {
876         $this->url    = clone($url);
877         $this->label  = $label;
878         $this->method = $method;
879         $this->primary = $primary;
880     }
882     /**
883      * Shortcut for adding a JS confirm dialog when the button is clicked.
884      * The message must be a yes/no question.
885      *
886      * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur.
887      */
888     public function add_confirm_action($confirmmessage) {
889         $this->add_action(new confirm_action($confirmmessage));
890     }
892     /**
893      * Add action to the button.
894      * @param component_action $action
895      */
896     public function add_action(component_action $action) {
897         $this->actions[] = $action;
898     }
900     /**
901      * Export data.
902      *
903      * @param renderer_base $output Renderer.
904      * @return stdClass
905      */
906     public function export_for_template(renderer_base $output) {
907         $url = $this->method === 'get' ? $this->url->out_omit_querystring(true) : $this->url->out_omit_querystring();
909         $data = new stdClass();
910         $data->id = html_writer::random_id('single_button');
911         $data->formid = $this->formid;
912         $data->method = $this->method;
913         $data->url = $url === '' ? '#' : $url;
914         $data->label = $this->label;
915         $data->classes = $this->class;
916         $data->disabled = $this->disabled;
917         $data->tooltip = $this->tooltip;
918         $data->primary = $this->primary;
920         // Form parameters.
921         $params = $this->url->params();
922         if ($this->method === 'post') {
923             $params['sesskey'] = sesskey();
924         }
925         $data->params = array_map(function($key) use ($params) {
926             return ['name' => $key, 'value' => $params[$key]];
927         }, array_keys($params));
929         // Button actions.
930         $actions = $this->actions;
931         $data->actions = array_map(function($action) use ($output) {
932             return $action->export_for_template($output);
933         }, $actions);
934         $data->hasactions = !empty($data->actions);
936         return $data;
937     }
941 /**
942  * Simple form with just one select field that gets submitted automatically.
943  *
944  * If JS not enabled small go button is printed too.
945  *
946  * @copyright 2009 Petr Skoda
947  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
948  * @since Moodle 2.0
949  * @package core
950  * @category output
951  */
952 class single_select implements renderable, templatable {
954     /**
955      * @var moodle_url Target url - includes hidden fields
956      */
957     var $url;
959     /**
960      * @var string Name of the select element.
961      */
962     var $name;
964     /**
965      * @var array $options associative array value=>label ex.: array(1=>'One, 2=>Two)
966      *     it is also possible to specify optgroup as complex label array ex.:
967      *         array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
968      *         array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
969      */
970     var $options;
972     /**
973      * @var string Selected option
974      */
975     var $selected;
977     /**
978      * @var array Nothing selected
979      */
980     var $nothing;
982     /**
983      * @var array Extra select field attributes
984      */
985     var $attributes = array();
987     /**
988      * @var string Button label
989      */
990     var $label = '';
992     /**
993      * @var array Button label's attributes
994      */
995     var $labelattributes = array();
997     /**
998      * @var string Form submit method post or get
999      */
1000     var $method = 'get';
1002     /**
1003      * @var string Wrapping div class
1004      */
1005     var $class = 'singleselect';
1007     /**
1008      * @var bool True if button disabled, false if normal
1009      */
1010     var $disabled = false;
1012     /**
1013      * @var string Button tooltip
1014      */
1015     var $tooltip = null;
1017     /**
1018      * @var string Form id
1019      */
1020     var $formid = null;
1022     /**
1023      * @var array List of attached actions
1024      */
1025     var $helpicon = null;
1027     /**
1028      * Constructor
1029      * @param moodle_url $url form action target, includes hidden fields
1030      * @param string $name name of selection field - the changing parameter in url
1031      * @param array $options list of options
1032      * @param string $selected selected element
1033      * @param array $nothing
1034      * @param string $formid
1035      */
1036     public function __construct(moodle_url $url, $name, array $options, $selected = '', $nothing = array('' => 'choosedots'), $formid = null) {
1037         $this->url      = $url;
1038         $this->name     = $name;
1039         $this->options  = $options;
1040         $this->selected = $selected;
1041         $this->nothing  = $nothing;
1042         $this->formid   = $formid;
1043     }
1045     /**
1046      * Shortcut for adding a JS confirm dialog when the button is clicked.
1047      * The message must be a yes/no question.
1048      *
1049      * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur.
1050      */
1051     public function add_confirm_action($confirmmessage) {
1052         $this->add_action(new component_action('submit', 'M.util.show_confirm_dialog', array('message' => $confirmmessage)));
1053     }
1055     /**
1056      * Add action to the button.
1057      *
1058      * @param component_action $action
1059      */
1060     public function add_action(component_action $action) {
1061         $this->actions[] = $action;
1062     }
1064     /**
1065      * Adds help icon.
1066      *
1067      * @deprecated since Moodle 2.0
1068      */
1069     public function set_old_help_icon($helppage, $title, $component = 'moodle') {
1070         throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().');
1071     }
1073     /**
1074      * Adds help icon.
1075      *
1076      * @param string $identifier The keyword that defines a help page
1077      * @param string $component
1078      */
1079     public function set_help_icon($identifier, $component = 'moodle') {
1080         $this->helpicon = new help_icon($identifier, $component);
1081     }
1083     /**
1084      * Sets select's label
1085      *
1086      * @param string $label
1087      * @param array $attributes (optional)
1088      */
1089     public function set_label($label, $attributes = array()) {
1090         $this->label = $label;
1091         $this->labelattributes = $attributes;
1093     }
1095     /**
1096      * Export data.
1097      *
1098      * @param renderer_base $output Renderer.
1099      * @return stdClass
1100      */
1101     public function export_for_template(renderer_base $output) {
1102         $attributes = $this->attributes;
1104         $data = new stdClass();
1105         $data->name = $this->name;
1106         $data->method = $this->method;
1107         $data->action = $this->method === 'get' ? $this->url->out_omit_querystring(true) : $this->url->out_omit_querystring();
1108         $data->classes = $this->class;
1109         $data->label = $this->label;
1110         $data->disabled = $this->disabled;
1111         $data->title = $this->tooltip;
1112         $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('single_select_f');
1113         $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('single_select');
1114         unset($attributes['id']);
1116         // Form parameters.
1117         $params = $this->url->params();
1118         if ($this->method === 'post') {
1119             $params['sesskey'] = sesskey();
1120         }
1121         $data->params = array_map(function($key) use ($params) {
1122             return ['name' => $key, 'value' => $params[$key]];
1123         }, array_keys($params));
1125         // Select options.
1126         $hasnothing = false;
1127         if (is_string($this->nothing) && $this->nothing !== '') {
1128             $nothing = ['' => $this->nothing];
1129             $hasnothing = true;
1130             $nothingkey = '';
1131         } else if (is_array($this->nothing)) {
1132             $nothingvalue = reset($this->nothing);
1133             if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') {
1134                 $nothing = [key($this->nothing) => get_string('choosedots')];
1135             } else {
1136                 $nothing = $this->nothing;
1137             }
1138             $hasnothing = true;
1139             $nothingkey = key($this->nothing);
1140         }
1141         if ($hasnothing) {
1142             $options = $nothing + $this->options;
1143         } else {
1144             $options = $this->options;
1145         }
1147         foreach ($options as $value => $name) {
1148             if (is_array($options[$value])) {
1149                 foreach ($options[$value] as $optgroupname => $optgroupvalues) {
1150                     $sublist = [];
1151                     foreach ($optgroupvalues as $optvalue => $optname) {
1152                         $option = [
1153                             'value' => $optvalue,
1154                             'name' => $optname,
1155                             'selected' => strval($this->selected) === strval($optvalue),
1156                         ];
1158                         if ($hasnothing && $nothingkey === $optvalue) {
1159                             $option['ignore'] = 'data-ignore';
1160                         }
1162                         $sublist[] = $option;
1163                     }
1164                     $data->options[] = [
1165                         'name' => $optgroupname,
1166                         'optgroup' => true,
1167                         'options' => $sublist
1168                     ];
1169                 }
1170             } else {
1171                 $option = [
1172                     'value' => $value,
1173                     'name' => $options[$value],
1174                     'selected' => strval($this->selected) === strval($value),
1175                     'optgroup' => false
1176                 ];
1178                 if ($hasnothing && $nothingkey === $value) {
1179                     $option['ignore'] = 'data-ignore';
1180                 }
1182                 $data->options[] = $option;
1183             }
1184         }
1186         // Label attributes.
1187         $data->labelattributes = [];
1188         foreach ($this->labelattributes as $key => $value) {
1189             $data->labelattributes = ['name' => $key, 'value' => $value];
1190         }
1192         // Help icon.
1193         $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false;
1195         return $data;
1196     }
1199 /**
1200  * Simple URL selection widget description.
1201  *
1202  * @copyright 2009 Petr Skoda
1203  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1204  * @since Moodle 2.0
1205  * @package core
1206  * @category output
1207  */
1208 class url_select implements renderable, templatable {
1209     /**
1210      * @var array $urls associative array value=>label ex.: array(1=>'One, 2=>Two)
1211      *     it is also possible to specify optgroup as complex label array ex.:
1212      *         array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
1213      *         array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
1214      */
1215     var $urls;
1217     /**
1218      * @var string Selected option
1219      */
1220     var $selected;
1222     /**
1223      * @var array Nothing selected
1224      */
1225     var $nothing;
1227     /**
1228      * @var array Extra select field attributes
1229      */
1230     var $attributes = array();
1232     /**
1233      * @var string Button label
1234      */
1235     var $label = '';
1237     /**
1238      * @var array Button label's attributes
1239      */
1240     var $labelattributes = array();
1242     /**
1243      * @var string Wrapping div class
1244      */
1245     var $class = 'urlselect';
1247     /**
1248      * @var bool True if button disabled, false if normal
1249      */
1250     var $disabled = false;
1252     /**
1253      * @var string Button tooltip
1254      */
1255     var $tooltip = null;
1257     /**
1258      * @var string Form id
1259      */
1260     var $formid = null;
1262     /**
1263      * @var array List of attached actions
1264      */
1265     var $helpicon = null;
1267     /**
1268      * @var string If set, makes button visible with given name for button
1269      */
1270     var $showbutton = null;
1272     /**
1273      * Constructor
1274      * @param array $urls list of options
1275      * @param string $selected selected element
1276      * @param array $nothing
1277      * @param string $formid
1278      * @param string $showbutton Set to text of button if it should be visible
1279      *   or null if it should be hidden (hidden version always has text 'go')
1280      */
1281     public function __construct(array $urls, $selected = '', $nothing = array('' => 'choosedots'), $formid = null, $showbutton = null) {
1282         $this->urls       = $urls;
1283         $this->selected   = $selected;
1284         $this->nothing    = $nothing;
1285         $this->formid     = $formid;
1286         $this->showbutton = $showbutton;
1287     }
1289     /**
1290      * Adds help icon.
1291      *
1292      * @deprecated since Moodle 2.0
1293      */
1294     public function set_old_help_icon($helppage, $title, $component = 'moodle') {
1295         throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().');
1296     }
1298     /**
1299      * Adds help icon.
1300      *
1301      * @param string $identifier The keyword that defines a help page
1302      * @param string $component
1303      */
1304     public function set_help_icon($identifier, $component = 'moodle') {
1305         $this->helpicon = new help_icon($identifier, $component);
1306     }
1308     /**
1309      * Sets select's label
1310      *
1311      * @param string $label
1312      * @param array $attributes (optional)
1313      */
1314     public function set_label($label, $attributes = array()) {
1315         $this->label = $label;
1316         $this->labelattributes = $attributes;
1317     }
1319     /**
1320      * Clean a URL.
1321      *
1322      * @param string $value The URL.
1323      * @return The cleaned URL.
1324      */
1325     protected function clean_url($value) {
1326         global $CFG;
1328         if (empty($value)) {
1329             // Nothing.
1331         } else if (strpos($value, $CFG->wwwroot . '/') === 0) {
1332             $value = str_replace($CFG->wwwroot, '', $value);
1334         } else if (strpos($value, '/') !== 0) {
1335             debugging("Invalid url_select urls parameter: url '$value' is not local relative url!", DEBUG_DEVELOPER);
1336         }
1338         return $value;
1339     }
1341     /**
1342      * Flatten the options for Mustache.
1343      *
1344      * This also cleans the URLs.
1345      *
1346      * @param array $options The options.
1347      * @param array $nothing The nothing option.
1348      * @return array
1349      */
1350     protected function flatten_options($options, $nothing) {
1351         $flattened = [];
1353         foreach ($options as $value => $option) {
1354             if (is_array($option)) {
1355                 foreach ($option as $groupname => $optoptions) {
1356                     if (!isset($flattened[$groupname])) {
1357                         $flattened[$groupname] = [
1358                             'name' => $groupname,
1359                             'isgroup' => true,
1360                             'options' => []
1361                         ];
1362                     }
1363                     foreach ($optoptions as $optvalue => $optoption) {
1364                         $cleanedvalue = $this->clean_url($optvalue);
1365                         $flattened[$groupname]['options'][$cleanedvalue] = [
1366                             'name' => $optoption,
1367                             'value' => $cleanedvalue,
1368                             'selected' => $this->selected == $optvalue,
1369                         ];
1370                     }
1371                 }
1373             } else {
1374                 $cleanedvalue = $this->clean_url($value);
1375                 $flattened[$cleanedvalue] = [
1376                     'name' => $option,
1377                     'value' => $cleanedvalue,
1378                     'selected' => $this->selected == $value,
1379                 ];
1380             }
1381         }
1383         if (!empty($nothing)) {
1384             $value = key($nothing);
1385             $name = reset($nothing);
1386             $flattened = [
1387                 $value => ['name' => $name, 'value' => $value, 'selected' => $this->selected == $value]
1388             ] + $flattened;
1389         }
1391         // Make non-associative array.
1392         foreach ($flattened as $key => $value) {
1393             if (!empty($value['options'])) {
1394                 $flattened[$key]['options'] = array_values($value['options']);
1395             }
1396         }
1397         $flattened = array_values($flattened);
1399         return $flattened;
1400     }
1402     /**
1403      * Export for template.
1404      *
1405      * @param renderer_base $output Renderer.
1406      * @return stdClass
1407      */
1408     public function export_for_template(renderer_base $output) {
1409         $attributes = $this->attributes;
1411         $data = new stdClass();
1412         $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('url_select_f');
1413         $data->classes = $this->class;
1414         $data->label = $this->label;
1415         $data->disabled = $this->disabled;
1416         $data->title = $this->tooltip;
1417         $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('url_select');
1418         $data->sesskey = sesskey();
1419         $data->action = (new moodle_url('/course/jumpto.php'))->out(false);
1421         // Remove attributes passed as property directly.
1422         unset($attributes['class']);
1423         unset($attributes['id']);
1424         unset($attributes['name']);
1425         unset($attributes['title']);
1427         $data->showbutton = $this->showbutton;
1429         // Select options.
1430         $nothing = false;
1431         if (is_string($this->nothing) && $this->nothing !== '') {
1432             $nothing = ['' => $this->nothing];
1433         } else if (is_array($this->nothing)) {
1434             $nothingvalue = reset($this->nothing);
1435             if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') {
1436                 $nothing = [key($this->nothing) => get_string('choosedots')];
1437             } else {
1438                 $nothing = $this->nothing;
1439             }
1440         }
1441         $data->options = $this->flatten_options($this->urls, $nothing);
1443         // Label attributes.
1444         $data->labelattributes = [];
1445         foreach ($this->labelattributes as $key => $value) {
1446             $data->labelattributes[] = ['name' => $key, 'value' => $value];
1447         }
1449         // Help icon.
1450         $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false;
1452         // Finally all the remaining attributes.
1453         $data->attributes = [];
1454         foreach ($this->attributes as $key => $value) {
1455             $data->attributes = ['name' => $key, 'value' => $value];
1456         }
1458         return $data;
1459     }
1462 /**
1463  * Data structure describing html link with special action attached.
1464  *
1465  * @copyright 2010 Petr Skoda
1466  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1467  * @since Moodle 2.0
1468  * @package core
1469  * @category output
1470  */
1471 class action_link implements renderable {
1473     /**
1474      * @var moodle_url Href url
1475      */
1476     public $url;
1478     /**
1479      * @var string Link text HTML fragment
1480      */
1481     public $text;
1483     /**
1484      * @var array HTML attributes
1485      */
1486     public $attributes;
1488     /**
1489      * @var array List of actions attached to link
1490      */
1491     public $actions;
1493     /**
1494      * @var pix_icon Optional pix icon to render with the link
1495      */
1496     public $icon;
1498     /**
1499      * Constructor
1500      * @param moodle_url $url
1501      * @param string $text HTML fragment
1502      * @param component_action $action
1503      * @param array $attributes associative array of html link attributes + disabled
1504      * @param pix_icon $icon optional pix_icon to render with the link text
1505      */
1506     public function __construct(moodle_url $url,
1507                                 $text,
1508                                 component_action $action=null,
1509                                 array $attributes=null,
1510                                 pix_icon $icon=null) {
1511         $this->url = clone($url);
1512         $this->text = $text;
1513         $this->attributes = (array)$attributes;
1514         if ($action) {
1515             $this->add_action($action);
1516         }
1517         $this->icon = $icon;
1518     }
1520     /**
1521      * Add action to the link.
1522      *
1523      * @param component_action $action
1524      */
1525     public function add_action(component_action $action) {
1526         $this->actions[] = $action;
1527     }
1529     /**
1530      * Adds a CSS class to this action link object
1531      * @param string $class
1532      */
1533     public function add_class($class) {
1534         if (empty($this->attributes['class'])) {
1535             $this->attributes['class'] = $class;
1536         } else {
1537             $this->attributes['class'] .= ' ' . $class;
1538         }
1539     }
1541     /**
1542      * Returns true if the specified class has been added to this link.
1543      * @param string $class
1544      * @return bool
1545      */
1546     public function has_class($class) {
1547         return strpos(' ' . $this->attributes['class'] . ' ', ' ' . $class . ' ') !== false;
1548     }
1550     /**
1551      * Return the rendered HTML for the icon. Useful for rendering action links in a template.
1552      * @return string
1553      */
1554     public function get_icon_html() {
1555         global $OUTPUT;
1556         if (!$this->icon) {
1557             return '';
1558         }
1559         return $OUTPUT->render($this->icon);
1560     }
1562     /**
1563      * Export for template.
1564      *
1565      * @param renderer_base $output The renderer.
1566      * @return stdClass
1567      */
1568     public function export_for_template(renderer_base $output) {
1569         $data = new stdClass();
1570         $attributes = $this->attributes;
1572         if (empty($attributes['id'])) {
1573             $attributes['id'] = html_writer::random_id('action_link');
1574         }
1575         $data->id = $attributes['id'];
1576         unset($attributes['id']);
1578         $data->disabled = !empty($attributes['disabled']);
1579         unset($attributes['disabled']);
1581         $data->text = $this->text instanceof renderable ? $output->render($this->text) : (string) $this->text;
1582         $data->url = $this->url ? $this->url->out(false) : '';
1583         $data->icon = $this->icon ? $this->icon->export_for_pix() : null;
1584         $data->classes = isset($attributes['class']) ? $attributes['class'] : '';
1585         unset($attributes['class']);
1587         $data->attributes = array_map(function($key, $value) {
1588             return [
1589                 'name' => $key,
1590                 'value' => $value
1591             ];
1592         }, array_keys($attributes), $attributes);
1594         $data->actions = array_map(function($action) use ($output) {
1595             return $action->export_for_template($output);
1596         }, !empty($this->actions) ? $this->actions : []);
1597         $data->hasactions = !empty($this->actions);
1599         return $data;
1600     }
1603 /**
1604  * Simple html output class
1605  *
1606  * @copyright 2009 Tim Hunt, 2010 Petr Skoda
1607  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1608  * @since Moodle 2.0
1609  * @package core
1610  * @category output
1611  */
1612 class html_writer {
1614     /**
1615      * Outputs a tag with attributes and contents
1616      *
1617      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1618      * @param string $contents What goes between the opening and closing tags
1619      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1620      * @return string HTML fragment
1621      */
1622     public static function tag($tagname, $contents, array $attributes = null) {
1623         return self::start_tag($tagname, $attributes) . $contents . self::end_tag($tagname);
1624     }
1626     /**
1627      * Outputs an opening tag with attributes
1628      *
1629      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1630      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1631      * @return string HTML fragment
1632      */
1633     public static function start_tag($tagname, array $attributes = null) {
1634         return '<' . $tagname . self::attributes($attributes) . '>';
1635     }
1637     /**
1638      * Outputs a closing tag
1639      *
1640      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1641      * @return string HTML fragment
1642      */
1643     public static function end_tag($tagname) {
1644         return '</' . $tagname . '>';
1645     }
1647     /**
1648      * Outputs an empty tag with attributes
1649      *
1650      * @param string $tagname The name of tag ('input', 'img', 'br' etc.)
1651      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1652      * @return string HTML fragment
1653      */
1654     public static function empty_tag($tagname, array $attributes = null) {
1655         return '<' . $tagname . self::attributes($attributes) . ' />';
1656     }
1658     /**
1659      * Outputs a tag, but only if the contents are not empty
1660      *
1661      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
1662      * @param string $contents What goes between the opening and closing tags
1663      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1664      * @return string HTML fragment
1665      */
1666     public static function nonempty_tag($tagname, $contents, array $attributes = null) {
1667         if ($contents === '' || is_null($contents)) {
1668             return '';
1669         }
1670         return self::tag($tagname, $contents, $attributes);
1671     }
1673     /**
1674      * Outputs a HTML attribute and value
1675      *
1676      * @param string $name The name of the attribute ('src', 'href', 'class' etc.)
1677      * @param string $value The value of the attribute. The value will be escaped with {@link s()}
1678      * @return string HTML fragment
1679      */
1680     public static function attribute($name, $value) {
1681         if ($value instanceof moodle_url) {
1682             return ' ' . $name . '="' . $value->out() . '"';
1683         }
1685         // special case, we do not want these in output
1686         if ($value === null) {
1687             return '';
1688         }
1690         // no sloppy trimming here!
1691         return ' ' . $name . '="' . s($value) . '"';
1692     }
1694     /**
1695      * Outputs a list of HTML attributes and values
1696      *
1697      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
1698      *       The values will be escaped with {@link s()}
1699      * @return string HTML fragment
1700      */
1701     public static function attributes(array $attributes = null) {
1702         $attributes = (array)$attributes;
1703         $output = '';
1704         foreach ($attributes as $name => $value) {
1705             $output .= self::attribute($name, $value);
1706         }
1707         return $output;
1708     }
1710     /**
1711      * Generates a simple image tag with attributes.
1712      *
1713      * @param string $src The source of image
1714      * @param string $alt The alternate text for image
1715      * @param array $attributes The tag attributes (array('height' => $max_height, 'class' => 'class1') etc.)
1716      * @return string HTML fragment
1717      */
1718     public static function img($src, $alt, array $attributes = null) {
1719         $attributes = (array)$attributes;
1720         $attributes['src'] = $src;
1721         $attributes['alt'] = $alt;
1723         return self::empty_tag('img', $attributes);
1724     }
1726     /**
1727      * Generates random html element id.
1728      *
1729      * @staticvar int $counter
1730      * @staticvar type $uniq
1731      * @param string $base A string fragment that will be included in the random ID.
1732      * @return string A unique ID
1733      */
1734     public static function random_id($base='random') {
1735         static $counter = 0;
1736         static $uniq;
1738         if (!isset($uniq)) {
1739             $uniq = uniqid();
1740         }
1742         $counter++;
1743         return $base.$uniq.$counter;
1744     }
1746     /**
1747      * Generates a simple html link
1748      *
1749      * @param string|moodle_url $url The URL
1750      * @param string $text The text
1751      * @param array $attributes HTML attributes
1752      * @return string HTML fragment
1753      */
1754     public static function link($url, $text, array $attributes = null) {
1755         $attributes = (array)$attributes;
1756         $attributes['href']  = $url;
1757         return self::tag('a', $text, $attributes);
1758     }
1760     /**
1761      * Generates a simple checkbox with optional label
1762      *
1763      * @param string $name The name of the checkbox
1764      * @param string $value The value of the checkbox
1765      * @param bool $checked Whether the checkbox is checked
1766      * @param string $label The label for the checkbox
1767      * @param array $attributes Any attributes to apply to the checkbox
1768      * @return string html fragment
1769      */
1770     public static function checkbox($name, $value, $checked = true, $label = '', array $attributes = null) {
1771         $attributes = (array)$attributes;
1772         $output = '';
1774         if ($label !== '' and !is_null($label)) {
1775             if (empty($attributes['id'])) {
1776                 $attributes['id'] = self::random_id('checkbox_');
1777             }
1778         }
1779         $attributes['type']    = 'checkbox';
1780         $attributes['value']   = $value;
1781         $attributes['name']    = $name;
1782         $attributes['checked'] = $checked ? 'checked' : null;
1784         $output .= self::empty_tag('input', $attributes);
1786         if ($label !== '' and !is_null($label)) {
1787             $output .= self::tag('label', $label, array('for'=>$attributes['id']));
1788         }
1790         return $output;
1791     }
1793     /**
1794      * Generates a simple select yes/no form field
1795      *
1796      * @param string $name name of select element
1797      * @param bool $selected
1798      * @param array $attributes - html select element attributes
1799      * @return string HTML fragment
1800      */
1801     public static function select_yes_no($name, $selected=true, array $attributes = null) {
1802         $options = array('1'=>get_string('yes'), '0'=>get_string('no'));
1803         return self::select($options, $name, $selected, null, $attributes);
1804     }
1806     /**
1807      * Generates a simple select form field
1808      *
1809      * @param array $options associative array value=>label ex.:
1810      *                array(1=>'One, 2=>Two)
1811      *              it is also possible to specify optgroup as complex label array ex.:
1812      *                array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two')))
1813      *                array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three')))
1814      * @param string $name name of select element
1815      * @param string|array $selected value or array of values depending on multiple attribute
1816      * @param array|bool $nothing add nothing selected option, or false of not added
1817      * @param array $attributes html select element attributes
1818      * @return string HTML fragment
1819      */
1820     public static function select(array $options, $name, $selected = '', $nothing = array('' => 'choosedots'), array $attributes = null) {
1821         $attributes = (array)$attributes;
1822         if (is_array($nothing)) {
1823             foreach ($nothing as $k=>$v) {
1824                 if ($v === 'choose' or $v === 'choosedots') {
1825                     $nothing[$k] = get_string('choosedots');
1826                 }
1827             }
1828             $options = $nothing + $options; // keep keys, do not override
1830         } else if (is_string($nothing) and $nothing !== '') {
1831             // BC
1832             $options = array(''=>$nothing) + $options;
1833         }
1835         // we may accept more values if multiple attribute specified
1836         $selected = (array)$selected;
1837         foreach ($selected as $k=>$v) {
1838             $selected[$k] = (string)$v;
1839         }
1841         if (!isset($attributes['id'])) {
1842             $id = 'menu'.$name;
1843             // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading
1844             $id = str_replace('[', '', $id);
1845             $id = str_replace(']', '', $id);
1846             $attributes['id'] = $id;
1847         }
1849         if (!isset($attributes['class'])) {
1850             $class = 'menu'.$name;
1851             // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading
1852             $class = str_replace('[', '', $class);
1853             $class = str_replace(']', '', $class);
1854             $attributes['class'] = $class;
1855         }
1856         $attributes['class'] = 'select custom-select ' . $attributes['class']; // Add 'select' selector always.
1858         $attributes['name'] = $name;
1860         if (!empty($attributes['disabled'])) {
1861             $attributes['disabled'] = 'disabled';
1862         } else {
1863             unset($attributes['disabled']);
1864         }
1866         $output = '';
1867         foreach ($options as $value=>$label) {
1868             if (is_array($label)) {
1869                 // ignore key, it just has to be unique
1870                 $output .= self::select_optgroup(key($label), current($label), $selected);
1871             } else {
1872                 $output .= self::select_option($label, $value, $selected);
1873             }
1874         }
1875         return self::tag('select', $output, $attributes);
1876     }
1878     /**
1879      * Returns HTML to display a select box option.
1880      *
1881      * @param string $label The label to display as the option.
1882      * @param string|int $value The value the option represents
1883      * @param array $selected An array of selected options
1884      * @return string HTML fragment
1885      */
1886     private static function select_option($label, $value, array $selected) {
1887         $attributes = array();
1888         $value = (string)$value;
1889         if (in_array($value, $selected, true)) {
1890             $attributes['selected'] = 'selected';
1891         }
1892         $attributes['value'] = $value;
1893         return self::tag('option', $label, $attributes);
1894     }
1896     /**
1897      * Returns HTML to display a select box option group.
1898      *
1899      * @param string $groupname The label to use for the group
1900      * @param array $options The options in the group
1901      * @param array $selected An array of selected values.
1902      * @return string HTML fragment.
1903      */
1904     private static function select_optgroup($groupname, $options, array $selected) {
1905         if (empty($options)) {
1906             return '';
1907         }
1908         $attributes = array('label'=>$groupname);
1909         $output = '';
1910         foreach ($options as $value=>$label) {
1911             $output .= self::select_option($label, $value, $selected);
1912         }
1913         return self::tag('optgroup', $output, $attributes);
1914     }
1916     /**
1917      * This is a shortcut for making an hour selector menu.
1918      *
1919      * @param string $type The type of selector (years, months, days, hours, minutes)
1920      * @param string $name fieldname
1921      * @param int $currenttime A default timestamp in GMT
1922      * @param int $step minute spacing
1923      * @param array $attributes - html select element attributes
1924      * @return HTML fragment
1925      */
1926     public static function select_time($type, $name, $currenttime = 0, $step = 5, array $attributes = null) {
1927         global $OUTPUT;
1929         if (!$currenttime) {
1930             $currenttime = time();
1931         }
1932         $calendartype = \core_calendar\type_factory::get_calendar_instance();
1933         $currentdate = $calendartype->timestamp_to_date_array($currenttime);
1934         $userdatetype = $type;
1935         $timeunits = array();
1937         switch ($type) {
1938             case 'years':
1939                 $timeunits = $calendartype->get_years();
1940                 $userdatetype = 'year';
1941                 break;
1942             case 'months':
1943                 $timeunits = $calendartype->get_months();
1944                 $userdatetype = 'month';
1945                 $currentdate['month'] = (int)$currentdate['mon'];
1946                 break;
1947             case 'days':
1948                 $timeunits = $calendartype->get_days();
1949                 $userdatetype = 'mday';
1950                 break;
1951             case 'hours':
1952                 for ($i=0; $i<=23; $i++) {
1953                     $timeunits[$i] = sprintf("%02d",$i);
1954                 }
1955                 break;
1956             case 'minutes':
1957                 if ($step != 1) {
1958                     $currentdate['minutes'] = ceil($currentdate['minutes']/$step)*$step;
1959                 }
1961                 for ($i=0; $i<=59; $i+=$step) {
1962                     $timeunits[$i] = sprintf("%02d",$i);
1963                 }
1964                 break;
1965             default:
1966                 throw new coding_exception("Time type $type is not supported by html_writer::select_time().");
1967         }
1969         $attributes = (array) $attributes;
1970         $data = (object) [
1971             'name' => $name,
1972             'id' => !empty($attributes['id']) ? $attributes['id'] : self::random_id('ts_'),
1973             'label' => get_string(substr($type, 0, -1), 'form'),
1974             'options' => array_map(function($value) use ($timeunits, $currentdate, $userdatetype) {
1975                 return [
1976                     'name' => $timeunits[$value],
1977                     'value' => $value,
1978                     'selected' => $currentdate[$userdatetype] == $value
1979                 ];
1980             }, array_keys($timeunits)),
1981         ];
1983         unset($attributes['id']);
1984         unset($attributes['name']);
1985         $data->attributes = array_map(function($name) use ($attributes) {
1986             return [
1987                 'name' => $name,
1988                 'value' => $attributes[$name]
1989             ];
1990         }, array_keys($attributes));
1992         return $OUTPUT->render_from_template('core/select_time', $data);
1993     }
1995     /**
1996      * Shortcut for quick making of lists
1997      *
1998      * Note: 'list' is a reserved keyword ;-)
1999      *
2000      * @param array $items
2001      * @param array $attributes
2002      * @param string $tag ul or ol
2003      * @return string
2004      */
2005     public static function alist(array $items, array $attributes = null, $tag = 'ul') {
2006         $output = html_writer::start_tag($tag, $attributes)."\n";
2007         foreach ($items as $item) {
2008             $output .= html_writer::tag('li', $item)."\n";
2009         }
2010         $output .= html_writer::end_tag($tag);
2011         return $output;
2012     }
2014     /**
2015      * Returns hidden input fields created from url parameters.
2016      *
2017      * @param moodle_url $url
2018      * @param array $exclude list of excluded parameters
2019      * @return string HTML fragment
2020      */
2021     public static function input_hidden_params(moodle_url $url, array $exclude = null) {
2022         $exclude = (array)$exclude;
2023         $params = $url->params();
2024         foreach ($exclude as $key) {
2025             unset($params[$key]);
2026         }
2028         $output = '';
2029         foreach ($params as $key => $value) {
2030             $attributes = array('type'=>'hidden', 'name'=>$key, 'value'=>$value);
2031             $output .= self::empty_tag('input', $attributes)."\n";
2032         }
2033         return $output;
2034     }
2036     /**
2037      * Generate a script tag containing the the specified code.
2038      *
2039      * @param string $jscode the JavaScript code
2040      * @param moodle_url|string $url optional url of the external script, $code ignored if specified
2041      * @return string HTML, the code wrapped in <script> tags.
2042      */
2043     public static function script($jscode, $url=null) {
2044         if ($jscode) {
2045             $attributes = array('type'=>'text/javascript');
2046             return self::tag('script', "\n//<![CDATA[\n$jscode\n//]]>\n", $attributes) . "\n";
2048         } else if ($url) {
2049             $attributes = array('type'=>'text/javascript', 'src'=>$url);
2050             return self::tag('script', '', $attributes) . "\n";
2052         } else {
2053             return '';
2054         }
2055     }
2057     /**
2058      * Renders HTML table
2059      *
2060      * This method may modify the passed instance by adding some default properties if they are not set yet.
2061      * If this is not what you want, you should make a full clone of your data before passing them to this
2062      * method. In most cases this is not an issue at all so we do not clone by default for performance
2063      * and memory consumption reasons.
2064      *
2065      * @param html_table $table data to be rendered
2066      * @return string HTML code
2067      */
2068     public static function table(html_table $table) {
2069         // prepare table data and populate missing properties with reasonable defaults
2070         if (!empty($table->align)) {
2071             foreach ($table->align as $key => $aa) {
2072                 if ($aa) {
2073                     $table->align[$key] = 'text-align:'. fix_align_rtl($aa) .';';  // Fix for RTL languages
2074                 } else {
2075                     $table->align[$key] = null;
2076                 }
2077             }
2078         }
2079         if (!empty($table->size)) {
2080             foreach ($table->size as $key => $ss) {
2081                 if ($ss) {
2082                     $table->size[$key] = 'width:'. $ss .';';
2083                 } else {
2084                     $table->size[$key] = null;
2085                 }
2086             }
2087         }
2088         if (!empty($table->wrap)) {
2089             foreach ($table->wrap as $key => $ww) {
2090                 if ($ww) {
2091                     $table->wrap[$key] = 'white-space:nowrap;';
2092                 } else {
2093                     $table->wrap[$key] = '';
2094                 }
2095             }
2096         }
2097         if (!empty($table->head)) {
2098             foreach ($table->head as $key => $val) {
2099                 if (!isset($table->align[$key])) {
2100                     $table->align[$key] = null;
2101                 }
2102                 if (!isset($table->size[$key])) {
2103                     $table->size[$key] = null;
2104                 }
2105                 if (!isset($table->wrap[$key])) {
2106                     $table->wrap[$key] = null;
2107                 }
2109             }
2110         }
2111         if (empty($table->attributes['class'])) {
2112             $table->attributes['class'] = 'generaltable';
2113         }
2114         if (!empty($table->tablealign)) {
2115             $table->attributes['class'] .= ' boxalign' . $table->tablealign;
2116         }
2118         // explicitly assigned properties override those defined via $table->attributes
2119         $table->attributes['class'] = trim($table->attributes['class']);
2120         $attributes = array_merge($table->attributes, array(
2121                 'id'            => $table->id,
2122                 'width'         => $table->width,
2123                 'summary'       => $table->summary,
2124                 'cellpadding'   => $table->cellpadding,
2125                 'cellspacing'   => $table->cellspacing,
2126             ));
2127         $output = html_writer::start_tag('table', $attributes) . "\n";
2129         $countcols = 0;
2131         // Output a caption if present.
2132         if (!empty($table->caption)) {
2133             $captionattributes = array();
2134             if ($table->captionhide) {
2135                 $captionattributes['class'] = 'accesshide';
2136             }
2137             $output .= html_writer::tag(
2138                 'caption',
2139                 $table->caption,
2140                 $captionattributes
2141             );
2142         }
2144         if (!empty($table->head)) {
2145             $countcols = count($table->head);
2147             $output .= html_writer::start_tag('thead', array()) . "\n";
2148             $output .= html_writer::start_tag('tr', array()) . "\n";
2149             $keys = array_keys($table->head);
2150             $lastkey = end($keys);
2152             foreach ($table->head as $key => $heading) {
2153                 // Convert plain string headings into html_table_cell objects
2154                 if (!($heading instanceof html_table_cell)) {
2155                     $headingtext = $heading;
2156                     $heading = new html_table_cell();
2157                     $heading->text = $headingtext;
2158                     $heading->header = true;
2159                 }
2161                 if ($heading->header !== false) {
2162                     $heading->header = true;
2163                 }
2165                 if ($heading->header && empty($heading->scope)) {
2166                     $heading->scope = 'col';
2167                 }
2169                 $heading->attributes['class'] .= ' header c' . $key;
2170                 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
2171                     $heading->colspan = $table->headspan[$key];
2172                     $countcols += $table->headspan[$key] - 1;
2173                 }
2175                 if ($key == $lastkey) {
2176                     $heading->attributes['class'] .= ' lastcol';
2177                 }
2178                 if (isset($table->colclasses[$key])) {
2179                     $heading->attributes['class'] .= ' ' . $table->colclasses[$key];
2180                 }
2181                 $heading->attributes['class'] = trim($heading->attributes['class']);
2182                 $attributes = array_merge($heading->attributes, array(
2183                         'style'     => $table->align[$key] . $table->size[$key] . $heading->style,
2184                         'scope'     => $heading->scope,
2185                         'colspan'   => $heading->colspan,
2186                     ));
2188                 $tagtype = 'td';
2189                 if ($heading->header === true) {
2190                     $tagtype = 'th';
2191                 }
2192                 $output .= html_writer::tag($tagtype, $heading->text, $attributes) . "\n";
2193             }
2194             $output .= html_writer::end_tag('tr') . "\n";
2195             $output .= html_writer::end_tag('thead') . "\n";
2197             if (empty($table->data)) {
2198                 // For valid XHTML strict every table must contain either a valid tr
2199                 // or a valid tbody... both of which must contain a valid td
2200                 $output .= html_writer::start_tag('tbody', array('class' => 'empty'));
2201                 $output .= html_writer::tag('tr', html_writer::tag('td', '', array('colspan'=>count($table->head))));
2202                 $output .= html_writer::end_tag('tbody');
2203             }
2204         }
2206         if (!empty($table->data)) {
2207             $keys       = array_keys($table->data);
2208             $lastrowkey = end($keys);
2209             $output .= html_writer::start_tag('tbody', array());
2211             foreach ($table->data as $key => $row) {
2212                 if (($row === 'hr') && ($countcols)) {
2213                     $output .= html_writer::tag('td', html_writer::tag('div', '', array('class' => 'tabledivider')), array('colspan' => $countcols));
2214                 } else {
2215                     // Convert array rows to html_table_rows and cell strings to html_table_cell objects
2216                     if (!($row instanceof html_table_row)) {
2217                         $newrow = new html_table_row();
2219                         foreach ($row as $cell) {
2220                             if (!($cell instanceof html_table_cell)) {
2221                                 $cell = new html_table_cell($cell);
2222                             }
2223                             $newrow->cells[] = $cell;
2224                         }
2225                         $row = $newrow;
2226                     }
2228                     if (isset($table->rowclasses[$key])) {
2229                         $row->attributes['class'] .= ' ' . $table->rowclasses[$key];
2230                     }
2232                     if ($key == $lastrowkey) {
2233                         $row->attributes['class'] .= ' lastrow';
2234                     }
2236                     // Explicitly assigned properties should override those defined in the attributes.
2237                     $row->attributes['class'] = trim($row->attributes['class']);
2238                     $trattributes = array_merge($row->attributes, array(
2239                             'id'            => $row->id,
2240                             'style'         => $row->style,
2241                         ));
2242                     $output .= html_writer::start_tag('tr', $trattributes) . "\n";
2243                     $keys2 = array_keys($row->cells);
2244                     $lastkey = end($keys2);
2246                     $gotlastkey = false; //flag for sanity checking
2247                     foreach ($row->cells as $key => $cell) {
2248                         if ($gotlastkey) {
2249                             //This should never happen. Why do we have a cell after the last cell?
2250                             mtrace("A cell with key ($key) was found after the last key ($lastkey)");
2251                         }
2253                         if (!($cell instanceof html_table_cell)) {
2254                             $mycell = new html_table_cell();
2255                             $mycell->text = $cell;
2256                             $cell = $mycell;
2257                         }
2259                         if (($cell->header === true) && empty($cell->scope)) {
2260                             $cell->scope = 'row';
2261                         }
2263                         if (isset($table->colclasses[$key])) {
2264                             $cell->attributes['class'] .= ' ' . $table->colclasses[$key];
2265                         }
2267                         $cell->attributes['class'] .= ' cell c' . $key;
2268                         if ($key == $lastkey) {
2269                             $cell->attributes['class'] .= ' lastcol';
2270                             $gotlastkey = true;
2271                         }
2272                         $tdstyle = '';
2273                         $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
2274                         $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
2275                         $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
2276                         $cell->attributes['class'] = trim($cell->attributes['class']);
2277                         $tdattributes = array_merge($cell->attributes, array(
2278                                 'style' => $tdstyle . $cell->style,
2279                                 'colspan' => $cell->colspan,
2280                                 'rowspan' => $cell->rowspan,
2281                                 'id' => $cell->id,
2282                                 'abbr' => $cell->abbr,
2283                                 'scope' => $cell->scope,
2284                             ));
2285                         $tagtype = 'td';
2286                         if ($cell->header === true) {
2287                             $tagtype = 'th';
2288                         }
2289                         $output .= html_writer::tag($tagtype, $cell->text, $tdattributes) . "\n";
2290                     }
2291                 }
2292                 $output .= html_writer::end_tag('tr') . "\n";
2293             }
2294             $output .= html_writer::end_tag('tbody') . "\n";
2295         }
2296         $output .= html_writer::end_tag('table') . "\n";
2298         return $output;
2299     }
2301     /**
2302      * Renders form element label
2303      *
2304      * By default, the label is suffixed with a label separator defined in the
2305      * current language pack (colon by default in the English lang pack).
2306      * Adding the colon can be explicitly disabled if needed. Label separators
2307      * are put outside the label tag itself so they are not read by
2308      * screenreaders (accessibility).
2309      *
2310      * Parameter $for explicitly associates the label with a form control. When
2311      * set, the value of this attribute must be the same as the value of
2312      * the id attribute of the form control in the same document. When null,
2313      * the label being defined is associated with the control inside the label
2314      * element.
2315      *
2316      * @param string $text content of the label tag
2317      * @param string|null $for id of the element this label is associated with, null for no association
2318      * @param bool $colonize add label separator (colon) to the label text, if it is not there yet
2319      * @param array $attributes to be inserted in the tab, for example array('accesskey' => 'a')
2320      * @return string HTML of the label element
2321      */
2322     public static function label($text, $for, $colonize = true, array $attributes=array()) {
2323         if (!is_null($for)) {
2324             $attributes = array_merge($attributes, array('for' => $for));
2325         }
2326         $text = trim($text);
2327         $label = self::tag('label', $text, $attributes);
2329         // TODO MDL-12192 $colonize disabled for now yet
2330         // if (!empty($text) and $colonize) {
2331         //     // the $text may end with the colon already, though it is bad string definition style
2332         //     $colon = get_string('labelsep', 'langconfig');
2333         //     if (!empty($colon)) {
2334         //         $trimmed = trim($colon);
2335         //         if ((substr($text, -strlen($trimmed)) == $trimmed) or (substr($text, -1) == ':')) {
2336         //             //debugging('The label text should not end with colon or other label separator,
2337         //             //           please fix the string definition.', DEBUG_DEVELOPER);
2338         //         } else {
2339         //             $label .= $colon;
2340         //         }
2341         //     }
2342         // }
2344         return $label;
2345     }
2347     /**
2348      * Combines a class parameter with other attributes. Aids in code reduction
2349      * because the class parameter is very frequently used.
2350      *
2351      * If the class attribute is specified both in the attributes and in the
2352      * class parameter, the two values are combined with a space between.
2353      *
2354      * @param string $class Optional CSS class (or classes as space-separated list)
2355      * @param array $attributes Optional other attributes as array
2356      * @return array Attributes (or null if still none)
2357      */
2358     private static function add_class($class = '', array $attributes = null) {
2359         if ($class !== '') {
2360             $classattribute = array('class' => $class);
2361             if ($attributes) {
2362                 if (array_key_exists('class', $attributes)) {
2363                     $attributes['class'] = trim($attributes['class'] . ' ' . $class);
2364                 } else {
2365                     $attributes = $classattribute + $attributes;
2366                 }
2367             } else {
2368                 $attributes = $classattribute;
2369             }
2370         }
2371         return $attributes;
2372     }
2374     /**
2375      * Creates a <div> tag. (Shortcut function.)
2376      *
2377      * @param string $content HTML content of tag
2378      * @param string $class Optional CSS class (or classes as space-separated list)
2379      * @param array $attributes Optional other attributes as array
2380      * @return string HTML code for div
2381      */
2382     public static function div($content, $class = '', array $attributes = null) {
2383         return self::tag('div', $content, self::add_class($class, $attributes));
2384     }
2386     /**
2387      * Starts a <div> tag. (Shortcut function.)
2388      *
2389      * @param string $class Optional CSS class (or classes as space-separated list)
2390      * @param array $attributes Optional other attributes as array
2391      * @return string HTML code for open div tag
2392      */
2393     public static function start_div($class = '', array $attributes = null) {
2394         return self::start_tag('div', self::add_class($class, $attributes));
2395     }
2397     /**
2398      * Ends a <div> tag. (Shortcut function.)
2399      *
2400      * @return string HTML code for close div tag
2401      */
2402     public static function end_div() {
2403         return self::end_tag('div');
2404     }
2406     /**
2407      * Creates a <span> tag. (Shortcut function.)
2408      *
2409      * @param string $content HTML content of tag
2410      * @param string $class Optional CSS class (or classes as space-separated list)
2411      * @param array $attributes Optional other attributes as array
2412      * @return string HTML code for span
2413      */
2414     public static function span($content, $class = '', array $attributes = null) {
2415         return self::tag('span', $content, self::add_class($class, $attributes));
2416     }
2418     /**
2419      * Starts a <span> tag. (Shortcut function.)
2420      *
2421      * @param string $class Optional CSS class (or classes as space-separated list)
2422      * @param array $attributes Optional other attributes as array
2423      * @return string HTML code for open span tag
2424      */
2425     public static function start_span($class = '', array $attributes = null) {
2426         return self::start_tag('span', self::add_class($class, $attributes));
2427     }
2429     /**
2430      * Ends a <span> tag. (Shortcut function.)
2431      *
2432      * @return string HTML code for close span tag
2433      */
2434     public static function end_span() {
2435         return self::end_tag('span');
2436     }
2439 /**
2440  * Simple javascript output class
2441  *
2442  * @copyright 2010 Petr Skoda
2443  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2444  * @since Moodle 2.0
2445  * @package core
2446  * @category output
2447  */
2448 class js_writer {
2450     /**
2451      * Returns javascript code calling the function
2452      *
2453      * @param string $function function name, can be complex like Y.Event.purgeElement
2454      * @param array $arguments parameters
2455      * @param int $delay execution delay in seconds
2456      * @return string JS code fragment
2457      */
2458     public static function function_call($function, array $arguments = null, $delay=0) {
2459         if ($arguments) {
2460             $arguments = array_map('json_encode', convert_to_array($arguments));
2461             $arguments = implode(', ', $arguments);
2462         } else {
2463             $arguments = '';
2464         }
2465         $js = "$function($arguments);";
2467         if ($delay) {
2468             $delay = $delay * 1000; // in miliseconds
2469             $js = "setTimeout(function() { $js }, $delay);";
2470         }
2471         return $js . "\n";
2472     }
2474     /**
2475      * Special function which adds Y as first argument of function call.
2476      *
2477      * @param string $function The function to call
2478      * @param array $extraarguments Any arguments to pass to it
2479      * @return string Some JS code
2480      */
2481     public static function function_call_with_Y($function, array $extraarguments = null) {
2482         if ($extraarguments) {
2483             $extraarguments = array_map('json_encode', convert_to_array($extraarguments));
2484             $arguments = 'Y, ' . implode(', ', $extraarguments);
2485         } else {
2486             $arguments = 'Y';
2487         }
2488         return "$function($arguments);\n";
2489     }
2491     /**
2492      * Returns JavaScript code to initialise a new object
2493      *
2494      * @param string $var If it is null then no var is assigned the new object.
2495      * @param string $class The class to initialise an object for.
2496      * @param array $arguments An array of args to pass to the init method.
2497      * @param array $requirements Any modules required for this class.
2498      * @param int $delay The delay before initialisation. 0 = no delay.
2499      * @return string Some JS code
2500      */
2501     public static function object_init($var, $class, array $arguments = null, array $requirements = null, $delay=0) {
2502         if (is_array($arguments)) {
2503             $arguments = array_map('json_encode', convert_to_array($arguments));
2504             $arguments = implode(', ', $arguments);
2505         }
2507         if ($var === null) {
2508             $js = "new $class(Y, $arguments);";
2509         } else if (strpos($var, '.')!==false) {
2510             $js = "$var = new $class(Y, $arguments);";
2511         } else {
2512             $js = "var $var = new $class(Y, $arguments);";
2513         }
2515         if ($delay) {
2516             $delay = $delay * 1000; // in miliseconds
2517             $js = "setTimeout(function() { $js }, $delay);";
2518         }
2520         if (count($requirements) > 0) {
2521             $requirements = implode("', '", $requirements);
2522             $js = "Y.use('$requirements', function(Y){ $js });";
2523         }
2524         return $js."\n";
2525     }
2527     /**
2528      * Returns code setting value to variable
2529      *
2530      * @param string $name
2531      * @param mixed $value json serialised value
2532      * @param bool $usevar add var definition, ignored for nested properties
2533      * @return string JS code fragment
2534      */
2535     public static function set_variable($name, $value, $usevar = true) {
2536         $output = '';
2538         if ($usevar) {
2539             if (strpos($name, '.')) {
2540                 $output .= '';
2541             } else {
2542                 $output .= 'var ';
2543             }
2544         }
2546         $output .= "$name = ".json_encode($value).";";
2548         return $output;
2549     }
2551     /**
2552      * Writes event handler attaching code
2553      *
2554      * @param array|string $selector standard YUI selector for elements, may be
2555      *     array or string, element id is in the form "#idvalue"
2556      * @param string $event A valid DOM event (click, mousedown, change etc.)
2557      * @param string $function The name of the function to call
2558      * @param array $arguments An optional array of argument parameters to pass to the function
2559      * @return string JS code fragment
2560      */
2561     public static function event_handler($selector, $event, $function, array $arguments = null) {
2562         $selector = json_encode($selector);
2563         $output = "Y.on('$event', $function, $selector, null";
2564         if (!empty($arguments)) {
2565             $output .= ', ' . json_encode($arguments);
2566         }
2567         return $output . ");\n";
2568     }
2571 /**
2572  * Holds all the information required to render a <table> by {@link core_renderer::table()}
2573  *
2574  * Example of usage:
2575  * $t = new html_table();
2576  * ... // set various properties of the object $t as described below
2577  * echo html_writer::table($t);
2578  *
2579  * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
2580  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2581  * @since Moodle 2.0
2582  * @package core
2583  * @category output
2584  */
2585 class html_table {
2587     /**
2588      * @var string Value to use for the id attribute of the table
2589      */
2590     public $id = null;
2592     /**
2593      * @var array Attributes of HTML attributes for the <table> element
2594      */
2595     public $attributes = array();
2597     /**
2598      * @var array An array of headings. The n-th array item is used as a heading of the n-th column.
2599      * For more control over the rendering of the headers, an array of html_table_cell objects
2600      * can be passed instead of an array of strings.
2601      *
2602      * Example of usage:
2603      * $t->head = array('Student', 'Grade');
2604      */
2605     public $head;
2607     /**
2608      * @var array An array that can be used to make a heading span multiple columns.
2609      * In this example, {@link html_table:$data} is supposed to have three columns. For the first two columns,
2610      * the same heading is used. Therefore, {@link html_table::$head} should consist of two items.
2611      *
2612      * Example of usage:
2613      * $t->headspan = array(2,1);
2614      */
2615     public $headspan;
2617     /**
2618      * @var array An array of column alignments.
2619      * The value is used as CSS 'text-align' property. Therefore, possible
2620      * values are 'left', 'right', 'center' and 'justify'. Specify 'right' or 'left' from the perspective
2621      * of a left-to-right (LTR) language. For RTL, the values are flipped automatically.
2622      *
2623      * Examples of usage:
2624      * $t->align = array(null, 'right');
2625      * or
2626      * $t->align[1] = 'right';
2627      */
2628     public $align;
2630     /**
2631      * @var array The value is used as CSS 'size' property.
2632      *
2633      * Examples of usage:
2634      * $t->size = array('50%', '50%');
2635      * or
2636      * $t->size[1] = '120px';
2637      */
2638     public $size;
2640     /**
2641      * @var array An array of wrapping information.
2642      * The only possible value is 'nowrap' that sets the
2643      * CSS property 'white-space' to the value 'nowrap' in the given column.
2644      *
2645      * Example of usage:
2646      * $t->wrap = array(null, 'nowrap');
2647      */
2648     public $wrap;
2650     /**
2651      * @var array Array of arrays or html_table_row objects containing the data. Alternatively, if you have
2652      * $head specified, the string 'hr' (for horizontal ruler) can be used
2653      * instead of an array of cells data resulting in a divider rendered.
2654      *
2655      * Example of usage with array of arrays:
2656      * $row1 = array('Harry Potter', '76 %');
2657      * $row2 = array('Hermione Granger', '100 %');
2658      * $t->data = array($row1, $row2);
2659      *
2660      * Example with array of html_table_row objects: (used for more fine-grained control)
2661      * $cell1 = new html_table_cell();
2662      * $cell1->text = 'Harry Potter';
2663      * $cell1->colspan = 2;
2664      * $row1 = new html_table_row();
2665      * $row1->cells[] = $cell1;
2666      * $cell2 = new html_table_cell();
2667      * $cell2->text = 'Hermione Granger';
2668      * $cell3 = new html_table_cell();
2669      * $cell3->text = '100 %';
2670      * $row2 = new html_table_row();
2671      * $row2->cells = array($cell2, $cell3);
2672      * $t->data = array($row1, $row2);
2673      */
2674     public $data = [];
2676     /**
2677      * @deprecated since Moodle 2.0. Styling should be in the CSS.
2678      * @var string Width of the table, percentage of the page preferred.
2679      */
2680     public $width = null;
2682     /**
2683      * @deprecated since Moodle 2.0. Styling should be in the CSS.
2684      * @var string Alignment for the whole table. Can be 'right', 'left' or 'center' (default).
2685      */
2686     public $tablealign = null;
2688     /**
2689      * @deprecated since Moodle 2.0. Styling should be in the CSS.
2690      * @var int Padding on each cell, in pixels
2691      */
2692     public $cellpadding = null;
2694     /**
2695      * @var int Spacing between cells, in pixels
2696      * @deprecated since Moodle 2.0. Styling should be in the CSS.
2697      */
2698     public $cellspacing = null;
2700     /**
2701      * @var array Array of classes to add to particular rows, space-separated string.
2702      * Class 'lastrow' is added automatically for the last row in the table.
2703      *
2704      * Example of usage:
2705      * $t->rowclasses[9] = 'tenth'
2706      */
2707     public $rowclasses;
2709     /**
2710      * @var array An array of classes to add to every cell in a particular column,
2711      * space-separated string. Class 'cell' is added automatically by the renderer.
2712      * Classes 'c0' or 'c1' are added automatically for every odd or even column,
2713      * respectively. Class 'lastcol' is added automatically for all last cells
2714      * in a row.
2715      *
2716      * Example of usage:
2717      * $t->colclasses = array(null, 'grade');
2718      */
2719     public $colclasses;
2721     /**
2722      * @var string Description of the contents for screen readers.
2723      */
2724     public $summary;
2726     /**
2727      * @var string Caption for the table, typically a title.
2728      *
2729      * Example of usage:
2730      * $t->caption = "TV Guide";
2731      */
2732     public $caption;
2734     /**
2735      * @var bool Whether to hide the table's caption from sighted users.
2736      *
2737      * Example of usage:
2738      * $t->caption = "TV Guide";
2739      * $t->captionhide = true;
2740      */
2741     public $captionhide = false;
2743     /**
2744      * Constructor
2745      */
2746     public function __construct() {
2747         $this->attributes['class'] = '';
2748     }
2751 /**
2752  * Component representing a table row.
2753  *
2754  * @copyright 2009 Nicolas Connault
2755  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2756  * @since Moodle 2.0
2757  * @package core
2758  * @category output
2759  */
2760 class html_table_row {
2762     /**
2763      * @var string Value to use for the id attribute of the row.
2764      */
2765     public $id = null;
2767     /**
2768      * @var array Array of html_table_cell objects
2769      */
2770     public $cells = array();
2772     /**
2773      * @var string Value to use for the style attribute of the table row
2774      */
2775     public $style = null;
2777     /**
2778      * @var array Attributes of additional HTML attributes for the <tr> element
2779      */
2780     public $attributes = array();
2782     /**
2783      * Constructor
2784      * @param array $cells
2785      */
2786     public function __construct(array $cells=null) {
2787         $this->attributes['class'] = '';
2788         $cells = (array)$cells;
2789         foreach ($cells as $cell) {
2790             if ($cell instanceof html_table_cell) {
2791                 $this->cells[] = $cell;
2792             } else {
2793                 $this->cells[] = new html_table_cell($cell);
2794             }
2795         }
2796     }
2799 /**
2800  * Component representing a table cell.
2801  *
2802  * @copyright 2009 Nicolas Connault
2803  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2804  * @since Moodle 2.0
2805  * @package core
2806  * @category output
2807  */
2808 class html_table_cell {
2810     /**
2811      * @var string Value to use for the id attribute of the cell.
2812      */
2813     public $id = null;
2815     /**
2816      * @var string The contents of the cell.
2817      */
2818     public $text;
2820     /**
2821      * @var string Abbreviated version of the contents of the cell.
2822      */
2823     public $abbr = null;
2825     /**
2826      * @var int Number of columns this cell should span.
2827      */
2828     public $colspan = null;
2830     /**
2831      * @var int Number of rows this cell should span.
2832      */
2833     public $rowspan = null;
2835     /**
2836      * @var string Defines a way to associate header cells and data cells in a table.
2837      */
2838     public $scope = null;
2840     /**
2841      * @var bool Whether or not this cell is a header cell.
2842      */
2843     public $header = null;
2845     /**
2846      * @var string Value to use for the style attribute of the table cell
2847      */
2848     public $style = null;
2850     /**
2851      * @var array Attributes of additional HTML attributes for the <td> element
2852      */
2853     public $attributes = array();
2855     /**
2856      * Constructs a table cell
2857      *
2858      * @param string $text
2859      */
2860     public function __construct($text = null) {
2861         $this->text = $text;
2862         $this->attributes['class'] = '';
2863     }
2866 /**
2867  * Component representing a paging bar.
2868  *
2869  * @copyright 2009 Nicolas Connault
2870  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2871  * @since Moodle 2.0
2872  * @package core
2873  * @category output
2874  */
2875 class paging_bar implements renderable, templatable {
2877     /**
2878      * @var int The maximum number of pagelinks to display.
2879      */
2880     public $maxdisplay = 18;
2882     /**
2883      * @var int The total number of entries to be pages through..
2884      */
2885     public $totalcount;
2887     /**
2888      * @var int The page you are currently viewing.
2889      */
2890     public $page;
2892     /**
2893      * @var int The number of entries that should be shown per page.
2894      */
2895     public $perpage;
2897     /**
2898      * @var string|moodle_url If this  is a string then it is the url which will be appended with $pagevar,
2899      * an equals sign and the page number.
2900      * If this is a moodle_url object then the pagevar param will be replaced by
2901      * the page no, for each page.
2902      */
2903     public $baseurl;
2905     /**
2906      * @var string This is the variable name that you use for the pagenumber in your
2907      * code (ie. 'tablepage', 'blogpage', etc)
2908      */
2909     public $pagevar;
2911     /**
2912      * @var string A HTML link representing the "previous" page.
2913      */
2914     public $previouslink = null;
2916     /**
2917      * @var string A HTML link representing the "next" page.
2918      */
2919     public $nextlink = null;
2921     /**
2922      * @var string A HTML link representing the first page.
2923      */
2924     public $firstlink = null;
2926     /**
2927      * @var string A HTML link representing the last page.
2928      */
2929     public $lastlink = null;
2931     /**
2932      * @var array An array of strings. One of them is just a string: the current page
2933      */
2934     public $pagelinks = array();
2936     /**
2937      * Constructor paging_bar with only the required params.
2938      *
2939      * @param int $totalcount The total number of entries available to be paged through
2940      * @param int $page The page you are currently viewing
2941      * @param int $perpage The number of entries that should be shown per page
2942      * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
2943      * @param string $pagevar name of page parameter that holds the page number
2944      */
2945     public function __construct($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
2946         $this->totalcount = $totalcount;
2947         $this->page       = $page;
2948         $this->perpage    = $perpage;
2949         $this->baseurl    = $baseurl;
2950         $this->pagevar    = $pagevar;
2951     }
2953     /**
2954      * Prepares the paging bar for output.
2955      *
2956      * This method validates the arguments set up for the paging bar and then
2957      * produces fragments of HTML to assist display later on.
2958      *
2959      * @param renderer_base $output
2960      * @param moodle_page $page
2961      * @param string $target
2962      * @throws coding_exception
2963      */
2964     public function prepare(renderer_base $output, moodle_page $page, $target) {
2965         if (!isset($this->totalcount) || is_null($this->totalcount)) {
2966             throw new coding_exception('paging_bar requires a totalcount value.');
2967         }
2968         if (!isset($this->page) || is_null($this->page)) {
2969             throw new coding_exception('paging_bar requires a page value.');
2970         }
2971         if (empty($this->perpage)) {
2972             throw new coding_exception('paging_bar requires a perpage value.');
2973         }
2974         if (empty($this->baseurl)) {
2975             throw new coding_exception('paging_bar requires a baseurl value.');
2976         }
2978         if ($this->totalcount > $this->perpage) {
2979             $pagenum = $this->page - 1;
2981             if ($this->page > 0) {
2982                 $this->previouslink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('previous'), array('class'=>'previous'));
2983             }
2985             if ($this->perpage > 0) {
2986                 $lastpage = ceil($this->totalcount / $this->perpage);
2987             } else {
2988                 $lastpage = 1;
2989             }
2991             if ($this->page > round(($this->maxdisplay/3)*2)) {
2992                 $currpage = $this->page - round($this->maxdisplay/3);
2994                 $this->firstlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>0)), '1', array('class'=>'first'));
2995             } else {
2996                 $currpage = 0;
2997             }
2999             $displaycount = $displaypage = 0;
3001             while ($displaycount < $this->maxdisplay and $currpage < $lastpage) {
3002                 $displaypage = $currpage + 1;
3004                 if ($this->page == $currpage) {
3005                     $this->pagelinks[] = html_writer::span($displaypage, 'current-page');
3006                 } else {
3007                     $pagelink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$currpage)), $displaypage);
3008                     $this->pagelinks[] = $pagelink;
3009                 }
3011                 $displaycount++;
3012                 $currpage++;
3013             }
3015             if ($currpage < $lastpage) {
3016                 $lastpageactual = $lastpage - 1;
3017                 $this->lastlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$lastpageactual)), $lastpage, array('class'=>'last'));
3018             }
3020             $pagenum = $this->page + 1;
3022             if ($pagenum != $lastpage) {
3023                 $this->nextlink = html_writer::link(new moodle_url($this->baseurl, array($this->pagevar=>$pagenum)), get_string('next'), array('class'=>'next'));
3024             }
3025         }
3026     }
3028     /**
3029      * Export for template.
3030      *
3031      * @param renderer_base $output The renderer.
3032      * @return stdClass
3033      */
3034     public function export_for_template(renderer_base $output) {
3035         $data = new stdClass();
3036         $data->previous = null;
3037         $data->next = null;
3038         $data->first = null;
3039         $data->last = null;
3040         $data->label = get_string('page');
3041         $data->pages = [];
3042         $data->haspages = $this->totalcount > $this->perpage;
3044         if (!$data->haspages) {
3045             return $data;
3046         }
3048         if ($this->page > 0) {
3049             $data->previous = [
3050                 'page' => $this->page - 1,
3051                 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $this->page - 1]))->out(false)
3052             ];
3053         }
3055         $currpage = 0;
3056         if ($this->page > round(($this->maxdisplay / 3) * 2)) {
3057             $currpage = $this->page - round($this->maxdisplay / 3);
3058             $data->first = [
3059                 'page' => 1,
3060                 'url' => (new moodle_url($this->baseurl, [$this->pagevar => 0]))->out(false)
3061             ];
3062         }
3064         $lastpage = 1;
3065         if ($this->perpage > 0) {
3066             $lastpage = ceil($this->totalcount / $this->perpage);
3067         }
3069         $displaycount = 0;
3070         $displaypage = 0;
3071         while ($displaycount < $this->maxdisplay and $currpage < $lastpage) {
3072             $displaypage = $currpage + 1;
3074             $iscurrent = $this->page == $currpage;
3075             $link = new moodle_url($this->baseurl, [$this->pagevar => $currpage]);
3077             $data->pages[] = [
3078                 'page' => $displaypage,
3079                 'active' => $iscurrent,
3080                 'url' => $iscurrent ? null : $link->out(false)
3081             ];
3083             $displaycount++;
3084             $currpage++;
3085         }
3087         if ($currpage < $lastpage) {
3088             $data->last = [
3089                 'page' => $lastpage,
3090                 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $lastpage - 1]))->out(false)
3091             ];
3092         }
3094         if ($this->page + 1 != $lastpage) {
3095             $data->next = [
3096                 'page' => $this->page + 1,
3097                 'url' => (new moodle_url($this->baseurl, [$this->pagevar => $this->page + 1]))->out(false)
3098             ];
3099         }
3101         return $data;
3102     }
3105 /**
3106  * Component representing initials bar.
3107  *
3108  * @copyright 2017 Ilya Tregubov
3109  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3110  * @since Moodle 3.3
3111  * @package core
3112  * @category output
3113  */
3114 class initials_bar implements renderable, templatable {
3116     /**
3117      * @var string Currently selected letter.
3118      */
3119     public $current;
3121     /**
3122      * @var string Class name to add to this initial bar.
3123      */
3124     public $class;
3126     /**
3127      * @var string The name to put in front of this initial bar.
3128      */
3129     public $title;
3131     /**
3132      * @var string URL parameter name for this initial.
3133      */
3134     public $urlvar;
3136     /**
3137      * @var string URL object.
3138      */
3139     public $url;
3141     /**
3142      * @var array An array of letters in the alphabet.
3143      */
3144     public $alpha;
3146     /**
3147      * Constructor initials_bar with only the required params.
3148      *
3149      * @param string $current the currently selected letter.
3150      * @param string $class class name to add to this initial bar.
3151      * @param string $title the name to put in front of this initial bar.
3152      * @param string $urlvar URL parameter name for this initial.
3153      * @param string $url URL object.
3154      * @param array $alpha of letters in the alphabet.
3155      */
3156     public function __construct($current, $class, $title, $urlvar, $url, $alpha = null) {
3157         $this->current       = $current;
3158         $this->class    = $class;
3159         $this->title    = $title;
3160         $this->urlvar    = $urlvar;
3161         $this->url    = $url;
3162         $this->alpha    = $alpha;
3163     }
3165     /**
3166      * Export for template.
3167      *
3168      * @param renderer_base $output The renderer.
3169      * @return stdClass
3170      */
3171     public function export_for_template(renderer_base $output) {
3172         $data = new stdClass();
3174         if ($this->alpha == null) {
3175             $this->alpha = explode(',', get_string('alphabet', 'langconfig'));
3176         }
3178         if ($this->current == 'all') {
3179             $this->current = '';
3180         }
3182         // We want to find a letter grouping size which suits the language so
3183         // find the largest group size which is less than 15 chars.
3184         // The choice of 15 chars is the largest number of chars that reasonably
3185         // fits on the smallest supported screen size. By always using a max number
3186         // of groups which is a factor of 2, we always get nice wrapping, and the
3187         // last row is always the shortest.
3188         $groupsize = count($this->alpha);
3189         $groups = 1;
3190         while ($groupsize > 15) {
3191             $groups *= 2;
3192             $groupsize = ceil(count($this->alpha) / $groups);
3193         }
3195         $groupsizelimit = 0;
3196         $groupnumber = 0;
3197         foreach ($this->alpha as $letter) {
3198             if ($groupsizelimit++ > 0 && $groupsizelimit % $groupsize == 1) {
3199                 $groupnumber++;
3200             }
3201             $groupletter = new stdClass();
3202             $groupletter->name = $letter;
3203             $groupletter->url = $this->url->out(false, array($this->urlvar => $letter));
3204             if ($letter == $this->current) {
3205                 $groupletter->selected = $this->current;
3206             }
3207             $data->group[$groupnumber]->letter[] = $groupletter;
3208         }
3210         $data->class = $this->class;
3211         $data->title = $this->title;
3212         $data->url = $this->url->out(false, array($this->urlvar => ''));
3213         $data->current = $this->current;
3214         $data->all = get_string('all');
3216         return $data;
3217     }
3220 /**
3221  * This class represents how a block appears on a page.
3222  *
3223  * During output, each block instance is asked to return a block_contents object,
3224  * those are then passed to the $OUTPUT->block function for display.
3225  *
3226  * contents should probably be generated using a moodle_block_..._renderer.
3227  *
3228  * Other block-like things that need to appear on the page, for example the
3229  * add new block UI, are also represented as block_contents objects.
3230  *
3231  * @copyright 2009 Tim Hunt
3232  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3233  * @since Moodle 2.0
3234  * @package core
3235  * @category output
3236  */
3237 class block_contents {
3239     /** Used when the block cannot be collapsed **/
3240     const NOT_HIDEABLE = 0;
3242     /** Used when the block can be collapsed but currently is not **/
3243     const VISIBLE = 1;
3245     /** Used when the block has been collapsed **/
3246     const HIDDEN = 2;
3248     /**
3249      * @var int Used to set $skipid.
3250      */
3251     protected static $idcounter = 1;
3253     /**
3254      * @var int All the blocks (or things that look like blocks) printed on
3255      * a page are given a unique number that can be used to construct id="" attributes.
3256      * This is set automatically be the {@link prepare()} method.
3257      * Do not try to set it manually.
3258      */
3259     public $skipid;
3261     /**
3262      * @var int If this is the contents of a real block, this should be set
3263      * to the block_instance.id. Otherwise this should be set to 0.
3264      */
3265     public $blockinstanceid = 0;
3267     /**
3268      * @var int If this is a real block instance, and there is a corresponding
3269      * block_position.id for the block on this page, this should be set to that id.
3270      * Otherwise it should be 0.
3271      */
3272     public $blockpositionid = 0;
3274     /**
3275      * @var array An array of attribute => value pairs that are put on the outer div of this
3276      * block. {@link $id} and {@link $classes} attributes should be set separately.
3277      */
3278     public $attributes;
3280     /**
3281      * @var string The title of this block. If this came from user input, it should already
3282      * have had format_string() processing done on it. This will be output inside
3283      * <h2> tags. Please do not cause invalid XHTML.
3284      */
3285     public $title = '';
3287     /**
3288      * @var string The label to use when the block does not, or will not have a visible title.
3289      * You should never set this as well as title... it will just be ignored.
3290      */
3291     public $arialabel = '';
3293     /**
3294      * @var string HTML for the content
3295      */
3296     public $content = '';
3298     /**
3299      * @var array An alternative to $content, it you want a list of things with optional icons.
3300      */
3301     public $footer = '';
3303     /**
3304      * @var string Any small print that should appear under the block to explain
3305      * to the teacher about the block, for example 'This is a sticky block that was
3306      * added in the system context.'
3307      */
3308     public $annotation = '';
3310     /**
3311      * @var int One of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether
3312      * the user can toggle whether this block is visible.
3313      */
3314     public $collapsible = self::NOT_HIDEABLE;
3316     /**
3317      * Set this to true if the block is dockable.
3318      * @var bool
3319      */
3320     public $dockable = false;
3322     /**
3323      * @var array A (possibly empty) array of editing controls. Each element of
3324      * this array should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption).
3325      * $icon is the icon name. Fed to $OUTPUT->image_url.
3326      */
3327     public $controls = array();
3330     /**
3331      * Create new instance of block content
3332      * @param array $attributes
3333      */
3334     public function __construct(array $attributes = null) {
3335         $this->skipid = self::$idcounter;
3336         self::$idcounter += 1;
3338         if ($attributes) {
3339             // standard block
3340             $this->attributes = $attributes;
3341         } else {
3342             // simple "fake" blocks used in some modules and "Add new block" block
3343             $this->attributes = array('class'=>'block');
3344         }
3345     }
3347     /**
3348      * Add html class to block
3349      *
3350      * @param string $class
3351      */
3352     public function add_class($class) {
3353         $this->attributes['class'] .= ' '.$class;
3354     }
3358 /**
3359  * This class represents a target for where a block can go when it is being moved.
3360  *
3361  * This needs to be rendered as a form with the given hidden from fields, and
3362  * clicking anywhere in the form should submit it. The form action should be
3363  * $PAGE->url.
3364  *
3365  * @copyright 2009 Tim Hunt
3366  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3367  * @since Moodle 2.0
3368  * @package core
3369  * @category output
3370  */
3371 class block_move_target {
3373     /**
3374      * @var moodle_url Move url
3375      */
3376     public $url;
3378     /**
3379      * Constructor
3380      * @param moodle_url $url
3381      */
3382     public function __construct(moodle_url $url) {
3383         $this->url  = $url;
3384     }
3387 /**
3388  * Custom menu item
3389  *
3390  * This class is used to represent one item within a custom menu that may or may
3391  * not have children.
3392  *
3393  * @copyright 2010 Sam Hemelryk
3394  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3395  * @since Moodle 2.0
3396  * @package core
3397  * @category output
3398  */
3399 class custom_menu_item implements renderable, templatable {
3401     /**
3402      * @var string The text to show for the item
3403      */
3404     protected $text;
3406     /**
3407      * @var moodle_url The link to give the icon if it has no children
3408      */
3409     protected $url;
3411     /**
3412      * @var string A title to apply to the item. By default the text
3413      */
3414     protected $title;
3416     /**
3417      * @var int A sort order for the item, not necessary if you order things in
3418      * the CFG var.
3419      */
3420     protected $sort;
3422     /**
3423      * @var custom_menu_item A reference to the parent for this item or NULL if
3424      * it is a top level item
3425      */
3426     protected $parent;
3428     /**
3429      * @var array A array in which to store children this item has.
3430      */
3431     protected $children = array();
3433     /**
3434      * @var int A reference to the sort var of the last child that was added
3435      */
3436     protected $lastsort = 0;
3438     /**
3439      * Constructs the new custom menu item
3440      *
3441      * @param string $text
3442      * @param moodle_url $url A moodle url to apply as the link for this item [Optional]
3443      * @param string $title A title to apply to this item [Optional]
3444      * @param int $sort A sort or to use if we need to sort differently [Optional]
3445      * @param custom_menu_item $parent A reference to the parent custom_menu_item this child
3446      *        belongs to, only if the child has a parent. [Optional]
3447      */
3448     public function __construct($text, moodle_url $url=null, $title=null, $sort = null, custom_menu_item $parent = null) {
3449         $this->text = $text;
3450         $this->url = $url;
3451         $this->title = $title;
3452         $this->sort = (int)$sort;
3453         $this->parent = $parent;
3454     }
3456     /**
3457      * Adds a custom menu item as a child of this node given its properties.
3458      *
3459      * @param string $text
3460      * @param moodle_url $url
3461      * @param string $title
3462      * @param int $sort
3463      * @return custom_menu_item
3464      */
3465     public function add($text, moodle_url $url = null, $title = null, $sort = null) {
3466         $key = count($this->children);
3467         if (empty($sort)) {
3468             $sort = $this->lastsort + 1;
3469         }
3470         $this->children[$key] = new custom_menu_item($text, $url, $title, $sort, $this);
3471         $this->lastsort = (int)$sort;
3472         return $this->children[$key];
3473     }
3475     /**
3476      * Removes a custom menu item that is a child or descendant to the current menu.
3477      *
3478      * Returns true if child was found and removed.
3479      *
3480      * @param custom_menu_item $menuitem
3481      * @return bool
3482      */
3483     public function remove_child(custom_menu_item $menuitem) {
3484         $removed = false;
3485         if (($key = array_search($menuitem, $this->children)) !== false) {
3486             unset($this->children[$key]);
3487             $this->children = array_values($this->children);
3488             $removed = true;
3489         } else {
3490             foreach ($this->children as $child) {
3491                 if ($removed = $child->remove_child($menuitem)) {
3492                     break;
3493                 }
3494             }
3495         }
3496         return $removed;
3497     }
3499     /**
3500      * Returns the text for this item
3501      * @return string
3502      */
3503     public function get_text() {
3504         return $this->text;
3505     }
3507     /**
3508      * Returns the url for this item
3509      * @return moodle_url
3510      */
3511     public function get_url() {
3512         return $this->url;
3513     }
3515     /**
3516      * Returns the title for this item
3517      * @return string
3518      */
3519     public function get_title() {
3520         return $this->title;
3521     }
3523     /**
3524      * Sorts and returns the children for this item
3525      * @return array
3526      */
3527     public function get_children() {
3528         $this->sort();
3529         return $this->children;
3530     }
3532     /**
3533      * Gets the sort order for this child
3534      * @return int
3535      */
3536     public function get_sort_order() {
3537         return $this->sort;
3538     }
3540     /**
3541      * Gets the parent this child belong to