. /** * Classes representing HTML elements, used by $OUTPUT methods * * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML * for an overview. * * @package core * @category output * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Interface marking other classes as suitable for renderer_base::render() * * @copyright 2010 Petr Skoda (skodak) info@skodak.org * @package core * @category output */ interface renderable { // intentionally empty } /** * Interface marking other classes having the ability to export their data for use by templates. * * @copyright 2015 Damyon Wiese * @package core * @category output * @since 2.9 */ interface templatable { /** * Function to export the renderer data in a format that is suitable for a * mustache template. This means: * 1. No complex types - only stdClass, array, int, string, float, bool * 2. Any additional info that is required for the template is pre-calculated (e.g. capability checks). * * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. * @return stdClass|array */ public function export_for_template(renderer_base $output); } /** * Data structure representing a file picker. * * @copyright 2010 Dongsheng Cai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class file_picker implements renderable { /** * @var stdClass An object containing options for the file picker */ public $options; /** * Constructs a file picker object. * * The following are possible options for the filepicker: * - accepted_types (*) * - return_types (FILE_INTERNAL) * - env (filepicker) * - client_id (uniqid) * - itemid (0) * - maxbytes (-1) * - maxfiles (1) * - buttonname (false) * * @param stdClass $options An object containing options for the file picker. */ public function __construct(stdClass $options) { global $CFG, $USER, $PAGE; require_once($CFG->dirroot. '/repository/lib.php'); $defaults = array( 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL, 'env' => 'filepicker', 'client_id' => uniqid(), 'itemid' => 0, 'maxbytes'=>-1, 'maxfiles'=>1, 'buttonname'=>false ); foreach ($defaults as $key=>$value) { if (empty($options->$key)) { $options->$key = $value; } } $options->currentfile = ''; if (!empty($options->itemid)) { $fs = get_file_storage(); $usercontext = context_user::instance($USER->id); if (empty($options->filename)) { if ($files = $fs->get_area_files($usercontext->id, 'user', 'draft', $options->itemid, 'id DESC', false)) { $file = reset($files); } } else { $file = $fs->get_file($usercontext->id, 'user', 'draft', $options->itemid, $options->filepath, $options->filename); } if (!empty($file)) { $options->currentfile = html_writer::link(moodle_url::make_draftfile_url($file->get_itemid(), $file->get_filepath(), $file->get_filename()), $file->get_filename()); } } // initilise options, getting files in root path $this->options = initialise_filepicker($options); // copying other options foreach ($options as $name=>$value) { if (!isset($this->options->$name)) { $this->options->$name = $value; } } } } /** * Data structure representing a user picture. * * @copyright 2009 Nicolas Connault, 2010 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Modle 2.0 * @package core * @category output */ class user_picture implements renderable { /** * @var array List of mandatory fields in user record here. (do not include * TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE) */ protected static $fields = array('id', 'picture', 'firstname', 'lastname', 'firstnamephonetic', 'lastnamephonetic', 'middlename', 'alternatename', 'imagealt', 'email'); /** * @var stdClass A user object with at least fields all columns specified * in $fields array constant set. */ public $user; /** * @var int The course id. Used when constructing the link to the user's * profile, page course id used if not specified. */ public $courseid; /** * @var bool Add course profile link to image */ public $link = true; /** * @var int Size in pixels. Special values are (true/1 = 100px) and * (false/0 = 35px) * for backward compatibility. */ public $size = 35; /** * @var bool Add non-blank alt-text to the image. * Default true, set to false when image alt just duplicates text in screenreaders. */ public $alttext = true; /** * @var bool Whether or not to open the link in a popup window. */ public $popup = false; /** * @var string Image class attribute */ public $class = 'userpicture'; /** * @var bool Whether to be visible to screen readers. */ public $visibletoscreenreaders = true; /** * @var bool Whether to include the fullname in the user picture link. */ public $includefullname = false; /** * @var mixed Include user authentication token. True indicates to generate a token for current user, and integer value * indicates to generate a token for the user whose id is the value indicated. */ public $includetoken = false; /** * User picture constructor. * * @param stdClass $user user record with at least id, picture, imagealt, firstname and lastname set. * It is recommended to add also contextid of the user for performance reasons. */ public function __construct(stdClass $user) { global $DB; if (empty($user->id)) { throw new coding_exception('User id is required when printing user avatar image.'); } // only touch the DB if we are missing data and complain loudly... $needrec = false; foreach (self::$fields as $field) { if (!array_key_exists($field, $user)) { $needrec = true; debugging('Missing '.$field.' property in $user object, this is a performance problem that needs to be fixed by a developer. ' .'Please use user_picture::fields() to get the full list of required fields.', DEBUG_DEVELOPER); break; } } if ($needrec) { $this->user = $DB->get_record('user', array('id'=>$user->id), self::fields(), MUST_EXIST); } else { $this->user = clone($user); } } /** * Returns a list of required user fields, useful when fetching required user info from db. * * In some cases we have to fetch the user data together with some other information, * the idalias is useful there because the id would otherwise override the main * id of the result record. Please note it has to be converted back to id before rendering. * * @param string $tableprefix name of database table prefix in query * @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) * @param string $idalias alias of id field * @param string $fieldprefix prefix to add to all columns in their aliases, does not apply to 'id' * @return string */ public static function fields($tableprefix = '', array $extrafields = NULL, $idalias = 'id', $fieldprefix = '') { if (!$tableprefix and !$extrafields and !$idalias) { return implode(',', self::$fields); } if ($tableprefix) { $tableprefix .= '.'; } foreach (self::$fields as $field) { if ($field === 'id' and $idalias and $idalias !== 'id') { $fields[$field] = "$tableprefix$field AS $idalias"; } else { if ($fieldprefix and $field !== 'id') { $fields[$field] = "$tableprefix$field AS $fieldprefix$field"; } else { $fields[$field] = "$tableprefix$field"; } } } // add extra fields if not already there if ($extrafields) { foreach ($extrafields as $e) { if ($e === 'id' or isset($fields[$e])) { continue; } if ($fieldprefix) { $fields[$e] = "$tableprefix$e AS $fieldprefix$e"; } else { $fields[$e] = "$tableprefix$e"; } } } return implode(',', $fields); } /** * Extract the aliased user fields from a given record * * Given a record that was previously obtained using {@link self::fields()} with aliases, * this method extracts user related unaliased fields. * * @param stdClass $record containing user picture fields * @param array $extrafields extra fields included in the $record * @param string $idalias alias of the id field * @param string $fieldprefix prefix added to all columns in their aliases, does not apply to 'id' * @return stdClass object with unaliased user fields */ public static function unalias(stdClass $record, array $extrafields = null, $idalias = 'id', $fieldprefix = '') { if (empty($idalias)) { $idalias = 'id'; } $return = new stdClass(); foreach (self::$fields as $field) { if ($field === 'id') { if (property_exists($record, $idalias)) { $return->id = $record->{$idalias}; } } else { if (property_exists($record, $fieldprefix.$field)) { $return->{$field} = $record->{$fieldprefix.$field}; } } } // add extra fields if not already there if ($extrafields) { foreach ($extrafields as $e) { if ($e === 'id' or property_exists($return, $e)) { continue; } $return->{$e} = $record->{$fieldprefix.$e}; } } return $return; } /** * Works out the URL for the users picture. * * This method is recommended as it avoids costly redirects of user pictures * if requests are made for non-existent files etc. * * @param moodle_page $page * @param renderer_base $renderer * @return moodle_url */ public function get_url(moodle_page $page, renderer_base $renderer = null) { global $CFG; if (is_null($renderer)) { $renderer = $page->get_renderer('core'); } // Sort out the filename and size. Size is only required for the gravatar // implementation presently. if (empty($this->size)) { $filename = 'f2'; $size = 35; } else if ($this->size === true or $this->size == 1) { $filename = 'f1'; $size = 100; } else if ($this->size > 100) { $filename = 'f3'; $size = (int)$this->size; } else if ($this->size >= 50) { $filename = 'f1'; $size = (int)$this->size; } else { $filename = 'f2'; $size = (int)$this->size; } $defaulturl = $renderer->image_url('u/'.$filename); // default image if ((!empty($CFG->forcelogin) and !isloggedin()) || (!empty($CFG->forceloginforprofileimage) && (!isloggedin() || isguestuser()))) { // Protect images if login required and not logged in; // also if login is required for profile images and is not logged in or guest // do not use require_login() because it is expensive and not suitable here anyway. return $defaulturl; } // First try to detect deleted users - but do not read from database for performance reasons! if (!empty($this->user->deleted) or strpos($this->user->email, '@') === false) { // All deleted users should have email replaced by md5 hash, // all active users are expected to have valid email. return $defaulturl; } // Did the user upload a picture? if ($this->user->picture > 0) { if (!empty($this->user->contextid)) { $contextid = $this->user->contextid; } else { $context = context_user::instance($this->user->id, IGNORE_MISSING); if (!$context) { // This must be an incorrectly deleted user, all other users have context. return $defaulturl; } $contextid = $context->id; } $path = '/'; if (clean_param($page->theme->name, PARAM_THEME) == $page->theme->name) { // We append the theme name to the file path if we have it so that // in the circumstance that the profile picture is not available // when the user actually requests it they still get the profile // picture for the correct theme. $path .= $page->theme->name.'/'; } // Set the image URL to the URL for the uploaded file and return. $url = moodle_url::make_pluginfile_url( $contextid, 'user', 'icon', null, $path, $filename, false, $this->includetoken); $url->param('rev', $this->user->picture); return $url; } if ($this->user->picture == 0 and !empty($CFG->enablegravatar)) { // Normalise the size variable to acceptable bounds if ($size < 1 || $size > 512) { $size = 35; } // Hash the users email address $md5 = md5(strtolower(trim($this->user->email))); // Build a gravatar URL with what we know. // Find the best default image URL we can (MDL-35669) if (empty($CFG->gravatardefaulturl)) { $absoluteimagepath = $page->theme->resolve_image_location('u/'.$filename, 'core'); if (strpos($absoluteimagepath, $CFG->dirroot) === 0) { $gravatardefault = $CFG->wwwroot . substr($absoluteimagepath, strlen($CFG->dirroot)); } else { $gravatardefault = $CFG->wwwroot . '/pix/u/' . $filename . '.png'; } } else { $gravatardefault = $CFG->gravatardefaulturl; } // If the currently requested page is https then we'll return an // https gravatar page. if (is_https()) { return new moodle_url("https://secure.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault)); } else { return new moodle_url("http://www.gravatar.com/avatar/{$md5}", array('s' => $size, 'd' => $gravatardefault)); } } return $defaulturl; } } /** * Data structure representing a help icon. * * @copyright 2010 Petr Skoda (info@skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class help_icon implements renderable, templatable { /** * @var string lang pack identifier (without the "_help" suffix), * both get_string($identifier, $component) and get_string($identifier.'_help', $component) * must exist. */ public $identifier; /** * @var string Component name, the same as in get_string() */ public $component; /** * @var string Extra descriptive text next to the icon */ public $linktext = null; /** * Constructor * * @param string $identifier string for help page title, * string with _help suffix is used for the actual help text. * string with _link suffix is used to create a link to further info (if it exists) * @param string $component */ public function __construct($identifier, $component) { $this->identifier = $identifier; $this->component = $component; } /** * Verifies that both help strings exists, shows debug warnings if not */ public function diag_strings() { $sm = get_string_manager(); if (!$sm->string_exists($this->identifier, $this->component)) { debugging("Help title string does not exist: [$this->identifier, $this->component]"); } if (!$sm->string_exists($this->identifier.'_help', $this->component)) { debugging("Help contents string does not exist: [{$this->identifier}_help, $this->component]"); } } /** * Export this data so it can be used as the context for a mustache template. * * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. * @return array */ public function export_for_template(renderer_base $output) { global $CFG; $title = get_string($this->identifier, $this->component); if (empty($this->linktext)) { $alt = get_string('helpprefix2', '', trim($title, ". \t")); } else { $alt = get_string('helpwiththis'); } $data = get_formatted_help_string($this->identifier, $this->component, false); $data->alt = $alt; $data->icon = (new pix_icon('help', $alt, 'core', ['class' => 'iconhelp']))->export_for_template($output); $data->linktext = $this->linktext; $data->title = get_string('helpprefix2', '', trim($title, ". \t")); $options = [ 'component' => $this->component, 'identifier' => $this->identifier, 'lang' => current_language() ]; // Debugging feature lets you display string identifier and component. if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) { $options['strings'] = 1; } $data->url = (new moodle_url('/help.php', $options))->out(false); $data->ltr = !right_to_left(); return $data; } } /** * Data structure representing an icon font. * * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package core * @category output */ class pix_icon_font implements templatable { /** * @var pix_icon $pixicon The original icon. */ private $pixicon = null; /** * @var string $key The mapped key. */ private $key; /** * @var bool $mapped The icon could not be mapped. */ private $mapped; /** * Constructor * * @param pix_icon $pixicon The original icon */ public function __construct(pix_icon $pixicon) { global $PAGE; $this->pixicon = $pixicon; $this->mapped = false; $iconsystem = \core\output\icon_system::instance(); $this->key = $iconsystem->remap_icon_name($pixicon->pix, $pixicon->component); if (!empty($this->key)) { $this->mapped = true; } } /** * Return true if this pix_icon was successfully mapped to an icon font. * * @return bool */ public function is_mapped() { return $this->mapped; } /** * Export this data so it can be used as the context for a mustache template. * * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. * @return array */ public function export_for_template(renderer_base $output) { $pixdata = $this->pixicon->export_for_template($output); $title = isset($this->pixicon->attributes['title']) ? $this->pixicon->attributes['title'] : ''; $alt = isset($this->pixicon->attributes['alt']) ? $this->pixicon->attributes['alt'] : ''; if (empty($title)) { $title = $alt; } $data = array( 'extraclasses' => $pixdata['extraclasses'], 'title' => $title, 'alt' => $alt, 'key' => $this->key ); return $data; } } /** * Data structure representing an icon subtype. * * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package core * @category output */ class pix_icon_fontawesome extends pix_icon_font { } /** * Data structure representing an icon. * * @copyright 2010 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class pix_icon implements renderable, templatable { /** * @var string The icon name */ var $pix; /** * @var string The component the icon belongs to. */ var $component; /** * @var array An array of attributes to use on the icon */ var $attributes = array(); /** * Constructor * * @param string $pix short icon name * @param string $alt The alt text to use for the icon * @param string $component component name * @param array $attributes html attributes */ public function __construct($pix, $alt, $component='moodle', array $attributes = null) { global $PAGE; $this->pix = $pix; $this->component = $component; $this->attributes = (array)$attributes; if (empty($this->attributes['class'])) { $this->attributes['class'] = ''; } // Set an additional class for big icons so that they can be styled properly. if (substr($pix, 0, 2) === 'b/') { $this->attributes['class'] .= ' iconsize-big'; } // If the alt is empty, don't place it in the attributes, otherwise it will override parent alt text. if (!is_null($alt)) { $this->attributes['alt'] = $alt; // If there is no title, set it to the attribute. if (!isset($this->attributes['title'])) { $this->attributes['title'] = $this->attributes['alt']; } } else { unset($this->attributes['alt']); } if (empty($this->attributes['title'])) { // Remove the title attribute if empty, we probably want to use the parent node's title // and some browsers might overwrite it with an empty title. unset($this->attributes['title']); } // Hide icons from screen readers that have no alt. if (empty($this->attributes['alt'])) { $this->attributes['aria-hidden'] = 'true'; } } /** * Export this data so it can be used as the context for a mustache template. * * @param renderer_base $output Used to do a final render of any components that need to be rendered for export. * @return array */ public function export_for_template(renderer_base $output) { $attributes = $this->attributes; $extraclasses = ''; foreach ($attributes as $key => $item) { if ($key == 'class') { $extraclasses = $item; unset($attributes[$key]); break; } } $attributes['src'] = $output->image_url($this->pix, $this->component)->out(false); $templatecontext = array(); foreach ($attributes as $name => $value) { $templatecontext[] = array('name' => $name, 'value' => $value); } $title = isset($attributes['title']) ? $attributes['title'] : ''; if (empty($title)) { $title = isset($attributes['alt']) ? $attributes['alt'] : ''; } $data = array( 'attributes' => $templatecontext, 'extraclasses' => $extraclasses ); return $data; } /** * Much simpler version of export that will produce the data required to render this pix with the * pix helper in a mustache tag. * * @return array */ public function export_for_pix() { $title = isset($this->attributes['title']) ? $this->attributes['title'] : ''; if (empty($title)) { $title = isset($this->attributes['alt']) ? $this->attributes['alt'] : ''; } return [ 'key' => $this->pix, 'component' => $this->component, 'title' => $title ]; } } /** * Data structure representing an activity icon. * * The difference is that activity icons will always render with the standard icon system (no font icons). * * @copyright 2017 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package core */ class image_icon extends pix_icon { } /** * Data structure representing an emoticon image * * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class pix_emoticon extends pix_icon implements renderable { /** * Constructor * @param string $pix short icon name * @param string $alt alternative text * @param string $component emoticon image provider * @param array $attributes explicit HTML attributes */ public function __construct($pix, $alt, $component = 'moodle', array $attributes = array()) { if (empty($attributes['class'])) { $attributes['class'] = 'emoticon'; } parent::__construct($pix, $alt, $component, $attributes); } } /** * Data structure representing a simple form with only one button. * * @copyright 2009 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class single_button implements renderable { /** * @var moodle_url Target url */ public $url; /** * @var string Button label */ public $label; /** * @var string Form submit method post or get */ public $method = 'post'; /** * @var string Wrapping div class */ public $class = 'singlebutton'; /** * @var bool True if button is primary button. Used for styling. */ public $primary = false; /** * @var bool True if button disabled, false if normal */ public $disabled = false; /** * @var string Button tooltip */ public $tooltip = null; /** * @var string Form id */ public $formid; /** * @var array List of attached actions */ public $actions = array(); /** * @var array $params URL Params */ public $params; /** * @var string Action id */ public $actionid; /** * Constructor * @param moodle_url $url * @param string $label button text * @param string $method get or post submit method */ public function __construct(moodle_url $url, $label, $method='post', $primary=false) { $this->url = clone($url); $this->label = $label; $this->method = $method; $this->primary = $primary; } /** * Shortcut for adding a JS confirm dialog when the button is clicked. * The message must be a yes/no question. * * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur. */ public function add_confirm_action($confirmmessage) { $this->add_action(new confirm_action($confirmmessage)); } /** * Add action to the button. * @param component_action $action */ public function add_action(component_action $action) { $this->actions[] = $action; } /** * Export data. * * @param renderer_base $output Renderer. * @return stdClass */ public function export_for_template(renderer_base $output) { $url = $this->method === 'get' ? $this->url->out_omit_querystring(true) : $this->url->out_omit_querystring(); $data = new stdClass(); $data->id = html_writer::random_id('single_button'); $data->formid = $this->formid; $data->method = $this->method; $data->url = $url === '' ? '#' : $url; $data->label = $this->label; $data->classes = $this->class; $data->disabled = $this->disabled; $data->tooltip = $this->tooltip; $data->primary = $this->primary; // Form parameters. $params = $this->url->params(); if ($this->method === 'post') { $params['sesskey'] = sesskey(); } $data->params = array_map(function($key) use ($params) { return ['name' => $key, 'value' => $params[$key]]; }, array_keys($params)); // Button actions. $actions = $this->actions; $data->actions = array_map(function($action) use ($output) { return $action->export_for_template($output); }, $actions); $data->hasactions = !empty($data->actions); return $data; } } /** * Simple form with just one select field that gets submitted automatically. * * If JS not enabled small go button is printed too. * * @copyright 2009 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class single_select implements renderable, templatable { /** * @var moodle_url Target url - includes hidden fields */ var $url; /** * @var string Name of the select element. */ var $name; /** * @var array $options associative array value=>label ex.: array(1=>'One, 2=>Two) * it is also possible to specify optgroup as complex label array ex.: * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) */ var $options; /** * @var string Selected option */ var $selected; /** * @var array Nothing selected */ var $nothing; /** * @var array Extra select field attributes */ var $attributes = array(); /** * @var string Button label */ var $label = ''; /** * @var array Button label's attributes */ var $labelattributes = array(); /** * @var string Form submit method post or get */ var $method = 'get'; /** * @var string Wrapping div class */ var $class = 'singleselect'; /** * @var bool True if button disabled, false if normal */ var $disabled = false; /** * @var string Button tooltip */ var $tooltip = null; /** * @var string Form id */ var $formid = null; /** * @var help_icon The help icon for this element. */ var $helpicon = null; /** * Constructor * @param moodle_url $url form action target, includes hidden fields * @param string $name name of selection field - the changing parameter in url * @param array $options list of options * @param string $selected selected element * @param array $nothing * @param string $formid */ public function __construct(moodle_url $url, $name, array $options, $selected = '', $nothing = array('' => 'choosedots'), $formid = null) { $this->url = $url; $this->name = $name; $this->options = $options; $this->selected = $selected; $this->nothing = $nothing; $this->formid = $formid; } /** * Shortcut for adding a JS confirm dialog when the button is clicked. * The message must be a yes/no question. * * @param string $confirmmessage The yes/no confirmation question. If "Yes" is clicked, the original action will occur. */ public function add_confirm_action($confirmmessage) { $this->add_action(new component_action('submit', 'M.util.show_confirm_dialog', array('message' => $confirmmessage))); } /** * Add action to the button. * * @param component_action $action */ public function add_action(component_action $action) { $this->actions[] = $action; } /** * Adds help icon. * * @deprecated since Moodle 2.0 */ public function set_old_help_icon($helppage, $title, $component = 'moodle') { throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().'); } /** * Adds help icon. * * @param string $identifier The keyword that defines a help page * @param string $component */ public function set_help_icon($identifier, $component = 'moodle') { $this->helpicon = new help_icon($identifier, $component); } /** * Sets select's label * * @param string $label * @param array $attributes (optional) */ public function set_label($label, $attributes = array()) { $this->label = $label; $this->labelattributes = $attributes; } /** * Export data. * * @param renderer_base $output Renderer. * @return stdClass */ public function export_for_template(renderer_base $output) { $attributes = $this->attributes; $data = new stdClass(); $data->name = $this->name; $data->method = $this->method; $data->action = $this->method === 'get' ? $this->url->out_omit_querystring(true) : $this->url->out_omit_querystring(); $data->classes = $this->class; $data->label = $this->label; $data->disabled = $this->disabled; $data->title = $this->tooltip; $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('single_select_f'); $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('single_select'); // Select element attributes. // Unset attributes that are already predefined in the template. unset($attributes['id']); unset($attributes['class']); unset($attributes['name']); unset($attributes['title']); unset($attributes['disabled']); // Map the attributes. $data->attributes = array_map(function($key) use ($attributes) { return ['name' => $key, 'value' => $attributes[$key]]; }, array_keys($attributes)); // Form parameters. $params = $this->url->params(); if ($this->method === 'post') { $params['sesskey'] = sesskey(); } $data->params = array_map(function($key) use ($params) { return ['name' => $key, 'value' => $params[$key]]; }, array_keys($params)); // Select options. $hasnothing = false; if (is_string($this->nothing) && $this->nothing !== '') { $nothing = ['' => $this->nothing]; $hasnothing = true; $nothingkey = ''; } else if (is_array($this->nothing)) { $nothingvalue = reset($this->nothing); if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') { $nothing = [key($this->nothing) => get_string('choosedots')]; } else { $nothing = $this->nothing; } $hasnothing = true; $nothingkey = key($this->nothing); } if ($hasnothing) { $options = $nothing + $this->options; } else { $options = $this->options; } foreach ($options as $value => $name) { if (is_array($options[$value])) { foreach ($options[$value] as $optgroupname => $optgroupvalues) { $sublist = []; foreach ($optgroupvalues as $optvalue => $optname) { $option = [ 'value' => $optvalue, 'name' => $optname, 'selected' => strval($this->selected) === strval($optvalue), ]; if ($hasnothing && $nothingkey === $optvalue) { $option['ignore'] = 'data-ignore'; } $sublist[] = $option; } $data->options[] = [ 'name' => $optgroupname, 'optgroup' => true, 'options' => $sublist ]; } } else { $option = [ 'value' => $value, 'name' => $options[$value], 'selected' => strval($this->selected) === strval($value), 'optgroup' => false ]; if ($hasnothing && $nothingkey === $value) { $option['ignore'] = 'data-ignore'; } $data->options[] = $option; } } // Label attributes. $data->labelattributes = []; // Unset label attributes that are already in the template. unset($this->labelattributes['for']); // Map the label attributes. foreach ($this->labelattributes as $key => $value) { $data->labelattributes[] = ['name' => $key, 'value' => $value]; } // Help icon. $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false; return $data; } } /** * Simple URL selection widget description. * * @copyright 2009 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class url_select implements renderable, templatable { /** * @var array $urls associative array value=>label ex.: array(1=>'One, 2=>Two) * it is also possible to specify optgroup as complex label array ex.: * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) */ var $urls; /** * @var string Selected option */ var $selected; /** * @var array Nothing selected */ var $nothing; /** * @var array Extra select field attributes */ var $attributes = array(); /** * @var string Button label */ var $label = ''; /** * @var array Button label's attributes */ var $labelattributes = array(); /** * @var string Wrapping div class */ var $class = 'urlselect'; /** * @var bool True if button disabled, false if normal */ var $disabled = false; /** * @var string Button tooltip */ var $tooltip = null; /** * @var string Form id */ var $formid = null; /** * @var help_icon The help icon for this element. */ var $helpicon = null; /** * @var string If set, makes button visible with given name for button */ var $showbutton = null; /** * Constructor * @param array $urls list of options * @param string $selected selected element * @param array $nothing * @param string $formid * @param string $showbutton Set to text of button if it should be visible * or null if it should be hidden (hidden version always has text 'go') */ public function __construct(array $urls, $selected = '', $nothing = array('' => 'choosedots'), $formid = null, $showbutton = null) { $this->urls = $urls; $this->selected = $selected; $this->nothing = $nothing; $this->formid = $formid; $this->showbutton = $showbutton; } /** * Adds help icon. * * @deprecated since Moodle 2.0 */ public function set_old_help_icon($helppage, $title, $component = 'moodle') { throw new coding_exception('set_old_help_icon() can not be used any more, please see set_help_icon().'); } /** * Adds help icon. * * @param string $identifier The keyword that defines a help page * @param string $component */ public function set_help_icon($identifier, $component = 'moodle') { $this->helpicon = new help_icon($identifier, $component); } /** * Sets select's label * * @param string $label * @param array $attributes (optional) */ public function set_label($label, $attributes = array()) { $this->label = $label; $this->labelattributes = $attributes; } /** * Clean a URL. * * @param string $value The URL. * @return The cleaned URL. */ protected function clean_url($value) { global $CFG; if (empty($value)) { // Nothing. } else if (strpos($value, $CFG->wwwroot . '/') === 0) { $value = str_replace($CFG->wwwroot, '', $value); } else if (strpos($value, '/') !== 0) { debugging("Invalid url_select urls parameter: url '$value' is not local relative url!", DEBUG_DEVELOPER); } return $value; } /** * Flatten the options for Mustache. * * This also cleans the URLs. * * @param array $options The options. * @param array $nothing The nothing option. * @return array */ protected function flatten_options($options, $nothing) { $flattened = []; foreach ($options as $value => $option) { if (is_array($option)) { foreach ($option as $groupname => $optoptions) { if (!isset($flattened[$groupname])) { $flattened[$groupname] = [ 'name' => $groupname, 'isgroup' => true, 'options' => [] ]; } foreach ($optoptions as $optvalue => $optoption) { $cleanedvalue = $this->clean_url($optvalue); $flattened[$groupname]['options'][$cleanedvalue] = [ 'name' => $optoption, 'value' => $cleanedvalue, 'selected' => $this->selected == $optvalue, ]; } } } else { $cleanedvalue = $this->clean_url($value); $flattened[$cleanedvalue] = [ 'name' => $option, 'value' => $cleanedvalue, 'selected' => $this->selected == $value, ]; } } if (!empty($nothing)) { $value = key($nothing); $name = reset($nothing); $flattened = [ $value => ['name' => $name, 'value' => $value, 'selected' => $this->selected == $value] ] + $flattened; } // Make non-associative array. foreach ($flattened as $key => $value) { if (!empty($value['options'])) { $flattened[$key]['options'] = array_values($value['options']); } } $flattened = array_values($flattened); return $flattened; } /** * Export for template. * * @param renderer_base $output Renderer. * @return stdClass */ public function export_for_template(renderer_base $output) { $attributes = $this->attributes; $data = new stdClass(); $data->formid = !empty($this->formid) ? $this->formid : html_writer::random_id('url_select_f'); $data->classes = $this->class; $data->label = $this->label; $data->disabled = $this->disabled; $data->title = $this->tooltip; $data->id = !empty($attributes['id']) ? $attributes['id'] : html_writer::random_id('url_select'); $data->sesskey = sesskey(); $data->action = (new moodle_url('/course/jumpto.php'))->out(false); // Remove attributes passed as property directly. unset($attributes['class']); unset($attributes['id']); unset($attributes['name']); unset($attributes['title']); unset($attributes['disabled']); $data->showbutton = $this->showbutton; // Select options. $nothing = false; if (is_string($this->nothing) && $this->nothing !== '') { $nothing = ['' => $this->nothing]; } else if (is_array($this->nothing)) { $nothingvalue = reset($this->nothing); if ($nothingvalue === 'choose' || $nothingvalue === 'choosedots') { $nothing = [key($this->nothing) => get_string('choosedots')]; } else { $nothing = $this->nothing; } } $data->options = $this->flatten_options($this->urls, $nothing); // Label attributes. $data->labelattributes = []; // Unset label attributes that are already in the template. unset($this->labelattributes['for']); // Map the label attributes. foreach ($this->labelattributes as $key => $value) { $data->labelattributes[] = ['name' => $key, 'value' => $value]; } // Help icon. $data->helpicon = !empty($this->helpicon) ? $this->helpicon->export_for_template($output) : false; // Finally all the remaining attributes. $data->attributes = []; foreach ($attributes as $key => $value) { $data->attributes[] = ['name' => $key, 'value' => $value]; } return $data; } } /** * Data structure describing html link with special action attached. * * @copyright 2010 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class action_link implements renderable { /** * @var moodle_url Href url */ public $url; /** * @var string Link text HTML fragment */ public $text; /** * @var array HTML attributes */ public $attributes; /** * @var array List of actions attached to link */ public $actions; /** * @var pix_icon Optional pix icon to render with the link */ public $icon; /** * Constructor * @param moodle_url $url * @param string $text HTML fragment * @param component_action $action * @param array $attributes associative array of html link attributes + disabled * @param pix_icon $icon optional pix_icon to render with the link text */ public function __construct(moodle_url $url, $text, component_action $action=null, array $attributes=null, pix_icon $icon=null) { $this->url = clone($url); $this->text = $text; $this->attributes = (array)$attributes; if ($action) { $this->add_action($action); } $this->icon = $icon; } /** * Add action to the link. * * @param component_action $action */ public function add_action(component_action $action) { $this->actions[] = $action; } /** * Adds a CSS class to this action link object * @param string $class */ public function add_class($class) { if (empty($this->attributes['class'])) { $this->attributes['class'] = $class; } else { $this->attributes['class'] .= ' ' . $class; } } /** * Returns true if the specified class has been added to this link. * @param string $class * @return bool */ public function has_class($class) { return strpos(' ' . $this->attributes['class'] . ' ', ' ' . $class . ' ') !== false; } /** * Return the rendered HTML for the icon. Useful for rendering action links in a template. * @return string */ public function get_icon_html() { global $OUTPUT; if (!$this->icon) { return ''; } return $OUTPUT->render($this->icon); } /** * Export for template. * * @param renderer_base $output The renderer. * @return stdClass */ public function export_for_template(renderer_base $output) { $data = new stdClass(); $attributes = $this->attributes; if (empty($attributes['id'])) { $attributes['id'] = html_writer::random_id('action_link'); } $data->id = $attributes['id']; unset($attributes['id']); $data->disabled = !empty($attributes['disabled']); unset($attributes['disabled']); $data->text = $this->text instanceof renderable ? $output->render($this->text) : (string) $this->text; $data->url = $this->url ? $this->url->out(false) : ''; $data->icon = $this->icon ? $this->icon->export_for_pix() : null; $data->classes = isset($attributes['class']) ? $attributes['class'] : ''; unset($attributes['class']); $data->attributes = array_map(function($key, $value) { return [ 'name' => $key, 'value' => $value ]; }, array_keys($attributes), $attributes); $data->actions = array_map(function($action) use ($output) { return $action->export_for_template($output); }, !empty($this->actions) ? $this->actions : []); $data->hasactions = !empty($this->actions); return $data; } } /** * Simple html output class * * @copyright 2009 Tim Hunt, 2010 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since Moodle 2.0 * @package core * @category output */ class html_writer { /** * Outputs a tag with attributes and contents * * @param string $tagname The name of tag ('a', 'img', 'span' etc.) * @param string $contents What goes between the opening and closing tags * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) * @return string HTML fragment */ public static function tag($tagname, $contents, array $attributes = null) { return self::start_tag($tagname, $attributes) . $contents . self::end_tag($tagname); } /** * Outputs an opening tag with attributes * * @param string $tagname The name of tag ('a', 'img', 'span' etc.) * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) * @return string HTML fragment */ public static function start_tag($tagname, array $attributes = null) { return '<' . $tagname . self::attributes($attributes) . '>'; } /** * Outputs a closing tag * * @param string $tagname The name of tag ('a', 'img', 'span' etc.) * @return string HTML fragment */ public static function end_tag($tagname) { return ''; } /** * Outputs an empty tag with attributes * * @param string $tagname The name of tag ('input', 'img', 'br' etc.) * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) * @return string HTML fragment */ public static function empty_tag($tagname, array $attributes = null) { return '<' . $tagname . self::attributes($attributes) . ' />'; } /** * Outputs a tag, but only if the contents are not empty * * @param string $tagname The name of tag ('a', 'img', 'span' etc.) * @param string $contents What goes between the opening and closing tags * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) * @return string HTML fragment */ public static function nonempty_tag($tagname, $contents, array $attributes = null) { if ($contents === '' || is_null($contents)) { return ''; } return self::tag($tagname, $contents, $attributes); } /** * Outputs a HTML attribute and value * * @param string $name The name of the attribute ('src', 'href', 'class' etc.) * @param string $value The value of the attribute. The value will be escaped with {@link s()} * @return string HTML fragment */ public static function attribute($name, $value) { if ($value instanceof moodle_url) { return ' ' . $name . '="' . $value->out() . '"'; } // special case, we do not want these in output if ($value === null) { return ''; } // no sloppy trimming here! return ' ' . $name . '="' . s($value) . '"'; } /** * Outputs a list of HTML attributes and values * * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) * The values will be escaped with {@link s()} * @return string HTML fragment */ public static function attributes(array $attributes = null) { $attributes = (array)$attributes; $output = ''; foreach ($attributes as $name => $value) { $output .= self::attribute($name, $value); } return $output; } /** * Generates a simple image tag with attributes. * * @param string $src The source of image * @param string $alt The alternate text for image * @param array $attributes The tag attributes (array('height' => $max_height, 'class' => 'class1') etc.) * @return string HTML fragment */ public static function img($src, $alt, array $attributes = null) { $attributes = (array)$attributes; $attributes['src'] = $src; $attributes['alt'] = $alt; return self::empty_tag('img', $attributes); } /** * Generates random html element id. * * @staticvar int $counter * @staticvar type $uniq * @param string $base A string fragment that will be included in the random ID. * @return string A unique ID */ public static function random_id($base='random') { static $counter = 0; static $uniq; if (!isset($uniq)) { $uniq = uniqid(); } $counter++; return $base.$uniq.$counter; } /** * Generates a simple html link * * @param string|moodle_url $url The URL * @param string $text The text * @param array $attributes HTML attributes * @return string HTML fragment */ public static function link($url, $text, array $attributes = null) { $attributes = (array)$attributes; $attributes['href'] = $url; return self::tag('a', $text, $attributes); } /** * Generates a simple checkbox with optional label * * @param string $name The name of the checkbox * @param string $value The value of the checkbox * @param bool $checked Whether the checkbox is checked * @param string $label The label for the checkbox * @param array $attributes Any attributes to apply to the checkbox * @return string html fragment */ public static function checkbox($name, $value, $checked = true, $label = '', array $attributes = null) { $attributes = (array)$attributes; $output = ''; if ($label !== '' and !is_null($label)) { if (empty($attributes['id'])) { $attributes['id'] = self::random_id('checkbox_'); } } $attributes['type'] = 'checkbox'; $attributes['value'] = $value; $attributes['name'] = $name; $attributes['checked'] = $checked ? 'checked' : null; $output .= self::empty_tag('input', $attributes); if ($label !== '' and !is_null($label)) { $output .= self::tag('label', $label, array('for'=>$attributes['id'])); } return $output; } /** * Generates a simple select yes/no form field * * @param string $name name of select element * @param bool $selected * @param array $attributes - html select element attributes * @return string HTML fragment */ public static function select_yes_no($name, $selected=true, array $attributes = null) { $options = array('1'=>get_string('yes'), '0'=>get_string('no')); return self::select($options, $name, $selected, null, $attributes); } /** * Generates a simple select form field * * @param array $options associative array value=>label ex.: * array(1=>'One, 2=>Two) * it is also possible to specify optgroup as complex label array ex.: * array(array('Odd'=>array(1=>'One', 3=>'Three)), array('Even'=>array(2=>'Two'))) * array(1=>'One', '--1uniquekey'=>array('More'=>array(2=>'Two', 3=>'Three'))) * @param string $name name of select element * @param string|array $selected value or array of values depending on multiple attribute * @param array|bool $nothing add nothing selected option, or false of not added * @param array $attributes html select element attributes * @return string HTML fragment */ public static function select(array $options, $name, $selected = '', $nothing = array('' => 'choosedots'), array $attributes = null) { $attributes = (array)$attributes; if (is_array($nothing)) { foreach ($nothing as $k=>$v) { if ($v === 'choose' or $v === 'choosedots') { $nothing[$k] = get_string('choosedots'); } } $options = $nothing + $options; // keep keys, do not override } else if (is_string($nothing) and $nothing !== '') { // BC $options = array(''=>$nothing) + $options; } // we may accept more values if multiple attribute specified $selected = (array)$selected; foreach ($selected as $k=>$v) { $selected[$k] = (string)$v; } if (!isset($attributes['id'])) { $id = 'menu'.$name; // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading $id = str_replace('[', '', $id); $id = str_replace(']', '', $id); $attributes['id'] = $id; } if (!isset($attributes['class'])) { $class = 'menu'.$name; // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading $class = str_replace('[', '', $class); $class = str_replace(']', '', $class); $attributes['class'] = $class; } $attributes['class'] = 'select custom-select ' . $attributes['class']; // Add 'select' selector always. $attributes['name'] = $name; if (!empty($attributes['disabled'])) { $attributes['disabled'] = 'disabled'; } else { unset($attributes['disabled']); } $output = ''; foreach ($options as $value=>$label) { if (is_array($label)) { // ignore key, it just has to be unique $output .= self::select_optgroup(key($label), current($label), $selected); } else { $output .= self::select_option($label, $value, $selected); } } return self::tag('select', $output, $attributes); } /** * Returns HTML to display a select box option. * * @param string $label The label to display as the option. * @param string|int $value The value the option represents * @param array $selected An array of selected options * @return string HTML fragment */ private static function select_option($label, $value, array $selected) { $attributes = array(); $value = (string)$value; if (in_array($value, $selected, true)) { $attributes['selected'] = 'selected'; } $attributes['value'] = $value; return self::tag('option', $label, $attributes); } /** * Returns HTML to display a select box option group. * * @param string $groupname The label to use for the group * @param array $options The options in the group * @param array $selected An array of selected values. * @return string HTML fragment. */ private static function select_optgroup($groupname, $options, array $selected) { if (empty($options)) { return ''; } $attributes = array('label'=>$groupname); $output = ''; foreach ($options as $value=>$label) { $output .= self::select_option($label, $value, $selected); } return self::tag('optgroup', $output, $attributes); } /** * This is a shortcut for making an hour selector menu. * * @param string $type The type of selector (years, months, days, hours, minutes) * @param string $name fieldname * @param int $currenttime A default timestamp in GMT * @param int $step minute spacing * @param array $attributes - html select element attributes * @return HTML fragment */ public static function select_time($type, $name, $currenttime = 0, $step = 5, array $attributes = null) { global $OUTPUT; if (!$currenttime) { $currenttime = time(); } $calendartype = \core_calendar\type_factory::get_calendar_instance(); $currentdate = $calendartype->timestamp_to_date_array($currenttime); $userdatetype = $type; $timeunits = array(); switch ($type) { case 'years': $timeunits = $calendartype->get_years(); $userdatetype = 'year'; break; case 'months': $timeunits = $calendartype->get_months(); $userdatetype = 'month'; $currentdate['month'] = (int)$currentdate['mon']; break; case 'days': $timeunits = $calendartype->get_days(); $userdatetype = 'mday'; break; case 'hours': for ($i=0; $i<=23; $i++) { $timeunits[$i] = sprintf("%02d",$i); } break; case 'minutes': if ($step != 1) { $currentdate['minutes'] = ceil($currentdate['minutes']/$step)*$step; } for ($i=0; $i<=59; $i+=$step) { $timeunits[$i] = sprintf("%02d",$i); } break; default: throw new coding_exception("Time type $type is not supported by html_writer::select_time()."); } $attributes = (array) $attributes; $data = (object) [ 'name' => $name, 'id' => !empty($attributes['id']) ? $attributes['id'] : self::random_id('ts_'), 'label' => get_string(substr($type, 0, -1), 'form'), 'options' => array_map(function($value) use ($timeunits, $currentdate, $userdatetype) { return [ 'name' => $timeunits[$value], 'value' => $value, 'selected' => $currentdate[$userdatetype] == $value ]; }, array_keys($timeunits)), ]; unset($attributes['id']); unset($attributes['name']); $data->attributes = array_map(function($name) use ($attributes) { return [ 'name' => $name, 'value' => $attributes[$name] ]; }, array_keys($attributes)); return $OUTPUT->render_from_template('core/select_time', $data); } /** * Shortcut for quick making of lists * * Note: 'list' is a reserved keyword ;-) * * @param array $items * @param array $attributes * @param string $tag ul or ol * @return string */ public static function alist(array $items, array $attributes = null, $tag = 'ul') { $output = html_writer::start_tag($tag, $attributes)."\n"; foreach ($items as $item) { $output .= html_writer::tag('li', $item)."\n"; } $output .= html_writer::end_tag($tag); return $output; } /** * Returns hidden input fields created from url parameters. * * @param moodle_url $url * @param array $exclude list of excluded parameters * @return string HTML fragment */ public static function input_hidden_params(moodle_url $url, array $exclude = null) { $exclude = (array)$exclude; $params = $url->params(); foreach ($exclude as $key) { unset($params[$key]); } $output = ''; foreach ($params as $key => $value) { $attributes = array('type'=>'hidden', 'name'=>$key, 'value'=>$value); $output .= self::empty_tag('input', $attributes)."\n"; } return $output; } /** * Generate a script tag containing the the specified code. * * @param string $jscode the JavaScript code * @param moodle_url|string $url optional url of the external script, $code ignored if specified * @return string HTML, the code wrapped in