diff --git a/lib/outputactions.php b/lib/outputactions.php new file mode 100644 index 00000000000..3d29553b481 --- /dev/null +++ b/lib/outputactions.php @@ -0,0 +1,160 @@ +. + +/** + * Classes representing JS event handlers, used by output components. + * + * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML + * for an overview. + * + * @package moodlecore + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Helper class used by other components that involve an action on the page (URL or JS). + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class component_action { + + /** + * The DOM event that will trigger this action when caught + * @var string $event DOM event + */ + public $event; + + /** + * The JS function you create must have two arguments: + * 1. The event object + * 2. An object/array of arguments ($jsfunctionargs) + * @var string $jsfunction A function name to call when the button is clicked + */ + public $jsfunction = false; + + /** + * @var array $jsfunctionargs An array of arguments to pass to the JS function + */ + public $jsfunctionargs = array(); + + /** + * Constructor + * @param string $event DOM event + * @param moodle_url $url A moodle_url object, required if no jsfunction is given + * @param string $method 'post' or 'get' + * @param string $jsfunction An optional JS function. Required if jsfunctionargs is given + * @param array $jsfunctionargs An array of arguments to pass to the jsfunction + * @return void + */ + public function __construct($event, $jsfunction, $jsfunctionargs=array()) { + $this->event = $event; + + $this->jsfunction = $jsfunction; + $this->jsfunctionargs = $jsfunctionargs; + + if (!empty($this->jsfunctionargs)) { + if (empty($this->jsfunction)) { + throw new coding_exception('The component_action object needs a jsfunction value to pass the jsfunctionargs to.'); + } + } + } +} + +/** + * Component action for a popup window. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class popup_action extends component_action { + /** + * @var array $params An array of parameters that will be passed to the openpopup JS function + */ + public $params = array( + 'height' => 400, + 'width' => 500, + 'top' => 0, + 'left' => 0, + 'menubar' => false, + 'location' => false, + 'scrollbars' => true, + 'resizable' => true, + 'toolbar' => true, + 'status' => true, + 'directories' => false, + 'fullscreen' => false, + 'dependent' => true); + + /** + * Constructor + * @param string $event DOM event + * @param moodle_url $url A moodle_url object, required if no jsfunction is given + * @param string $method 'post' or 'get' + * @param array $params An array of popup parameters + * @return void + */ + public function __construct($event, $url, $name='popup', $params=array()) { + global $CFG; + $this->name = $name; + + $url = new moodle_url($url); + + if ($this->name) { + $_name = $this->name; + if (($_name = preg_replace("/\s/", '_', $_name)) != $this->name) { + throw new coding_exception('The $name of a popup window shouldn\'t contain spaces - string modified. '. $this->name .' changed to '. $_name); + $this->name = $_name; + } + } else { + $this->name = 'popup'; + } + + foreach ($this->params as $var => $val) { + if (array_key_exists($var, $params)) { + $this->params[$var] = $params[$var]; + } + } + parent::__construct($event, 'openpopup', array('url' => $url->out(false, array(), false), 'name' => $name, 'options' => $this->get_js_options($params))); + } + + /** + * Returns a string of concatenated option->value pairs used by JS to call the popup window, + * based on this object's variables + * + * @return string String of option->value pairs for JS popup function. + */ + public function get_js_options() { + $jsoptions = ''; + + foreach ($this->params as $var => $val) { + if (is_string($val) || is_int($val)) { + $jsoptions .= "$var=$val,"; + } elseif (is_bool($val)) { + $jsoptions .= ($val) ? "$var," : "$var=0,"; + } + } + + $jsoptions = substr($jsoptions, 0, strlen($jsoptions) - 1); + + return $jsoptions; + } +} + diff --git a/lib/outputcomponents.php b/lib/outputcomponents.php new file mode 100644 index 00000000000..37e9931111c --- /dev/null +++ b/lib/outputcomponents.php @@ -0,0 +1,2113 @@ +. + +/** + * Classes representing HTML elements, used by $OUTPUT methods + * + * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML + * for an overview. + * + * @package moodlecore + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Base class for classes representing HTML elements, like moodle_select. + * + * Handles the id and class attributes. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class moodle_html_component { + /** + * @var string value to use for the id attribute of this HTML tag. + */ + public $id = ''; + /** + * @var string $alt value to use for the alt attribute of this HTML tag. + */ + public $alt = ''; + /** + * @var string $style value to use for the style attribute of this HTML tag. + */ + public $style = ''; + /** + * @var array class names to add to this HTML element. + */ + public $classes = array(); + /** + * @var string $title The title attributes applicable to any XHTML element + */ + public $title = ''; + /** + * An optional array of component_action objects handling the action part of this component. + * @var array $actions + */ + protected $actions = array(); + /** + * This array of generated ids is kept static to avoid id collisions + * @var array $generated_ids + */ + public static $generated_ids = array(); + + /** + * Ensure some class names are an array. + * @param mixed $classes either an array of class names or a space-separated + * string containing class names. + * @return array the class names as an array. + */ + public static function clean_classes($classes) { + if (is_array($classes)) { + return $classes; + } else { + return explode(' ', trim($classes)); + } + } + + /** + * Set the class name array. + * @param mixed $classes either an array of class names or a space-separated + * string containing class names. + * @return void + */ + public function set_classes($classes) { + $this->classes = self::clean_classes($classes); + } + + /** + * Add a class name to the class names array. + * @param string $class the new class name to add. + * @return void + */ + public function add_class($class) { + $this->classes[] = $class; + } + + /** + * Add a whole lot of class names to the class names array. + * @param mixed $classes either an array of class names or a space-separated + * string containing class names. + * @return void + */ + public function add_classes($classes) { + $this->classes += self::clean_classes($classes); + } + + /** + * Get the class names as a string. + * @return string the class names as a space-separated string. Ready to be put in the class="" attribute. + */ + public function get_classes_string() { + return implode(' ', $this->classes); + } + + /** + * Perform any cleanup or final processing that should be done before an + * instance of this class is output. + * @return void + */ + public function prepare() { + $this->classes = array_unique(self::clean_classes($this->classes)); + } + + /** + * This checks developer do not try to assign a property directly + * if we have a setter for it. Otherwise, the property is set as expected. + * @param string $name The name of the variable to set + * @param mixed $value The value to assign to the variable + * @return void + */ + public function __set($name, $value) { + if ($name == 'class') { + debugging('this way of setting css class has been deprecated. use set_classes() method instead.'); + $this->set_classes($value); + } else { + $this->{$name} = $value; + } + } + + /** + * Adds a JS action to this component. + * Note: the JS function you write must have only two arguments: (string)event and (object|array)args + * If you want to add an instantiated component_action (or one of its subclasses), give the object as the only parameter + * + * @param mixed $event a DOM event (click, mouseover etc.) or a component_action object + * @param string $jsfunction The name of the JS function to call. required if argument 1 is a string (event) + * @param array $jsfunctionargs An optional array of JS arguments to pass to the function + */ + public function add_action($event, $jsfunction=null, $jsfunctionargs=array()) { + if (empty($this->id)) { + $this->generate_id(); + } + + if ($event instanceof component_action) { + $this->actions[] = $event; + } else { + if (empty($jsfunction)) { + throw new coding_exception('moodle_html_component::add_action requires a JS function argument if the first argument is a string event'); + } + $this->actions[] = new component_action($event, $jsfunction, $jsfunctionargs); + } + } + + /** + * Internal method for generating a unique ID for the purpose of event handlers. + */ + protected function generate_id() { + // Generate an id that is not already used. + do { + $newid = get_class($this) . '-' . substr(sha1(microtime() * rand(0, 500)), 0, 6); + } while (in_array($this->id, moodle_html_component::$generated_ids)); + $this->id = $newid; + moodle_html_component::$generated_ids[] = $newid; + } + + /** + * Returns the array of component_actions. + * @return array Component actions + */ + public function get_actions() { + return $this->actions; + } + + /** + * Adds a descriptive label to the component. + * + * This can be used in two ways: + * + *
+     * $component->set_label($elementlabel, $elementid);
+     * // OR
+     * $label = new html_label();
+     * $label->for = $elementid;
+     * $label->text = $elementlabel;
+     * $component->set_label($label);
+     * 
+ * + * Use the second form when you need to add additional HTML attributes + * to the label and/or JS actions. + * + * @param mixed $text Either the text of the label or a html_label object + * @param text $for The value of the "for" attribute (the associated element's id) + * @return void + */ + public function set_label($text, $for=null) { + if ($text instanceof html_label) { + $this->label = $text; + } else if (!empty($text)) { + $this->label = new html_label(); + $this->label->for = $for; + if (empty($for) && !empty($this->id)) { + $this->label->for = $this->id; + } + $this->label->text = $text; + } + } +} + + +/** + * This class hold all the information required to describe a element. Default 0. + */ + public $tabindex = 0; + /** + * @var mixed Defaults to false, which means display the select as a dropdown menu. + * If true, display this select as a list box whose size is chosen automatically. + * If an integer, display as list box of that size. + */ + public $listbox = false; + /** + * @var integer if you are using $listbox === true to get an automatically + * sized list box, the size of the list box will be the number of options, + * or this number, whichever is smaller. + */ + public $maxautosize = 10; + /** + * @var boolean if true, allow multiple selection. Only used if $listbox is true, or if + * the select is to be output as checkboxes. + */ + public $multiple = false; + /** + * Another way to use nested menu is to prefix optgroup labels with -- and end the optgroup with -- + * Leave this setting to false if you are using the latter method. + * @var boolean $nested if true, uses $options' keys as option headings (optgroup) + */ + public $nested = false; + /** + * @var html_form $form An optional html_form component + */ + public $form; + /** + * @var help_icon $form An optional help_icon component + */ + public $helpicon; + /** + * @var boolean $rendertype How the select element should be rendered: menu or radio (checkbox is just radio + multiple) + */ + public $rendertype = 'menu'; + + /** + * @see moodle_html_component::prepare() + * @return void + */ + public function prepare() { + global $CFG; + + // name may contain [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading + if (empty($this->id)) { + $this->id = 'menu' . str_replace(array('[', ']'), '', $this->name); + } + + if (empty($this->classes)) { + $this->set_classes(array('menu' . str_replace(array('[', ']'), '', $this->name))); + } + + if (is_null($this->nothinglabel)) { + $this->nothinglabel = get_string('choosedots'); + } + + if (!empty($this->label) && !($this->label instanceof html_label)) { + $label = new html_label(); + $label->text = $this->label; + $label->for = $this->name; + $this->label = $label; + } + + $this->add_class('select'); + + $this->initialise_options(); + parent::prepare(); + } + + /** + * This is a shortcut for making a simple select menu. It lets you specify + * the options, name and selected option in one line of code. + * @param array $options used to initialise {@link $options}. + * @param string $name used to initialise {@link $name}. + * @param string $selected used to initialise {@link $selected}. + * @return moodle_select A moodle_select object with the three common fields initialised. + */ + public static function make($options, $name, $selected = '') { + $menu = new moodle_select(); + $menu->options = $options; + $menu->name = $name; + $menu->selectedvalue = $selected; + return $menu; + } + + /** + * This is a shortcut for making a yes/no select menu. + * @param string $name used to initialise {@link $name}. + * @param string $selected used to initialise {@link $selected}. + * @return moodle_select A menu initialised with yes/no options. + */ + public static function make_yes_no($name, $selected) { + return self::make(array(0 => get_string('no'), 1 => get_string('yes')), $name, $selected); + } + + /** + * 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 + * @return moodle_select A menu initialised with hour options. + */ + public static function make_time_selector($type, $name, $currenttime=0, $step=5) { + + if (!$currenttime) { + $currenttime = time(); + } + $currentdate = usergetdate($currenttime); + $userdatetype = $type; + + switch ($type) { + case 'years': + for ($i=1970; $i<=2020; $i++) { + $timeunits[$i] = $i; + } + $userdatetype = 'year'; + break; + case 'months': + for ($i=1; $i<=12; $i++) { + $timeunits[$i] = userdate(gmmktime(12,0,0,$i,15,2000), "%B"); + } + $userdatetype = 'month'; + break; + case 'days': + for ($i=1; $i<=31; $i++) { + $timeunits[$i] = $i; + } + $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 moodle_select::make_time_selector()."); + } + + $timerselector = self::make($timeunits, $name, $currentdate[$userdatetype]); + $timerselector->label = new html_label(); + $timerselector->label->text = get_string(substr($type, -1), 'form'); + $timerselector->label->for = "menu$timerselector->name"; + $timerselector->label->add_class('accesshide'); + $timerselector->nothinglabel = ''; + + return $timerselector; + } + + /** + * Given an associative array of type => fieldname and an optional timestamp, + * returns an array of moodle_select components representing date/time selectors. + * @param array $selectors Arrays of type => fieldname. Selectors will be returned in the order of the types given + * @param int $currenttime A UNIX timestamp + * @param int $step minute spacing + * @return array Instantiated date/time selectors + */ + public function make_time_selectors($selectors, $currenttime=0, $step=5) { + $selects = array(); + foreach ($selectors as $type => $name) { + $selects[] = moodle_select::make_time_selector($type, $name, $currenttime, $step); + } + return $selects; + } + + /** + * This is a shortcut for making a select popup form. + * @param mixed $baseurl The target URL, string or moodle_url + * @param string $name The variable which this select's options are changing in the URL + * @param array $options A list of value-label pairs for the popup list + * @param string $formid id for the control. Must be unique on the page. Used in the HTML. + * @param string $selected The option that is initially selected + * @return moodle_select A menu initialised as a popup form. + */ + public function make_popup_form($baseurl, $name, $options, $formid, $selected=null) { + global $CFG; + + $selectedurl = null; + + if (!($baseurl instanceof moodle_url)) { + $baseurl = new moodle_url($baseurl); + } + + if (!empty($selected)) { + $selectedurl = $baseurl->out(false, array($name => $selected), false); + } + + if (!($baseurl instanceof moodle_url)) { + $baseurl = new moodle_url($baseurl); + } + + // Replace real value by formatted URLs + foreach ($options as $value => $label) { + $options[$baseurl->out(false, array($name => $value), false)] = $label; + unset($options[$value]); + } + + $select = self::make($options, 'jump', $selectedurl); + + $select->form = new html_form(); + $select->form->id = $formid; + $select->form->method = 'get'; + $select->form->add_class('popupform'); + $select->form->url = new moodle_url($CFG->wwwroot . '/course/jumpto.php', array('sesskey' => sesskey())); + $select->form->button->text = get_string('go'); + + $select->id = $formid . '_jump'; + + $select->add_action('change', 'submit_form_by_id', array('id' => $formid, 'selectid' => $select->id)); + + return $select; + } + + /** + * Override the URLs of the default popup_form, which only supports one base URL + * @param array $options value=>label pairs representing select options + * @return void + */ + public function override_option_values($options) { + global $PAGE; + + $this->initialise_options(); + + reset($options); + + foreach ($this->options as $optkey => $optgroup) { + if ($optgroup instanceof html_select_optgroup) { + foreach ($optgroup->options as $key => $option) { + next($options); + $this->options[$optkey]->options[$key]->value = key($options); + + $optionurl = new moodle_url(key($options)); + + if ($optionurl->compare($PAGE->url, URL_MATCH_PARAMS)) { + $this->options[$optkey]->options[$key]->selected = 'selected'; + } + } + next($options); + } else if ($optgroup instanceof html_select_option) { + next($options); + $this->options[$optkey]->value = key($options); + $optionurl = new moodle_url(key($options)); + + if ($optionurl->compare($PAGE->url, URL_MATCH_PARAMS)) { + $this->options[$optkey]->selected = 'selected'; + } + } + } + } + + /** + * Adds a help icon next to the select menu. + * + * This can be used in two ways: + * + *
+     * $select->set_help_icon($page, $text, $linktext);
+     * // OR
+     * $helpicon = new help_icon();
+     * $helpicon->page = $page;
+     * $helpicon->text = $text;
+     * $helpicon->linktext = $linktext;
+     * $select->set_help_icon($helpicon);
+     * 
+ * + * Use the second form when you need to add additional HTML attributes + * to the label and/or JS actions. + * + * @param mixed $page Either the keyword that defines a help page or a help_icon object + * @param text $text The text of the help icon + * @param boolean $linktext Whether or not to show text next to the icon + * @return void + */ + public function set_help_icon($page, $text, $linktext=false) { + if ($page instanceof help_icon) { + $this->helpicon = $page; + } else if (!empty($page)) { + $this->helpicon = new help_icon(); + $this->helpicon->page = $page; + $this->helpicon->text = $text; + $this->helpicon->linktext = $linktext; + } + } + + /** + * Parses the $options array and instantiates html_select_option objects in + * the place of the original value => label pairs. This is useful for when you + * need to setup extra html attributes and actions on individual options before + * the component is sent to the renderer + * @return void; + */ + public function initialise_options() { + // If options are already instantiated objects, stop here + $firstoption = reset($this->options); + if ($firstoption instanceof html_select_option || $firstoption instanceof html_select_optgroup) { + return; + } + + if ($this->rendertype == 'radio' && $this->multiple) { + $this->rendertype = 'checkbox'; + } + + // If nested is on, or if radio/checkbox rendertype is set, remove the default Choose option + if ($this->nested || $this->rendertype == 'radio' || $this->rendertype == 'checkbox') { + $this->nothinglabel = ''; + } + + $options = $this->options; + + $this->options = array(); + + if ($this->nested && $this->rendertype != 'menu') { + throw new coding_exception('moodle_select cannot render nested options as radio buttons or checkboxes.'); + } else if ($this->nested) { + foreach ($options as $section => $values) { + $optgroup = new html_select_optgroup(); + $optgroup->text = $section; + + foreach ($values as $value => $display) { + $option = new html_select_option(); + $option->value = s($value); + $option->text = $display; + if ($display === '') { + $option->text = $value; + } + + if ((string) $value == (string) $this->selectedvalue || + (is_array($this->selectedvalue) && in_array($value, $this->selectedvalue))) { + $option->selected = 'selected'; + } + + $optgroup->options[] = $option; + } + + $this->options[] = $optgroup; + } + } else { + $inoptgroup = false; + $optgroup = false; + + foreach ($options as $value => $display) { + if ($display == '--') { /// we are ending previous optgroup + // $this->options[] = $optgroup; + $inoptgroup = false; + continue; + } else if (substr($display,0,2) == '--') { /// we are starting a new optgroup + if (!empty($optgroup->options)) { + $this->options[] = $optgroup; + } + + $optgroup = new html_select_optgroup(); + $optgroup->text = substr($display,2); // stripping the -- + + $inoptgroup = true; /// everything following will be in an optgroup + continue; + + } else { + // Add $nothing option if there are not optgroups + if ($this->nothinglabel && empty($this->options[0]) && !$inoptgroup) { + $nothingoption = new html_select_option(); + $nothingoption->value = 0; + if (!empty($this->nothingvalue)) { + $nothingoption->value = $this->nothingvalue; + } + $nothingoption->text = $this->nothinglabel; + $this->options = array($nothingoption) + $this->options; + } + + $option = new html_select_option(); + $option->text = $display; + + if ($display === '') { + $option->text = $value; + } + + if ((string) $value == (string) $this->selectedvalue || + (is_array($this->selectedvalue) && in_array($value, $this->selectedvalue))) { + $option->selected = 'selected'; + } + + $option->value = s($value); + + if ($inoptgroup) { + $optgroup->options[] = $option; + } else { + $this->options[] = $option; + } + } + } + + if ($optgroup) { + $this->options[] = $optgroup; + } + } + } +} + +/** + * This class represents a label element + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_label extends moodle_html_component { + /** + * @var string $text The text to display in the label + */ + public $text; + /** + * @var string $for The name of the form field this label is associated with + */ + public $for; + + /** + * @see moodle_html_component::prepare() + * @return void + */ + public function prepare() { + if (empty($this->text)) { + throw new coding_exception('html_label must have a $text value.'); + } + parent::prepare(); + } +} + +/** + * This class represents a select option element + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_select_option extends moodle_html_component { + /** + * @var string $value The value of this option (will be sent with form) + */ + public $value; + /** + * @var string $text The display value of the option + */ + public $text; + /** + * @var boolean $selected Whether or not this option is selected + */ + public $selected = false; + /** + * @var mixed $label The label for that component. String or html_label object + */ + public $label; + + public function __construct() { + $this->label = new html_label(); + } + + /** + * @see moodle_html_component::prepare() + * @return void + */ + public function prepare() { + if (empty($this->text)) { + throw new coding_exception('html_select_option requires a $text value.'); + } + + if (empty($this->label->text)) { + $this->set_label($this->text); + } else if (!($this->label instanceof html_label)) { + $this->set_label($this->label); + } + if (empty($this->id)) { + $this->generate_id(); + } + + parent::prepare(); + } + + /** + * Shortcut for making a checkbox-ready option + * @param string $value The value of the checkbox + * @param boolean $checked + * @param string $label + * @param string $alt + * @return html_select_option A component ready for $OUTPUT->checkbox() + */ + public function make_checkbox($value, $checked, $label='', $alt='') { + $checkbox = new html_select_option(); + $checkbox->value = $value; + $checkbox->selected = $checked; + $checkbox->text = $label; + $checkbox->label->text = $label; + $checkbox->alt = $alt; + return $checkbox; + } +} + +/** + * This class represents a select optgroup element + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_select_optgroup extends moodle_html_component { + /** + * @var string $text The display value of the optgroup + */ + public $text; + /** + * @var array $options An array of html_select_option objects + */ + public $options = array(); + + public function prepare() { + if (empty($this->text)) { + throw new coding_exception('html_select_optgroup requires a $text value.'); + } + if (empty($this->options)) { + throw new coding_exception('html_select_optgroup requires at least one html_select_option object'); + } + parent::prepare(); + } +} + +/** + * This class represents an input field + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_field extends moodle_html_component { + /** + * @var string $name The name attribute of the field + */ + public $name; + /** + * @var string $value The value attribute of the field + */ + public $value; + /** + * @var string $type The type attribute of the field (text, submit, checkbox etc) + */ + public $type; + /** + * @var string $maxlength The maxlength attribute of the field (only applies to text type) + */ + public $maxlength; + /** + * @var mixed $label The label for that component. String or html_label object + */ + public $label; + + public function __construct() { + $this->label = new html_label(); + } + + /** + * @see moodle_html_component::prepare() + * @return void + */ + public function prepare() { + if (empty($this->style)) { + $this->style = 'width: 4em;'; + } + if (empty($this->id)) { + $this->generate_id(); + } + parent::prepare(); + } + + /** + * Shortcut for creating a text input component. + * @param string $name The name of the text field + * @param string $value The value of the text field + * @param string $alt The info to be inserted in the alt tag + * @param int $maxlength Sets the maxlength attribute of the field. Not set by default + * @return html_field The field component + */ + public static function make_text($name='unnamed', $value, $alt, $maxlength=0) { + $field = new html_field(); + if (empty($alt)) { + $alt = get_string('textfield'); + } + $field->type = 'text'; + $field->name = $name; + $field->value = $value; + $field->alt = $alt; + $field->maxlength = $maxlength; + return $field; + } +} + +/** + * This class represents how a block appears on a page. + * + * During output, each block instance is asked to return a block_contents object, + * those are then passed to the $OUTPUT->block function for display. + * + * {@link $contents} should probably be generated using a moodle_block_..._renderer. + * + * Other block-like things that need to appear on the page, for example the + * add new block UI, are also represented as block_contents objects. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class block_contents extends moodle_html_component { + /** @var int used to set $skipid. */ + protected static $idcounter = 1; + + const NOT_HIDEABLE = 0; + const VISIBLE = 1; + const HIDDEN = 2; + + /** + * @param integer $skipid All the blocks (or things that look like blocks) + * printed on a page are given a unique number that can be used to construct + * id="" attributes. This is set automatically be the {@link prepare()} method. + * Do not try to set it manually. + */ + public $skipid; + + /** + * @var integer If this is the contents of a real block, this should be set to + * the block_instance.id. Otherwise this should be set to 0. + */ + public $blockinstanceid = 0; + + /** + * @var integer if this is a real block instance, and there is a corresponding + * block_position.id for the block on this page, this should be set to that id. + * Otherwise it should be 0. + */ + public $blockpositionid = 0; + + /** + * @param array $attributes an array of attribute => value pairs that are put on the + * outer div of this block. {@link $id} and {@link $classes} attributes should be set separately. + */ + public $attributes = array(); + + /** + * @param string $title The title of this block. If this came from user input, + * it should already have had format_string() processing done on it. This will + * be output inside

tags. Please do not cause invalid XHTML. + */ + public $title = ''; + + /** + * @param string $content HTML for the content + */ + public $content = ''; + + /** + * @param array $list an alternative to $content, it you want a list of things with optional icons. + */ + public $footer = ''; + + /** + * Any small print that should appear under the block to explain to the + * teacher about the block, for example 'This is a sticky block that was + * added in the system context.' + * @var string + */ + public $annotation = ''; + + /** + * @var integer one of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether + * the user can toggle whether this block is visible. + */ + public $collapsible = self::NOT_HIDEABLE; + + /** + * A (possibly empty) array of editing controls. Each element of this array + * should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption). + * $icon is the icon name. Fed to $OUTPUT->old_icon_url. + * @var array + */ + public $controls = array(); + + /** + * @see moodle_html_component::prepare() + * @return void + */ + public function prepare() { + $this->skipid = self::$idcounter; + self::$idcounter += 1; + $this->add_class('sideblock'); + if (empty($this->blockinstanceid) || !strip_tags($this->title)) { + $this->collapsible = self::NOT_HIDEABLE; + } + if ($this->collapsible == self::HIDDEN) { + $this->add_class('hidden'); + } + if (!empty($this->controls)) { + $this->add_class('block_with_controls'); + } + parent::prepare(); + } +} + + +/** + * This class represents a target for where a block can go when it is being moved. + * + * This needs to be rendered as a form with the given hidden from fields, and + * clicking anywhere in the form should submit it. The form action should be + * $PAGE->url. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class block_move_target extends moodle_html_component { + /** + * List of hidden form fields. + * @var array + */ + public $url = array(); + /** + * List of hidden form fields. + * @var array + */ + public $text = ''; +} + + +/** + * Holds all the information required to render a by + * {@see moodle_core_renderer::table()} or by an overridden version of that + * method in a subclass. + * + * Example of usage: + * $t = new html_table(); + * ... // set various properties of the object $t as described below + * echo $OUTPUT->table($t); + * + * @copyright 2009 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_table extends moodle_html_component { + /** + * @var array of headings. The n-th array item is used as a heading of the n-th column. + * + * Example of usage: + * $t->head = array('Student', 'Grade'); + */ + public $head; + /** + * @var array can be used to make a heading span multiple columns + * + * Example of usage: + * $t->headspan = array(2,1); + * + * In this example, {@see html_table:$data} is supposed to have three columns. For the first two columns, + * the same heading is used. Therefore, {@see html_table::$head} should consist of two items. + */ + public $headspan; + /** + * @var array of column alignments. The value is used as CSS 'text-align' property. Therefore, possible + * values are 'left', 'right', 'center' and 'justify'. Specify 'right' or 'left' from the perspective + * of a left-to-right (LTR) language. For RTL, the values are flipped automatically. + * + * Examples of usage: + * $t->align = array(null, 'right'); + * or + * $t->align[1] = 'right'; + * + */ + public $align; + /** + * @var array of column sizes. The value is used as CSS 'size' property. + * + * Examples of usage: + * $t->size = array('50%', '50%'); + * or + * $t->size[1] = '120px'; + */ + public $size; + /** + * @var array of wrapping information. The only possible value is 'nowrap' that sets the + * CSS property 'white-space' to the value 'nowrap' in the given column. + * + * Example of usage: + * $t->wrap = array(null, 'nowrap'); + */ + public $wrap; + /** + * @var array of arrays or html_table_row objects containing the data. Alternatively, if you have + * $head specified, the string 'hr' (for horizontal ruler) can be used + * instead of an array of cells data resulting in a divider rendered. + * + * Example of usage with array of arrays: + * $row1 = array('Harry Potter', '76 %'); + * $row2 = array('Hermione Granger', '100 %'); + * $t->data = array($row1, $row2); + * + * Example with array of html_table_row objects: (used for more fine-grained control) + * $cell1 = new html_table_cell(); + * $cell1->text = 'Harry Potter'; + * $cell1->colspan = 2; + * $row1 = new html_table_row(); + * $row1->cells[] = $cell1; + * $cell2 = new html_table_cell(); + * $cell2->text = 'Hermione Granger'; + * $cell3 = new html_table_cell(); + * $cell3->text = '100 %'; + * $row2 = new html_table_row(); + * $row2->cells = array($cell2, $cell3); + * $t->data = array($row1, $row2); + */ + public $data; + /** + * @var string width of the table, percentage of the page preferred. Defaults to 80% of the page width. + * @deprecated since Moodle 2.0. Styling should be in the CSS. + */ + public $width = null; + /** + * @var string alignment the whole table. Can be 'right', 'left' or 'center' (default). + * @deprecated since Moodle 2.0. Styling should be in the CSS. + */ + public $tablealign = null; + /** + * @var int padding on each cell, in pixels + * @deprecated since Moodle 2.0. Styling should be in the CSS. + */ + public $cellpadding = null; + /** + * @var int spacing between cells, in pixels + * @deprecated since Moodle 2.0. Styling should be in the CSS. + */ + public $cellspacing = null; + /** + * @var array classes to add to particular rows, space-separated string. + * Classes 'r0' or 'r1' are added automatically for every odd or even row, + * respectively. Class 'lastrow' is added automatically for the last row + * in the table. + * + * Example of usage: + * $t->rowclasses[9] = 'tenth' + */ + public $rowclasses; + /** + * @var array classes to add to every cell in a particular column, + * space-separated string. Class 'cell' is added automatically by the renderer. + * Classes 'c0' or 'c1' are added automatically for every odd or even column, + * respectively. Class 'lastcol' is added automatically for all last cells + * in a row. + * + * Example of usage: + * $t->colclasses = array(null, 'grade'); + */ + public $colclasses; + /** + * @var string description of the contents for screen readers. + */ + public $summary; + /** + * @var bool true causes the contents of the heading cells to be rotated 90 degrees. + */ + public $rotateheaders = false; + + /** + * @see moodle_html_component::prepare() + * @return void + */ + public function prepare() { + if (!empty($this->align)) { + foreach ($this->align as $key => $aa) { + if ($aa) { + $this->align[$key] = 'text-align:'. fix_align_rtl($aa) .';'; // Fix for RTL languages + } else { + $this->align[$key] = ''; + } + } + } + if (!empty($this->size)) { + foreach ($this->size as $key => $ss) { + if ($ss) { + $this->size[$key] = 'width:'. $ss .';'; + } else { + $this->size[$key] = ''; + } + } + } + if (!empty($this->wrap)) { + foreach ($this->wrap as $key => $ww) { + if ($ww) { + $this->wrap[$key] = 'white-space:nowrap;'; + } else { + $this->wrap[$key] = ''; + } + } + } + if (!empty($this->head)) { + foreach ($this->head as $key => $val) { + if (!isset($this->align[$key])) { + $this->align[$key] = ''; + } + if (!isset($this->size[$key])) { + $this->size[$key] = ''; + } + if (!isset($this->wrap[$key])) { + $this->wrap[$key] = ''; + } + + } + } + if (empty($this->classes)) { // must be done before align + $this->set_classes(array('generaltable')); + } + if (!empty($this->tablealign)) { + $this->add_class('boxalign' . $this->tablealign); + } + if (!empty($this->rotateheaders)) { + $this->add_class('rotateheaders'); + } else { + $this->rotateheaders = false; // Makes life easier later. + } + parent::prepare(); + } + /** + * @param string $name The name of the variable to set + * @param mixed $value The value to assign to the variable + * @return void + */ + public function __set($name, $value) { + if ($name == 'rowclass') { + debugging('rowclass[] has been deprecated for html_table ' . + 'and should be replaced with rowclasses[]. please fix the code.'); + $this->rowclasses = $value; + } else { + parent::__set($name, $value); + } + } +} + +/** + * Component representing a table row. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_table_row extends moodle_html_component { + /** + * @var array $cells Array of html_table_cell objects + */ + public $cells = array(); + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + parent::prepare(); + } +} + +/** + * Component representing a table cell. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_table_cell extends moodle_html_component { + /** + * @var string $text The contents of the cell + */ + public $text; + /** + * @var string $abbr Abbreviated version of the contents of the cell + */ + public $abbr = ''; + /** + * @var int $colspan Number of columns this cell should span + */ + public $colspan = ''; + /** + * @var int $rowspan Number of rows this cell should span + */ + public $rowspan = ''; + /** + * @var string $scope Defines a way to associate header cells and data cells in a table + */ + public $scope = ''; + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + parent::prepare(); + } +} + +/** + * Component representing a XHTML link. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_link extends moodle_html_component { + /** + * URL can be simple text or a moodle_url object + * @var mixed $url + */ + public $url; + + /** + * @var string $text The text that will appear between the link tags + */ + public $text; + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + // We can't accept an empty text value + if (empty($this->text)) { + throw new coding_exception('A html_link must have a descriptive text value!'); + } + + parent::prepare(); + } + + /** + * Shortcut for creating a link component. + * @param mixed $url String or moodle_url + * @param string $text The text of the link + * @return html_link The link component + */ + public function make($url, $text) { + $link = new html_link(); + $link->url = $url; + $link->text = $text; + return $link; + } +} + +/** + * Component representing a help icon. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class help_icon extends moodle_html_component { + /** + * @var html_link $link A html_link object that will hold the URL info + */ + public $link; + /** + * @var string $text A descriptive text + */ + public $text; + /** + * @var string $page The keyword that defines a help page + */ + public $page; + /** + * @var string $module Which module is the page defined in + */ + public $module = 'moodle'; + /** + * @var boolean $linktext Whether or not to show text next to the icon + */ + public $linktext = false; + /** + * @var mixed $image The help icon. Can be set to true (will use default help icon), + * false (will not use any icon), the URL to an image, or a full + * html_image object. + */ + public $image; + + /** + * Constructor: sets up the other components in case they are needed + * @return void + */ + public function __construct() { + $this->link = new html_link(); + $this->image = new html_image(); + } + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + global $COURSE, $OUTPUT; + + if (empty($this->page)) { + throw new coding_exception('A help_icon object requires a $page parameter'); + } + + if (empty($this->text)) { + throw new coding_exception('A help_icon object requires a $text parameter'); + } + + $this->link->text = $this->text; + + // fix for MDL-7734 + $this->link->url = new moodle_url('/help.php', array('module' => $this->module, 'file' => $this->page .'.html')); + + // fix for MDL-7734 + if (!empty($COURSE->lang)) { + $this->link->url->param('forcelang', $COURSE->lang); + } + + // Catch references to the old text.html and emoticons.html help files that + // were renamed in MDL-13233. + if (in_array($this->page, array('text', 'emoticons', 'richtext'))) { + $oldname = $this->page; + $this->page .= '2'; + debugging("You are referring to the old help file '$oldname'. " . + "This was renamed to '$this->page' because of MDL-13233. " . + "Please update your code.", DEBUG_DEVELOPER); + } + + if ($this->module == '') { + $this->module = 'moodle'; + } + + // Warn users about new window for Accessibility + $this->title = get_string('helpprefix2', '', trim($this->text, ". \t")) .' ('.get_string('newwindow').')'; + + // Prepare image and linktext + if ($this->image && !($this->image instanceof html_image)) { + $image = fullclone($this->image); + $this->image = new html_image(); + + if ($image instanceof moodle_url) { + $this->image->src = $image->out(); + } else if ($image === true) { + $this->image->src = $OUTPUT->old_icon_url('help'); + } else if (is_string($image)) { + $this->image->src = $image; + } + $this->image->alt = $this->text; + + if ($this->linktext) { + $this->image->alt = get_string('helpwiththis'); + } else { + $this->image->alt = $this->title; + } + $this->image->add_class('iconhelp'); + } else if (empty($this->image->src)) { + if (!($this->image instanceof html_image)) { + $this->image = new html_image(); + } + $this->image->src = $OUTPUT->old_icon_url('help'); + } + + parent::prepare(); + } + + public static function make_scale_menu($courseid, $scale) { + $helpbutton = new help_icon(); + $strscales = get_string('scales'); + $helpbutton->image->alt = $scale->name; + $helpbutton->link->url = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scale->id)); + $popupaction = new popup_action('click', $helpbutton->url, 'ratingscale', $popupparams); + $popupaction->width = 500; + $popupaction->height = 400; + $helpbutton->link->add_action($popupaction); + $helpbutton->link->title = $scale->name; + return $helpbutton; + } +} + + +/** + * Component representing a XHTML button (input of type 'button'). + * The renderer will either output it as a button with an onclick event, + * or as a form with hidden inputs. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_button extends moodle_html_component { + /** + * @var string $text + */ + public $text; + + /** + * @var boolean $disabled Whether or not this button is disabled + */ + public $disabled = false; + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + $this->add_class('singlebutton'); + + if (empty($this->text)) { + throw new coding_exception('A html_button must have a text value!'); + } + + if ($this->disabled) { + $this->disabled = 'disabled'; + } + + parent::prepare(); + } +} + +/** + * Component representing an icon linking to a Moodle page. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class action_icon extends moodle_html_component { + /** + * @var string $linktext Optional text to display next to the icon + */ + public $linktext; + /** + * @var html_image $image The icon + */ + public $image; + /** + * @var html_link $link The link + */ + public $link; + + /** + * Constructor: sets up the other components in case they are needed + * @return void + */ + public function __construct() { + $this->image = new html_image(); + $this->link = new html_link(); + } + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + $this->image->add_class('action-icon'); + + parent::prepare(); + + if (empty($this->image->src)) { + throw new coding_exception('action_icon->image->src must not be empty'); + } + + if (empty($this->image->alt) && !empty($this->linktext)) { + $this->image->alt = $this->linktext; + } else if (empty($this->image->alt)) { + debugging('action_icon->image->alt should not be empty.', DEBUG_DEVELOPER); + } + } +} + +/** + * Component representing an image. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_image extends moodle_html_component { + /** + * @var string $alt A descriptive text + */ + public $alt = HTML_ATTR_EMPTY; + /** + * @var string $src The path to the image being used + */ + public $src; + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + $this->add_class('image'); + parent::prepare(); + } +} + +/** + * Component representing a user picture. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class user_picture extends moodle_html_component { + /** + * @var mixed $user A userid or a user object with at least fields id, picture, imagealrt, firstname and lastname set. + */ + public $user; + /** + * @var int $courseid The course id. Used when constructing the link to the user's profile. + */ + public $courseid; + /** + * @var html_image $image A custom image used as the user picture. + */ + public $image; + /** + * @var mixed $url False: picture not enclosed in a link. True: default link. moodle_url: custom link. + */ + public $url; + /** + * @var int $size Size in pixels. Special values are (true/1 = 100px) and (false/0 = 35px) for backward compatibility + */ + public $size; + /** + * @var boolean $alttext add non-blank alt-text to the image. (Default true, set to false for purely + */ + public $alttext = true; + /** + * @var boolean $popup Whether or not to open the link in a popup window + */ + public $popup = false; + + /** + * Constructor: sets up the other components in case they are needed + * @return void + */ + public function __construct() { + $this->image = new html_image(); + } + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + global $CFG, $DB, $OUTPUT; + + if (empty($this->user)) { + throw new coding_exception('A user_picture object must have a $user object before being rendered.'); + } + + if (empty($this->courseid)) { + throw new coding_exception('A user_picture object must have a courseid value before being rendered.'); + } + + if (!($this->image instanceof html_image)) { + debugging('user_picture::image must be an instance of html_image', DEBUG_DEVELOPER); + } + + $needrec = false; + // only touch the DB if we are missing data... + if (is_object($this->user)) { + // Note - both picture and imagealt _can_ be empty + // what we are trying to see here is if they have been fetched + // from the DB. We should use isset() _except_ that some installs + // have those fields as nullable, and isset() will return false + // on null. The only safe thing is to ask array_key_exists() + // which works on objects. property_exists() isn't quite + // what we want here... + if (! (array_key_exists('picture', $this->user) + && ($this->alttext && array_key_exists('imagealt', $this->user) + || (isset($this->user->firstname) && isset($this->user->lastname)))) ) { + $needrec = true; + $this->user = $this->user->id; + } + } else { + if ($this->alttext) { + // we need firstname, lastname, imagealt, can't escape... + $needrec = true; + } else { + $userobj = new StdClass; // fake it to save DB traffic + $userobj->id = $this->user; + $userobj->picture = $this->image->src; + $this->user = clone($userobj); + unset($userobj); + } + } + if ($needrec) { + $this->user = $DB->get_record('user', array('id' => $this->user), 'id,firstname,lastname,imagealt'); + } + + if ($this->url === true) { + $this->url = new moodle_url('/user/view.php', array('id' => $this->user->id, 'course' => $this->courseid)); + } + + if (!empty($this->url) && $this->popup) { + $this->add_action(new popup_action('click', $this->url)); + } + + if (empty($this->size)) { + $file = 'f2'; + $this->size = 35; + } else if ($this->size === true or $this->size == 1) { + $file = 'f1'; + $this->size = 100; + } else if ($this->size >= 50) { + $file = 'f1'; + } else { + $file = 'f2'; + } + + if (!empty($this->size)) { + $this->image->width = $this->size; + $this->image->height = $this->size; + } + + $this->add_class('userpicture'); + + if (empty($this->image->src) && !empty($this->user->picture)) { + $this->image->src = $this->user->picture; + } + + if (!empty($this->image->src)) { + require_once($CFG->libdir.'/filelib.php'); + $this->image->src = new moodle_url(get_file_url($this->user->id.'/'.$file.'.jpg', null, 'user')); + } else { // Print default user pictures (use theme version if available) + $this->add_class('defaultuserpic'); + $this->image->src = $OUTPUT->old_icon_url('u/' . $file); + } + + if ($this->alttext) { + if (!empty($this->user->imagealt)) { + $this->image->alt = $this->user->imagealt; + } else { + $this->image->alt = get_string('pictureof','',fullname($this->user)); + } + } + + parent::prepare(); + } +} + +/** + * Component representing a textarea. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_textarea extends moodle_html_component { + /** + * @param string $name Name to use for the textarea element. + */ + public $name; + /** + * @param string $value Initial content to display in the textarea. + */ + public $value; + /** + * @param int $rows Number of rows to display (minimum of 10 when $height is non-null) + */ + public $rows; + /** + * @param int $cols Number of columns to display (minimum of 65 when $width is non-null) + */ + public $cols; + /** + * @param bool $usehtmleditor Enables the use of the htmleditor for this field. + */ + public $usehtmleditor; + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + $this->add_class('form-textarea'); + + if (empty($this->id)) { + $this->id = "edit-$this->name"; + } + + if ($this->usehtmleditor) { + editors_head_setup(); + $editor = get_preferred_texteditor(FORMAT_HTML); + $editor->use_editor($this->id, array('legacy'=>true)); + $this->value = htmlspecialchars($value); + } + + parent::prepare(); + } +} + +/** + * Component representing a simple form wrapper. Its purpose is mainly to enclose + * a submit input with the appropriate action and hidden inputs. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_form extends moodle_html_component { + /** + * @var string $method post or get + */ + public $method = 'post'; + /** + * If a string is given, it will be converted to a moodle_url during prepare() + * @var mixed $url A moodle_url including params or a string + */ + public $url; + /** + * @var array $params Optional array of parameters. Ignored if $url instanceof moodle_url + */ + public $params = array(); + /** + * @var boolean $showbutton If true, the submit button will always be shown even if JavaScript is available + */ + public $showbutton = false; + /** + * @var string $targetwindow The name of the target page to open the linked page in. + */ + public $targetwindow = 'self'; + /** + * @var html_button $button A submit button + */ + public $button; + + /** + * Constructor: sets up the other components in case they are needed + * @return void + */ + public function __construct() { + static $yes; + $this->button = new html_button(); + if (!isset($yes)) { + $yes = get_string('yes'); + $this->button->text = $yes; + } + } + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + + if (empty($this->url)) { + throw new coding_exception('A html_form must have a $url value (string or moodle_url).'); + } + + if (!($this->url instanceof moodle_url)) { + $this->url = new moodle_url($this->url, $this->params); + } + + if ($this->method == 'post') { + $this->url->param('sesskey', sesskey()); + } + + parent::prepare(); + } +} + +/** + * Component representing a paging bar. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class moodle_paging_bar extends moodle_html_component { + /** + * @var int $maxdisplay The maximum number of pagelinks to display + */ + public $maxdisplay = 18; + /** + * @var int $totalcount post or get + */ + public $totalcount; + /** + * @var int $page The page you are currently viewing + */ + public $page = 0; + /** + * @var int $perpage The number of entries that should be shown per page + */ + public $perpage; + /** + * @var string $baseurl If this is a string then it is the url which will be appended with $pagevar, an equals sign and the page number. + * If this is a moodle_url object then the pagevar param will be replaced by the page no, for each page. + */ + public $baseurl; + /** + * @var string $pagevar This is the variable name that you use for the page number in your code (ie. 'tablepage', 'blogpage', etc) + */ + public $pagevar = 'page'; + /** + * @var bool $nocurr do not display the current page as a link + */ + public $nocurr; + /** + * @var html_link $previouslink A HTML link representing the "previous" page + */ + public $previouslink = null; + /** + * @var html_link $nextlink A HTML link representing the "next" page + */ + public $nextlink = null; + /** + * @var html_link $firstlink A HTML link representing the first page + */ + public $firstlink = null; + /** + * @var html_link $lastlink A HTML link representing the last page + */ + public $lastlink = null; + /** + * @var array $pagelinks An array of html_links. One of them is just a string: the current page + */ + public $pagelinks = array(); + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + if (empty($this->totalcount)) { + throw new coding_exception('moodle_paging_bar requires a totalcount value.'); + } + if (!isset($this->page) || is_null($this->page)) { + throw new coding_exception('moodle_paging_bar requires a page value.'); + } + if (empty($this->perpage)) { + throw new coding_exception('moodle_paging_bar requires a perpage value.'); + } + if (empty($this->baseurl)) { + throw new coding_exception('moodle_paging_bar requires a baseurl value.'); + } + if (!($this->baseurl instanceof moodle_url)) { + $this->baseurl = new moodle_url($this->baseurl); + } + + if ($this->totalcount > $this->perpage) { + $pagenum = $this->page - 1; + + if ($this->page > 0) { + $this->previouslink = new html_link(); + $this->previouslink->add_class('previous'); + $this->previouslink->url = clone($this->baseurl); + $this->previouslink->url->param($this->pagevar, $pagenum); + $this->previouslink->text = get_string('previous'); + } + + if ($this->perpage > 0) { + $lastpage = ceil($this->totalcount / $this->perpage); + } else { + $lastpage = 1; + } + + if ($this->page > 15) { + $startpage = $this->page - 10; + + $this->firstlink = new html_link(); + $this->firstlink->url = clone($this->baseurl); + $this->firstlink->url->param($this->pagevar, 0); + $this->firstlink->text = 1; + $this->firstlink->add_class('first'); + } else { + $startpage = 0; + } + + $currpage = $startpage; + $displaycount = $displaypage = 0; + + while ($displaycount < $this->maxdisplay and $currpage < $lastpage) { + $displaypage = $currpage + 1; + + if ($this->page == $currpage && empty($this->nocurr)) { + $this->pagelinks[] = $displaypage; + } else { + $pagelink = new html_link(); + $pagelink->url = clone($this->baseurl); + $pagelink->url->param($this->pagevar, $currpage); + $pagelink->text = $displaypage; + $this->pagelinks[] = $pagelink; + } + + $displaycount++; + $currpage++; + } + + if ($currpage < $lastpage) { + $lastpageactual = $lastpage - 1; + $this->lastlink = new html_link(); + $this->lastlink->url = clone($this->baseurl); + $this->lastlink->url->param($this->pagevar, $lastpageactual); + $this->lastlink->text = $lastpage; + $this->lastlink->add_class('last'); + } + + $pagenum = $this->page + 1; + + if ($pagenum != $displaypage) { + $this->nextlink = new html_link(); + $this->nextlink->url = clone($this->baseurl); + $this->nextlink->url->param($this->pagevar, $pagenum); + $this->nextlink->text = get_string('next'); + $this->nextlink->add_class('next'); + } + } + } + + /** + * Shortcut for initialising a moodle_paging_bar with only the required params. + * + * @param int $totalcount Thetotal number of entries available to be paged through + * @param int $page The page you are currently viewing + * @param int $perpage The number of entries that should be shown per page + * @param mixed $baseurl If this is a string then it is the url which will be appended with $pagevar, an equals sign and the page number. + * If this is a moodle_url object then the pagevar param will be replaced by the page no, for each page. + */ + public function make($totalcount, $page, $perpage, $baseurl) { + $pagingbar = new moodle_paging_bar(); + $pagingbar->totalcount = $totalcount; + $pagingbar->page = $page; + $pagingbar->perpage = $perpage; + $pagingbar->baseurl = $baseurl; + return $pagingbar; + } +} + +/** + * Component representing a list. + * + * The advantage of using this object instead of a flat array is that you can load it + * with metadata (CSS classes, event handlers etc.) which can be used by the renderers. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_list extends moodle_html_component { + + /** + * @var array $items An array of html_list_item or html_list objects + */ + public $items = array(); + + /** + * @var string $type The type of list (ordered|unordered), definition type not yet supported + */ + public $type = 'unordered'; + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + parent::prepare(); + } + + /** + * This function takes a nested array of data and maps it into this list's $items array + * as proper html_list_item and html_list objects, with appropriate metadata. + * + * @param array $tree A nested array (array keys are ignored); + * @param int $row Used in identifying the iteration level and in ul classes + * @return void + */ + public function load_data($tree, $level=0) { + + $this->add_class("list-$level"); + + foreach ($tree as $key => $element) { + if (is_array($element)) { + $newhtmllist = new html_list(); + $newhtmllist->load_data($element, $level + 1); + $this->items[] = $newhtmllist; + } else { + $listitem = new html_list_item(); + $listitem->value = $element; + $listitem->add_class("list-item-$level-$key"); + $this->items[] = $listitem; + } + } + } + + /** + * Adds a html_list_item or html_list to this list. + * If the param is a string, a html_list_item will be added. + * @param mixed $item String, html_list or html_list_item object + * @return void + */ + public function add_item($item) { + if ($item instanceof html_list_item || $item instanceof html_list) { + $this->items[] = $item; + } else { + $listitem = new html_list_item(); + $listitem->value = $item; + $this->items[] = $item; + } + } +} + +/** + * Component representing a list item. + * + * @copyright 2009 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class html_list_item extends moodle_html_component { + /** + * @var string $value The value of the list item + */ + public $value; + + /** + * @see lib/moodle_html_component#prepare() + * @return void + */ + public function prepare() { + parent::prepare(); + } +} diff --git a/lib/outputfactories.php b/lib/outputfactories.php new file mode 100644 index 00000000000..66540cfe97c --- /dev/null +++ b/lib/outputfactories.php @@ -0,0 +1,332 @@ +. + +/** + * Interface and classes for creating appropriate renderers for various + * parts of Moodle. + * + * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML + * for an overview. + * + * @package moodlecore + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * A renderer factory is just responsible for creating an appropriate renderer + * for any given part of Moodle. + * + * Which renderer factory to use is chose by the current theme, and an instance + * if created automatically when the theme is set up. + * + * A renderer factory must also have a constructor that takes a theme_config object. + * (See {@link renderer_factory_base::__construct} for an example.) + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +interface renderer_factory { + /** + * Return the renderer for a particular part of Moodle. + * + * The renderer interfaces are defined by classes called moodle_{plugin}_renderer + * where {plugin} is the name of the component. The renderers for core Moodle are + * defined in lib/renderer.php. For plugins, they will be defined in a file + * called renderer.php inside the plugin. + * + * Renderers will normally want to subclass the moodle_renderer_base class. + * (However, if you really know what you are doing, you don't have to do that.) + * + * There is no separate interface definition for renderers. The default + * moodle_{plugin}_renderer implementation also serves to define the API for + * other implementations of the interface, whether or not they subclass it. + * For example, {@link custom_corners_core_renderer} does subclass + * {@link moodle_core_renderer}. On the other hand, if you are using + * {@link template_renderer_factory} then you always get back an instance + * of the {@link template_renderer} class, whatever type of renderer you ask + * for. This uses the fact that PHP is a dynamic language. + * + * A particular plugin can define multiple renderers if it wishes, using the + * $subtype parameter. For example moodle_mod_workshop_renderer, + * moodle_mod_workshop_allocation_manual_renderer etc. + * + * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'. + * @param moodle_page $page the page the renderer is outputting content for. + * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' + * @return object an object implementing the requested renderer interface. + */ + public function get_renderer($component, $page, $subtype=null); +} + +/** + * This is a base class to help you implement the renderer_factory interface. + * + * It keeps a cache of renderers that have been constructed, so you only need + * to construct each one once in you subclass. + * + * It also has a method to get the name of, and include the renderer.php with + * the definition of, the standard renderer class for a given module. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +abstract class renderer_factory_base implements renderer_factory { + /** @var theme_config the theme we belong to. */ + protected $theme; + + /** + * Constructor. + * @param theme_config $theme the theme we belong to. + */ + public function __construct($theme) { + $this->theme = $theme; + } + /** + * For a given module name, return the name of the standard renderer class + * that defines the renderer interface for that module. + * + * Also, if it exists, include the renderer.php file for that module, so + * the class definition of the default renderer has been loaded. + * + * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'. + * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' + * @return string the name of the standard renderer class for that module. + */ + protected function standard_renderer_class_for_module($component, $subtype=null) { + if ($component != 'core') { + $pluginrenderer = get_component_directory($component) . '/renderer.php'; + if (file_exists($pluginrenderer)) { + include_once($pluginrenderer); + } + } + if (is_null($subtype)) { + $class = 'moodle_' . $component . '_renderer'; + } else { + $class = 'moodle_' . $component . '_' . $subtype . '_renderer'; + } + if (!class_exists($class)) { + throw new coding_exception('Request for an unknown renderer class ' . $class); + } + return $class; + } +} + + +/** + * This is the default renderer factory for Moodle. It simply returns an instance + * of the appropriate standard renderer class. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class standard_renderer_factory extends renderer_factory_base { + /** + * Implement the subclass method + * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. + * @param moodle_page $page the page the renderer is outputting content for. + * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' + * @return object an object implementing the requested renderer interface. + */ + public function get_renderer($module, $page, $subtype=null) { + if ($module == 'core') { + return new moodle_core_renderer($page); + } else { + $class = $this->standard_renderer_class_for_module($module, $subtype); + return new $class($page, $this->get_renderer('core', $page)); + } + } +} + + +/** + * This is a slight variation on the standard_renderer_factory used by CLI scripts. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class cli_renderer_factory extends standard_renderer_factory { + /** + * Implement the subclass method + * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. + * @param moodle_page $page the page the renderer is outputting content for. + * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' + * @return object an object implementing the requested renderer interface. + */ + public function get_renderer($module, $page, $subtype=null) { + if ($module == 'core') { + return new cli_core_renderer($page); + } else { + parent::get_renderer($module, $page, $subtype); + } + } +} + + +/** + * This is renderer factory allows themes to override the standard renderers using + * php code. + * + * It will load any code from theme/mytheme/renderers.php and + * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for + * a renderer for 'component', it will create a mytheme_component_renderer or a + * parenttheme_component_renderer, instead of a moodle_component_renderer, + * if either of those classes exist. + * + * This generates the slightly different HTML that the custom_corners theme expects. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class theme_overridden_renderer_factory extends standard_renderer_factory { + protected $prefixes = array(); + + /** + * Constructor. + * @param object $theme the theme we are rendering for. + */ + public function __construct($theme) { + global $CFG; + parent::__construct($theme); + + // Initialise $this->prefixes. + $renderersfile = $theme->dir . '/renderers.php'; + if (is_readable($renderersfile)) { + include_once($renderersfile); + $this->prefixes[] = $theme->name . '_'; + } + if (!empty($theme->parent)) { + $renderersfile = $CFG->themedir .'/'. $theme->parent . '/renderers.php'; + if (is_readable($renderersfile)) { + include_once($renderersfile); + $this->prefixes[] = $theme->parent . '_'; + } + } + } + + /** + * Implement the subclass method + * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. + * @param moodle_page $page the page the renderer is outputting content for. + * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' + * @return object an object implementing the requested renderer interface. + */ + public function get_renderer($module, $page, $subtype=null) { + foreach ($this->prefixes as $prefix) { + if (is_null($subtype)) { + $classname = $prefix . $module . '_renderer'; + } else { + $classname = $prefix . $module . '_' . $subtype . '_renderer'; + } + if (class_exists($classname)) { + if ($module == 'core') { + return new $classname($page); + } else { + return new $classname($page, $this->get_renderer('core', $page)); + } + } + } + return parent::get_renderer($module, $page, $subtype); + } +} + + +/** + * This is renderer factory that allows you to create templated themes. + * + * This should be considered an experimental proof of concept. In particular, + * the performance is probably not very good. Do not try to use in on a busy site + * without doing careful load testing first! + * + * This renderer factory returns instances of {@link template_renderer} class + * which which implement the corresponding renderer interface in terms of + * templates. To use this your theme must have a templates folder inside it. + * Then suppose the method moodle_core_renderer::greeting($name = 'world'); + * exists. Then, a call to $OUTPUT->greeting() will cause the template + * /theme/yourtheme/templates/core/greeting.php to be rendered, with the variable + * $name available. The greeting.php template might contain + * + *
+ * 

Hello !

+ *
+ * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class template_renderer_factory extends renderer_factory_base { + /** + * An array of paths of where to search for templates. Normally this theme, + * the parent theme then the standardtemplate theme. (If some of these do + * not exist, or are the same as each other, then the list will be shorter. + */ + protected $searchpaths = array(); + + /** + * Constructor. + * @param object $theme the theme we are rendering for. + */ + public function __construct($theme) { + global $CFG; + parent::__construct($theme); + + // Initialise $this->searchpaths. + if ($theme->name != 'standardtemplate') { + $templatesdir = $theme->dir . '/templates'; + if (is_dir($templatesdir)) { + $this->searchpaths[] = $templatesdir; + } + } + if (!empty($theme->parent)) { + $templatesdir = $CFG->themedir .'/'. $theme->parent . '/templates'; + if (is_dir($templatesdir)) { + $this->searchpaths[] = $templatesdir; + } + } + $this->searchpaths[] = $CFG->themedir .'/standardtemplate/templates'; + } + + /** + * Implement the subclass method + * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. + * @param moodle_page $page the page the renderer is outputting content for. + * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' + * @return object an object implementing the requested renderer interface. + */ + public function get_renderer($module, $page, $subtype=null) { + // Refine the list of search paths for this module. + $searchpaths = array(); + foreach ($this->searchpaths as $rootpath) { + $path = $rootpath . '/' . $module; + if (!is_null($subtype)) { + $path .= '/' . $subtype; + } + if (is_dir($path)) { + $searchpaths[] = $path; + } + } + + // Create a template_renderer that copies the API of the standard renderer. + $copiedclass = $this->standard_renderer_class_for_module($module, $subtype); + return new template_renderer($copiedclass, $searchpaths, $page); + } +} diff --git a/lib/outputlib.php b/lib/outputlib.php index ee939bb2e3e..aa4eb64ea75 100644 --- a/lib/outputlib.php +++ b/lib/outputlib.php @@ -23,6 +23,12 @@ */ define('HTML_ATTR_EMPTY', '@EMPTY@'); +require_once($CFG->libdir.'/outputcomponents.php'); +require_once($CFG->libdir.'/outputactions.php'); +require_once($CFG->libdir.'/outputfactories.php'); +require_once($CFG->libdir.'/outputpixfinders.php'); +require_once($CFG->libdir.'/outputrenderers.php'); + /** * Functions for generating the HTML that Moodle should output. * @@ -34,93 +40,6 @@ define('HTML_ATTR_EMPTY', '@EMPTY@'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -/** - * A renderer factory is just responsible for creating an appropriate renderer - * for any given part of Moodle. - * - * Which renderer factory to use is chose by the current theme, and an instance - * if created automatically when the theme is set up. - * - * A renderer factory must also have a constructor that takes a theme_config object. - * (See {@link renderer_factory_base::__construct} for an example.) - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -interface renderer_factory { - /** - * Return the renderer for a particular part of Moodle. - * - * The renderer interfaces are defined by classes called moodle_{plugin}_renderer - * where {plugin} is the name of the component. The renderers for core Moodle are - * defined in lib/renderer.php. For plugins, they will be defined in a file - * called renderer.php inside the plugin. - * - * Renderers will normally want to subclass the moodle_renderer_base class. - * (However, if you really know what you are doing, you don't have to do that.) - * - * There is no separate interface definition for renderers. The default - * moodle_{plugin}_renderer implementation also serves to define the API for - * other implementations of the interface, whether or not they subclass it. - * For example, {@link custom_corners_core_renderer} does subclass - * {@link moodle_core_renderer}. On the other hand, if you are using - * {@link template_renderer_factory} then you always get back an instance - * of the {@link template_renderer} class, whatever type of renderer you ask - * for. This uses the fact that PHP is a dynamic language. - * - * A particular plugin can define multiple renderers if it wishes, using the - * $subtype parameter. For example moodle_mod_workshop_renderer, - * moodle_mod_workshop_allocation_manual_renderer etc. - * - * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'. - * @param moodle_page $page the page the renderer is outputting content for. - * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' - * @return object an object implementing the requested renderer interface. - */ - public function get_renderer($component, $page, $subtype=null); -} - - -/** - * An icon finder is responsible for working out the correct URL for an icon. - * - * A icon finder must also have a constructor that takes a theme object. - * (See {@link standard_icon_finder::__construct} for an example.) - * - * Note that we are planning to change the Moodle icon naming convention before - * the Moodle 2.0 release. Therefore, this API will probably change. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -interface icon_finder { - /** - * Return the URL for an icon identified as in pre-Moodle 2.0 code. - * - * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif"; - * then old_icon_url('i/course'); will return the equivalent URL that is correct now. - * - * @param string $iconname the name of the icon. - * @return string the URL for that icon. - */ - public function old_icon_url($iconname); - - /** - * Return the URL for an icon identified as in pre-Moodle 2.0 code. - * - * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif"; - * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now. - * - * @param string $iconname the name of the icon. - * @param string $module the module the icon belongs to. - * @return string the URL for that icon. - */ - public function mod_icon_url($iconname, $module); -} - - /** * This class represents the configuration variables of a Moodle theme. * @@ -822,799 +741,6 @@ class theme_config { } } - -/** - * This icon finder implements the old scheme that was used when themes that had - * $THEME->custompix = false. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class pix_icon_finder implements icon_finder { - /** - * Constructor - * @param theme_config $theme the theme we are finding icons for (which is irrelevant). - */ - public function __construct($theme) { - } - - /** - * Implement interface method. - * @param string $iconname the name of the icon. - * @return string the URL for that icon. - */ - public function old_icon_url($iconname) { - global $CFG; - if (file_exists($CFG->dirroot . '/pix/' . $iconname . '.png')) { - return $CFG->httpswwwroot . '/pix/' . $iconname . '.png'; - } else { - return $CFG->httpswwwroot . '/pix/' . $iconname . '.gif'; - } - } - - /** - * Implement interface method. - * @param string $iconname the name of the icon. - * @param string $module the module the icon belongs to. - * @return string the URL for that icon. - */ - public function mod_icon_url($iconname, $module) { - global $CFG; - if (file_exists($CFG->dirroot . '/mod/' . $module . '/' . $iconname . '.png')) { - return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.png'; - } else { - return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.gif'; - } - } -} - - -/** - * This icon finder implements the old scheme that was used for themes that had - * $THEME->custompix = true. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class theme_icon_finder implements icon_finder { - protected $themename; - /** - * Constructor - * @param theme_config $theme the theme we are finding icons for. - */ - public function __construct($theme) { - $this->themename = $theme->name; - } - - /** - * Implement interface method. - * @param string $iconname the name of the icon. - * @return string the URL for that icon. - */ - public function old_icon_url($iconname) { - global $CFG; - if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/' . $iconname . '.png')) { - return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.png'; - } else { - return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.gif'; - } - } - - /** - * Implement interface method. - * @param string $iconname the name of the icon. - * @param string $module the module the icon belongs to. - * @return string the URL for that icon. - */ - public function mod_icon_url($iconname, $module) { - global $CFG; - if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png')) { - return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png'; - } else { - return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.gif'; - } - } -} - - -/** - * This icon finder implements the algorithm in pix/smartpix.php. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class smartpix_icon_finder extends pix_icon_finder { - protected $places = array(); - - /** - * Constructor - * @param theme_config $theme the theme we are finding icons for. - */ - public function __construct($theme) { - global $CFG; - $this->places[$CFG->themedir . '/' . $theme->name . '/pix/'] = - $CFG->httpsthemewww . '/' . $theme->name . '/pix/'; - if (!empty($theme->parent)) { - $this->places[$CFG->themedir . '/' . $theme->parent . '/pix/'] = - $CFG->httpsthemewww . '/' . $theme->parent . '/pix/'; - } - } - - /** - * Implement interface method. - * @param string $iconname the name of the icon. - * @return string the URL for that icon. - */ - public function old_icon_url($iconname) { - foreach ($this->places as $dirroot => $urlroot) { - if (file_exists($dirroot . $iconname . '.png')) { - return $dirroot . $iconname . '.png'; - } else if (file_exists($dirroot . $iconname . '.gif')) { - return $dirroot . $iconname . '.gif'; - } - } - return parent::old_icon_url($iconname); - } - - /** - * Implement interface method. - * @param string $iconname the name of the icon. - * @param string $module the module the icon belongs to. - * @return string the URL for that icon. - */ - public function mod_icon_url($iconname, $module) { - foreach ($this->places as $dirroot => $urlroot) { - if (file_exists($dirroot . 'mod/' . $iconname . '.png')) { - return $dirroot . 'mod/' . $iconname . '.png'; - } else if (file_exists($dirroot . 'mod/' . $iconname . '.gif')) { - return $dirroot . 'mod/' . $iconname . '.gif'; - } - } - return parent::old_icon_url($iconname, $module); - } -} - - -/** - * This is a base class to help you implement the renderer_factory interface. - * - * It keeps a cache of renderers that have been constructed, so you only need - * to construct each one once in you subclass. - * - * It also has a method to get the name of, and include the renderer.php with - * the definition of, the standard renderer class for a given module. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -abstract class renderer_factory_base implements renderer_factory { - /** @var theme_config the theme we belong to. */ - protected $theme; - - /** - * Constructor. - * @param theme_config $theme the theme we belong to. - */ - public function __construct($theme) { - $this->theme = $theme; - } - /** - * For a given module name, return the name of the standard renderer class - * that defines the renderer interface for that module. - * - * Also, if it exists, include the renderer.php file for that module, so - * the class definition of the default renderer has been loaded. - * - * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'. - * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' - * @return string the name of the standard renderer class for that module. - */ - protected function standard_renderer_class_for_module($component, $subtype=null) { - if ($component != 'core') { - $pluginrenderer = get_component_directory($component) . '/renderer.php'; - if (file_exists($pluginrenderer)) { - include_once($pluginrenderer); - } - } - if (is_null($subtype)) { - $class = 'moodle_' . $component . '_renderer'; - } else { - $class = 'moodle_' . $component . '_' . $subtype . '_renderer'; - } - if (!class_exists($class)) { - throw new coding_exception('Request for an unknown renderer class ' . $class); - } - return $class; - } -} - - -/** - * This is the default renderer factory for Moodle. It simply returns an instance - * of the appropriate standard renderer class. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class standard_renderer_factory extends renderer_factory_base { - /** - * Implement the subclass method - * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. - * @param moodle_page $page the page the renderer is outputting content for. - * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' - * @return object an object implementing the requested renderer interface. - */ - public function get_renderer($module, $page, $subtype=null) { - if ($module == 'core') { - return new moodle_core_renderer($page); - } else { - $class = $this->standard_renderer_class_for_module($module, $subtype); - return new $class($page, $this->get_renderer('core', $page)); - } - } -} - - -/** - * This is a slight variation on the standard_renderer_factory used by CLI scripts. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class cli_renderer_factory extends standard_renderer_factory { - /** - * Implement the subclass method - * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. - * @param moodle_page $page the page the renderer is outputting content for. - * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' - * @return object an object implementing the requested renderer interface. - */ - public function get_renderer($module, $page, $subtype=null) { - if ($module == 'core') { - return new cli_core_renderer($page); - } else { - parent::get_renderer($module, $page, $subtype); - } - } -} - - -/** - * This is renderer factory allows themes to override the standard renderers using - * php code. - * - * It will load any code from theme/mytheme/renderers.php and - * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for - * a renderer for 'component', it will create a mytheme_component_renderer or a - * parenttheme_component_renderer, instead of a moodle_component_renderer, - * if either of those classes exist. - * - * This generates the slightly different HTML that the custom_corners theme expects. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class theme_overridden_renderer_factory extends standard_renderer_factory { - protected $prefixes = array(); - - /** - * Constructor. - * @param object $theme the theme we are rendering for. - */ - public function __construct($theme) { - global $CFG; - parent::__construct($theme); - - // Initialise $this->prefixes. - $renderersfile = $theme->dir . '/renderers.php'; - if (is_readable($renderersfile)) { - include_once($renderersfile); - $this->prefixes[] = $theme->name . '_'; - } - if (!empty($theme->parent)) { - $renderersfile = $CFG->themedir .'/'. $theme->parent . '/renderers.php'; - if (is_readable($renderersfile)) { - include_once($renderersfile); - $this->prefixes[] = $theme->parent . '_'; - } - } - } - - /** - * Implement the subclass method - * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. - * @param moodle_page $page the page the renderer is outputting content for. - * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' - * @return object an object implementing the requested renderer interface. - */ - public function get_renderer($module, $page, $subtype=null) { - foreach ($this->prefixes as $prefix) { - if (is_null($subtype)) { - $classname = $prefix . $module . '_renderer'; - } else { - $classname = $prefix . $module . '_' . $subtype . '_renderer'; - } - if (class_exists($classname)) { - if ($module == 'core') { - return new $classname($page); - } else { - return new $classname($page, $this->get_renderer('core', $page)); - } - } - } - return parent::get_renderer($module, $page, $subtype); - } -} - - -/** - * This is renderer factory that allows you to create templated themes. - * - * This should be considered an experimental proof of concept. In particular, - * the performance is probably not very good. Do not try to use in on a busy site - * without doing careful load testing first! - * - * This renderer factory returns instances of {@link template_renderer} class - * which which implement the corresponding renderer interface in terms of - * templates. To use this your theme must have a templates folder inside it. - * Then suppose the method moodle_core_renderer::greeting($name = 'world'); - * exists. Then, a call to $OUTPUT->greeting() will cause the template - * /theme/yourtheme/templates/core/greeting.php to be rendered, with the variable - * $name available. The greeting.php template might contain - * - *
- * 

Hello !

- *
- * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class template_renderer_factory extends renderer_factory_base { - /** - * An array of paths of where to search for templates. Normally this theme, - * the parent theme then the standardtemplate theme. (If some of these do - * not exist, or are the same as each other, then the list will be shorter. - */ - protected $searchpaths = array(); - - /** - * Constructor. - * @param object $theme the theme we are rendering for. - */ - public function __construct($theme) { - global $CFG; - parent::__construct($theme); - - // Initialise $this->searchpaths. - if ($theme->name != 'standardtemplate') { - $templatesdir = $theme->dir . '/templates'; - if (is_dir($templatesdir)) { - $this->searchpaths[] = $templatesdir; - } - } - if (!empty($theme->parent)) { - $templatesdir = $CFG->themedir .'/'. $theme->parent . '/templates'; - if (is_dir($templatesdir)) { - $this->searchpaths[] = $templatesdir; - } - } - $this->searchpaths[] = $CFG->themedir .'/standardtemplate/templates'; - } - - /** - * Implement the subclass method - * @param string $module name such as 'core', 'mod_forum' or 'qtype_multichoice'. - * @param moodle_page $page the page the renderer is outputting content for. - * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' - * @return object an object implementing the requested renderer interface. - */ - public function get_renderer($module, $page, $subtype=null) { - // Refine the list of search paths for this module. - $searchpaths = array(); - foreach ($this->searchpaths as $rootpath) { - $path = $rootpath . '/' . $module; - if (!is_null($subtype)) { - $path .= '/' . $subtype; - } - if (is_dir($path)) { - $searchpaths[] = $path; - } - } - - // Create a template_renderer that copies the API of the standard renderer. - $copiedclass = $this->standard_renderer_class_for_module($module, $subtype); - return new template_renderer($copiedclass, $searchpaths, $page); - } -} - - -/** - * Simple base class for Moodle renderers. - * - * Tracks the xhtml_container_stack to use, which is passed in in the constructor. - * - * Also has methods to facilitate generating HTML output. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class moodle_renderer_base { - /** @var xhtml_container_stack the xhtml_container_stack to use. */ - protected $opencontainers; - /** @var moodle_page the page we are rendering for. */ - protected $page; - - /** - * Constructor - * @param moodle_page $page the page we are doing output for. - */ - public function __construct($page) { - $this->opencontainers = $page->opencontainers; - $this->page = $page; - } - - /** - * Have we started output yet? - * @return boolean true if the header has been printed. - */ - public function has_started() { - return $this->page->state >= moodle_page::STATE_IN_BODY; - } - - /** - * Outputs a tag with attributes and contents - * @param string $tagname The name of tag ('a', 'img', 'span' etc.) - * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) - * @param string $contents What goes between the opening and closing tags - * @return string HTML fragment - */ - protected function output_tag($tagname, $attributes, $contents) { - return $this->output_start_tag($tagname, $attributes) . $contents . - $this->output_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 - */ - protected function output_start_tag($tagname, $attributes) { - return '<' . $tagname . $this->output_attributes($attributes) . '>'; - } - - /** - * Outputs a closing tag - * @param string $tagname The name of tag ('a', 'img', 'span' etc.) - * @return string HTML fragment - */ - protected function output_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 - */ - protected function output_empty_tag($tagname, $attributes) { - return '<' . $tagname . $this->output_attributes($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 - */ - protected function output_attribute($name, $value) { - if (is_array($value)) { - debugging("Passed an array for the HTML attribute $name", DEBUG_DEVELOPER); - } - - $value = trim($value); - if ($value == HTML_ATTR_EMPTY) { - return ' ' . $name . '=""'; - } else if ($value || is_numeric($value)) { // We want 0 to be output. - 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 - */ - protected function output_attributes($attributes) { - if (empty($attributes)) { - $attributes = array(); - } - $output = ''; - foreach ($attributes as $name => $value) { - $output .= $this->output_attribute($name, $value); - } - return $output; - } - - /** - * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value - * @param mixed $classes Space-separated string or array of classes - * @return string HTML class attribute value - */ - public static function prepare_classes($classes) { - if (is_array($classes)) { - return implode(' ', array_unique($classes)); - } - return $classes; - } - - /** - * Return the URL for an icon identified as in pre-Moodle 2.0 code. - * - * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif"; - * then old_icon_url('i/course'); will return the equivalent URL that is correct now. - * - * @param string $iconname the name of the icon. - * @return string the URL for that icon. - */ - public function old_icon_url($iconname) { - return $this->page->theme->old_icon_url($iconname); - } - - /** - * Return the URL for an icon identified as in pre-Moodle 2.0 code. - * - * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif"; - * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now. - * - * @param string $iconname the name of the icon. - * @param string $module the module the icon belongs to. - * @return string the URL for that icon. - */ - public function mod_icon_url($iconname, $module) { - return $this->page->theme->mod_icon_url($iconname, $module); - } - - /** - * A helper function that takes a moodle_html_component subclass as param. - * If that component has an id attribute and an array of valid component_action objects, - * it sets up the appropriate event handlers. - * - * @param moodle_html_component $component - * @return void; - */ - protected function prepare_event_handlers(&$component) { - $actions = $component->get_actions(); - if (!empty($actions) && is_array($actions) && $actions[0] instanceof component_action) { - foreach ($actions as $action) { - if (!empty($action->jsfunction)) { - $this->page->requires->event_handler($component->id, $action->event, $action->jsfunction, $action->jsfunctionargs); - } - } - } - } - - /** - * Given a moodle_html_component with height and/or width set, translates them - * to appropriate CSS rules. - * - * @param moodle_html_component $component - * @return string CSS rules - */ - protected function prepare_legacy_width_and_height($component) { - $output = ''; - if (!empty($component->height)) { - // We need a more intelligent way to handle these warnings. If $component->height have come from - // somewhere in deprecatedlib.php, then there is no point outputting a warning here. - // debugging('Explicit height given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER); - $output .= "height: {$component->height}px;"; - } - if (!empty($component->width)) { - // debugging('Explicit width given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER); - $output .= "width: {$component->width}px;"; - } - return $output; - } -} - - -/** - * This is the templated renderer which copies the API of another class, replacing - * all methods calls with instantiation of a template. - * - * When the method method_name is called, this class will search for a template - * called method_name.php in the folders in $searchpaths, taking the first one - * that it finds. Then it will set up variables for each of the arguments of that - * method, and render the template. This is implemented in the {@link __call()} - * PHP magic method. - * - * Methods like print_box_start and print_box_end are handles specially, and - * implemented in terms of the print_box.php method. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class template_renderer extends moodle_renderer_base { - /** @var ReflectionClass information about the class whose API we are copying. */ - protected $copiedclass; - /** @var array of places to search for templates. */ - protected $searchpaths; - protected $rendererfactory; - - /** - * Magic word used when breaking apart container templates to implement - * _start and _end methods. - */ - const CONTENTSTOKEN = '-@#-Contents-go-here-#@-'; - - /** - * Constructor - * @param string $copiedclass the name of a class whose API we should be copying. - * @param array $searchpaths a list of folders to search for templates in. - * @param moodle_page $page the page we are doing output for. - */ - public function __construct($copiedclass, $searchpaths, $page) { - parent::__construct($page); - $this->copiedclass = new ReflectionClass($copiedclass); - $this->searchpaths = $searchpaths; - } - - /** - * PHP magic method implementation. Do not use this method directly. - * @param string $method The method to call - * @param array $arguments The arguments to pass to the method - * @return mixed The return value of the called method - */ - public function __call($method, $arguments) { - if (substr($method, -6) == '_start') { - return $this->process_start(substr($method, 0, -6), $arguments); - } else if (substr($method, -4) == '_end') { - return $this->process_end(substr($method, 0, -4), $arguments); - } else { - return $this->process_template($method, $arguments); - } - } - - /** - * Render the template for a given method of the renderer class we are copying, - * using the arguments passed. - * @param string $method the method that was called. - * @param array $arguments the arguments that were passed to it. - * @return string the HTML to be output. - */ - protected function process_template($method, $arguments) { - if (!$this->copiedclass->hasMethod($method) || - !$this->copiedclass->getMethod($method)->isPublic()) { - throw new coding_exception('Unknown method ' . $method); - } - - // Find the template file for this method. - $template = $this->find_template($method); - - // Use the reflection API to find out what variable names the arguments - // should be stored in, and fill in any missing ones with the defaults. - $namedarguments = array(); - $expectedparams = $this->copiedclass->getMethod($method)->getParameters(); - foreach ($expectedparams as $param) { - $paramname = $param->getName(); - if (!empty($arguments)) { - $namedarguments[$paramname] = array_shift($arguments); - } else if ($param->isDefaultValueAvailable()) { - $namedarguments[$paramname] = $param->getDefaultValue(); - } else { - throw new coding_exception('Missing required argument ' . $paramname); - } - } - - // Actually render the template. - return $this->render_template($template, $namedarguments); - } - - /** - * Actually do the work of rendering the template. - * @param string $_template the full path to the template file. - * @param array $_namedarguments an array variable name => value, the variables - * that should be available to the template. - * @return string the HTML to be output. - */ - protected function render_template($_template, $_namedarguments) { - // Note, we intentionally break the coding guidelines with regards to - // local variable names used in this function, so that they do not clash - // with the names of any variables being passed to the template. - - global $CFG, $SITE, $THEME, $USER; - // The next lines are a bit tricky. The point is, here we are in a method - // of a renderer class, and this object may, or may not, be the same as - // the global $OUTPUT object. When rendering the template, we want to use - // this object. However, people writing Moodle code expect the current - // renderer to be called $OUTPUT, not $this, so define a variable called - // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE. - $OUTPUT = $this; - $PAGE = $this->page; - $COURSE = $this->page->course; - - // And the parameters from the function call. - extract($_namedarguments); - - // Include the template, capturing the output. - ob_start(); - include($_template); - $_result = ob_get_contents(); - ob_end_clean(); - - return $_result; - } - - /** - * Searches the folders in {@link $searchpaths} to try to find a template for - * this method name. Throws an exception if one cannot be found. - * @param string $method the method name. - * @return string the full path of the template to use. - */ - protected function find_template($method) { - foreach ($this->searchpaths as $path) { - $filename = $path . '/' . $method . '.php'; - if (file_exists($filename)) { - return $filename; - } - } - throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method); - } - - /** - * Handle methods like print_box_start by using the print_box template, - * splitting the result, pushing the end onto the stack, then returning the start. - * @param string $method the method that was called, with _start stripped off. - * @param array $arguments the arguments that were passed to it. - * @return string the HTML to be output. - */ - protected function process_start($method, $arguments) { - array_unshift($arguments, self::CONTENTSTOKEN); - $html = $this->process_template($method, $arguments); - list($start, $end) = explode(self::CONTENTSTOKEN, $html, 2); - $this->opencontainers->push($method, $end); - return $start; - } - - /** - * Handle methods like print_box_end, we just need to pop the end HTML from - * the stack. - * @param string $method the method that was called, with _end stripped off. - * @param array $arguments not used. Assumed to be irrelevant. - * @return string the HTML to be output. - */ - protected function process_end($method, $arguments) { - return $this->opencontainers->pop($method); - } - - /** - * @return array the list of paths where this class searches for templates. - */ - public function get_search_paths() { - return $this->searchpaths; - } - - /** - * @return string the name of the class whose API we are copying. - */ - public function get_copied_class() { - return $this->copiedclass->getName(); - } -} - - /** * This class keeps track of which HTML tags are currently open. * @@ -1764,4076 +890,6 @@ class xhtml_container_stack { } -/** - * The standard implementation of the moodle_core_renderer interface. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class moodle_core_renderer extends moodle_renderer_base { - /** @var string used in {@link header()}. */ - const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%'; - /** @var string used in {@link header()}. */ - const END_HTML_TOKEN = '%%ENDHTML%%'; - /** @var string used in {@link header()}. */ - const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]'; - /** @var string used to pass information from {@link doctype()} to {@link standard_head_html()}. */ - protected $contenttype; - /** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */ - protected $metarefreshtag = ''; - - /** - * Get the DOCTYPE declaration that should be used with this page. Designed to - * be called in theme layout.php files. - * @return string the DOCTYPE declaration (and any XML prologue) that should be used. - */ - public function doctype() { - global $CFG; - - $doctype = '' . "\n"; - $this->contenttype = 'text/html; charset=utf-8'; - - if (empty($CFG->xmlstrictheaders)) { - return $doctype; - } - - // We want to serve the page with an XML content type, to force well-formedness errors to be reported. - $prolog = '' . "\n"; - if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) { - // Firefox and other browsers that can cope natively with XHTML. - $this->contenttype = 'application/xhtml+xml; charset=utf-8'; - - } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) { - // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet. - $this->contenttype = 'application/xml; charset=utf-8'; - $prolog .= 'httpswwwroot . '/lib/xhtml.xsl"?>' . "\n"; - - } else { - $prolog = ''; - } - - return $prolog . $doctype; - } - - /** - * The attributes that should be added to the tag. Designed to - * be called in theme layout.php files. - * @return string HTML fragment. - */ - public function htmlattributes() { - return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"'; - } - - /** - * The standard tags (meta tags, links to stylesheets and JavaScript, etc.) - * that should be included in the tag. Designed to be called in theme - * layout.php files. - * @return string HTML fragment. - */ - public function standard_head_html() { - global $CFG; - $output = ''; - $output .= '' . "\n"; - $output .= '' . "\n"; - if (!$this->page->cacheable) { - $output .= '' . "\n"; - $output .= '' . "\n"; - } - // This is only set by the {@link redirect()} method - $output .= $this->metarefreshtag; - - // Check if a periodic refresh delay has been set and make sure we arn't - // already meta refreshing - if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) { - $output .= ''; - } - - $this->page->requires->js('lib/javascript-static.js')->in_head(); - $this->page->requires->js('lib/javascript-deprecated.js')->in_head(); - $this->page->requires->js('lib/javascript-mod.php')->in_head(); - $this->page->requires->js('lib/overlib/overlib.js')->in_head(); - $this->page->requires->js('lib/overlib/overlib_cssstyle.js')->in_head(); - $this->page->requires->js('lib/cookies.js')->in_head(); - $this->page->requires->js_function_call('setTimeout', Array('fix_column_widths()', 20)); - - $focus = $this->page->focuscontrol; - if (!empty($focus)) { - if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) { - // This is a horrifically bad way to handle focus but it is passed in - // through messy formslib::moodleform - $this->page->requires->js_function_call('old_onload_focus', Array($matches[1], $matches[2])); - } else if (strpos($focus, '.')!==false) { - // Old style of focus, bad way to do it - debugging('This code is using the old style focus event, Please update this code to focus on an element id or the moodleform focus method.', DEBUG_DEVELOPER); - $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2)); - } else { - // Focus element with given id - $this->page->requires->js_function_call('focuscontrol', Array($focus)); - } - } - - // Add the meta tags from the themes if any were requested. - $output .= $this->page->theme->get_meta_tags($this->page); - - // Get any HTML from the page_requirements_manager. - $output .= $this->page->requires->get_head_code(); - - // List alternate versions. - foreach ($this->page->alternateversions as $type => $alt) { - $output .= $this->output_empty_tag('link', array('rel' => 'alternate', - 'type' => $type, 'title' => $alt->title, 'href' => $alt->url)); - } - - return $output; - } - - /** - * The standard tags (typically skip links) that should be output just inside - * the start of the tag. Designed to be called in theme layout.php files. - * @return string HTML fragment. - */ - public function standard_top_of_body_html() { - return $this->page->requires->get_top_of_body_code(); - } - - /** - * The standard tags (typically performance information and validation links, - * if we are in developer debug mode) that should be output in the footer area - * of the page. Designed to be called in theme layout.php files. - * @return string HTML fragment. - */ - public function standard_footer_html() { - global $CFG; - - // This function is normally called from a layout.php file in {@link header()} - // but some of the content won't be known until later, so we return a placeholder - // for now. This will be replaced with the real content in {@link footer()}. - $output = self::PERFORMANCE_INFO_TOKEN; - if (!empty($CFG->debugpageinfo)) { - $output .= '
This page is: ' . $this->page->debug_summary() . '
'; - } - if (!empty($CFG->debugvalidators)) { - $output .= ''; - } - return $output; - } - - /** - * The standard tags (typically script tags that are not needed earlier) that - * should be output after everything else, . Designed to be called in theme layout.php files. - * @return string HTML fragment. - */ - public function standard_end_of_body_html() { - // This function is normally called from a layout.php file in {@link header()} - // but some of the content won't be known until later, so we return a placeholder - // for now. This will be replaced with the real content in {@link footer()}. - echo self::END_HTML_TOKEN; - } - - /** - * Return the standard string that says whether you are logged in (and switched - * roles/logged in as another user). - * @return string HTML fragment. - */ - public function login_info() { - global $USER; - return user_login_string($this->page->course, $USER); - } - - /** - * Return the 'back' link that normally appears in the footer. - * @return string HTML fragment. - */ - public function home_link() { - global $CFG, $SITE; - - if ($this->page->pagetype == 'site-index') { - // Special case for site home page - please do not remove - return ''; - - } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) { - // Special case for during install/upgrade. - return ''; - - } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) { - return ''; - - } else { - return ''; - } - } - - /** - * Redirects the user by any means possible given the current state - * - * This function should not be called directly, it should always be called using - * the redirect function in lib/weblib.php - * - * The redirect function should really only be called before page output has started - * however it will allow itself to be called during the state STATE_IN_BODY - * - * @param string $encodedurl The URL to send to encoded if required - * @param string $message The message to display to the user if any - * @param int $delay The delay before redirecting a user, if $message has been - * set this is a requirement and defaults to 3, set to 0 no delay - * @param boolean $debugdisableredirect this redirect has been disabled for - * debugging purposes. Display a message that explains, and don't - * trigger the redirect. - * @return string The HTML to display to the user before dying, may contain - * meta refresh, javascript refresh, and may have set header redirects - */ - public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) { - global $CFG; - $url = str_replace('&', '&', $encodedurl); - - switch ($this->page->state) { - case moodle_page::STATE_BEFORE_HEADER : - // No output yet it is safe to delivery the full arsenal of redirect methods - if (!$debugdisableredirect) { - // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time. - $this->metarefreshtag = ''."\n"; - $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3); - } - $output = $this->header(); - break; - case moodle_page::STATE_PRINTING_HEADER : - // We should hopefully never get here - throw new coding_exception('You cannot redirect while printing the page header'); - break; - case moodle_page::STATE_IN_BODY : - // We really shouldn't be here but we can deal with this - debugging("You should really redirect before you start page output"); - if (!$debugdisableredirect) { - $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay); - } - $output = $this->opencontainers->pop_all_but_last(); - break; - case moodle_page::STATE_DONE : - // Too late to be calling redirect now - throw new coding_exception('You cannot redirect after the entire page has been generated'); - break; - } - $output .= $this->notification($message, 'redirectmessage'); - $output .= ''. get_string('continue') .''; - if ($debugdisableredirect) { - $output .= '

Error output, so disabling automatic redirect.

'; - } - $output .= $this->footer(); - return $output; - } - - /** - * Start output by sending the HTTP headers, and printing the HTML - * and the start of the . - * - * To control what is printed, you should set properties on $PAGE. If you - * are familiar with the old {@link print_header()} function from Moodle 1.9 - * you will find that there are properties on $PAGE that correspond to most - * of the old parameters to could be passed to print_header. - * - * Not that, in due course, the remaining $navigation, $menu parameters here - * will be replaced by more properties of $PAGE, but that is still to do. - * - * @param string $navigation legacy, like the old parameter to print_header. Will be - * removed when there is a $PAGE->... replacement. - * @param string $menu legacy, like the old parameter to print_header. Will be - * removed when there is a $PAGE->... replacement. - * @return string HTML that you must output this, preferably immediately. - */ - public function header($navigation = '', $menu='') { - // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation - global $USER, $CFG; - - $this->page->set_state(moodle_page::STATE_PRINTING_HEADER); - - // Find the appropriate page template, based on $this->page->generaltype. - $templatefile = $this->page->theme->template_for_page($this->page->generaltype); - if ($templatefile) { - // Render the template. - $template = $this->render_page_template($templatefile, $menu, $navigation); - } else { - // New style template not found, fall back to using header.html and footer.html. - $template = $this->handle_legacy_theme($navigation, $menu); - } - - // Slice the template output into header and footer. - $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN); - if ($cutpos === false) { - throw new coding_exception('Layout template ' . $templatefile . - ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".'); - } - $header = substr($template, 0, $cutpos); - $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN)); - - if (empty($this->contenttype)) { - debugging('The layout template did not call $OUTPUT->doctype()'); - $this->doctype(); - } - - send_headers($this->contenttype, $this->page->cacheable); - $this->opencontainers->push('header/footer', $footer); - $this->page->set_state(moodle_page::STATE_IN_BODY); - return $header . $this->skip_link_target(); - } - - /** - * Renders and outputs the page template. - * @param string $templatefile The name of the template's file - * @param array $menu The menu that will be used in the included file - * @param array $navigation The navigation that will be used in the included file - * @return string HTML code - */ - protected function render_page_template($templatefile, $menu, $navigation) { - global $CFG, $SITE, $THEME, $USER; - // The next lines are a bit tricky. The point is, here we are in a method - // of a renderer class, and this object may, or may not, be the same as - // the global $OUTPUT object. When rendering the template, we want to use - // this object. However, people writing Moodle code expect the current - // renderer to be called $OUTPUT, not $this, so define a variable called - // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE. - $OUTPUT = $this; - $PAGE = $this->page; - $COURSE = $this->page->course; - - ob_start(); - include($templatefile); - $template = ob_get_contents(); - ob_end_clean(); - return $template; - } - - /** - * Renders and outputs a legacy template. - * @param array $navigation The navigation that will be used in the included file - * @param array $menu The menu that will be used in the included file - * @return string HTML code - */ - protected function handle_legacy_theme($navigation, $menu) { - global $CFG, $SITE, $USER; - // Set a pretend global from the properties of this class. - // See the comment in render_page_template for a fuller explanation. - $COURSE = $this->page->course; - $THEME = $this->page->theme; - - // Set up local variables that header.html expects. - $direction = $this->htmlattributes(); - $title = $this->page->title; - $heading = $this->page->heading; - $focus = $this->page->focuscontrol; - $button = $this->page->button; - $pageid = $this->page->pagetype; - $pageclass = $this->page->bodyclasses; - $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"'; - $home = $this->page->generaltype == 'home'; - - $meta = $this->standard_head_html(); - // The next line is a nasty hack. having set $meta to standard_head_html, we have already - // got the contents of include($CFG->javascript). However, legacy themes are going to - // include($CFG->javascript) again. We want to make sure that when they do, nothing is output. - $CFG->javascript = $CFG->libdir . '/emptyfile.php'; - - // Set up local variables that footer.html expects. - $homelink = $this->home_link(); - $loggedinas = $this->login_info(); - $course = $this->page->course; - $performanceinfo = self::PERFORMANCE_INFO_TOKEN; - - if (!$menu && $navigation) { - $menu = $loggedinas; - } - - if (!empty($this->page->theme->layouttable)) { - $lt = $this->page->theme->layouttable; - } else { - $lt = array('left', 'middle', 'right'); - } - - if (!empty($this->page->theme->block_l_max_width)) { - $preferredwidthleft = $this->page->theme->block_l_max_width; - } else { - $preferredwidthleft = 210; - } - if (!empty($this->page->theme->block_r_max_width)) { - $preferredwidthright = $this->page->theme->block_r_max_width; - } else { - $preferredwidthright = 210; - } - - ob_start(); - include($this->page->theme->dir . '/header.html'); - - echo '
'; - foreach ($lt as $column) { - if ($column == 'left' && $this->page->blocks->region_has_content(BLOCK_POS_LEFT, $this)) { - echo ''; - - } else if ($column == 'middle') { - echo ''; - - } else if ($column == 'right' && $this->page->blocks->region_has_content(BLOCK_POS_RIGHT, $this)) { - echo ''; - } - } - echo '
'; - echo $this->container_start(); - echo $this->blocks_for_region(BLOCK_POS_LEFT); - echo $this->container_end(); - echo ''; - echo $this->container_start(); - echo $this->skip_link_target(); - echo self::MAIN_CONTENT_TOKEN; - echo $this->container_end(); - echo ''; - echo $this->container_start(); - echo $this->blocks_for_region(BLOCK_POS_RIGHT); - echo $this->container_end(); - echo '
'; - - $menu = str_replace('navmenu', 'navmenufooter', $menu); - include($THEME->dir . '/footer.html'); - - $output = ob_get_contents(); - ob_end_clean(); - - // Put in the start of body code. Bit of a hack, put it in before the first - //
standard_top_of_body_html() . - substr($output, $divpos); - - // Put in the end token before the end of body. - $output = str_replace('', self::END_HTML_TOKEN . '', $output); - - // Make sure we use the correct doctype. - $output = preg_replace('/()/s', $this->doctype(), $output); - - return $output; - } - - /** - * Outputs the page's footer - * @return string HTML fragment - */ - public function footer() { - $output = $this->opencontainers->pop_all_but_last(true); - - $footer = $this->opencontainers->pop('header/footer'); - - // Provide some performance info if required - $performanceinfo = ''; - if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { - $perf = get_performance_info(); - if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) { - error_log("PERF: " . $perf['txt']); - } - if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) { - $performanceinfo = $perf['html']; - } - } - $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer); - - $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer); - - $this->page->set_state(moodle_page::STATE_DONE); - - - return $output . $footer; - } - - /** - * Output the row of editing icons for a block, as defined by the controls array. - * @param array $controls an array like {@link block_contents::$controls}. - * @return HTML fragment. - */ - public function block_controls($controls) { - if (empty($controls)) { - return ''; - } - $controlshtml = array(); - foreach ($controls as $control) { - $controlshtml[] = $this->output_tag('a', array('class' => 'icon', - 'title' => $control['caption'], 'href' => $control['url']), - $this->output_empty_tag('img', array('src' => $this->old_icon_url($control['icon']), - 'alt' => $control['caption']))); - } - return $this->output_tag('div', array('class' => 'commands'), implode('', $controlshtml)); - } - - /** - * Prints a nice side block with an optional header. - * - * The content is described - * by a {@link block_contents} object. - * - * @param block_contents $bc HTML for the content - * @param string $region the region the block is appearing in. - * @return string the HTML to be output. - */ - function block($bc, $region) { - $bc = clone($bc); // Avoid messing up the object passed in. - $bc->prepare(); - - $skiptitle = strip_tags($bc->title); - if (empty($skiptitle)) { - $output = ''; - $skipdest = ''; - } else { - $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'), - get_string('skipa', 'access', $skiptitle)); - $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), ''); - } - - $bc->attributes['id'] = $bc->id; - $bc->attributes['class'] = $bc->get_classes_string(); - $output .= $this->output_start_tag('div', $bc->attributes); - - $controlshtml = $this->block_controls($bc->controls); - - $title = ''; - if ($bc->title) { - $title = $this->output_tag('h2', null, $bc->title); - } - - if ($title || $controlshtml) { - $output .= $this->output_tag('div', array('class' => 'header'), - $this->output_tag('div', array('class' => 'title'), - $title . $controlshtml)); - } - - $output .= $this->output_start_tag('div', array('class' => 'content')); - $output .= $bc->content; - - if ($bc->footer) { - $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer); - } - - $output .= $this->output_end_tag('div'); - $output .= $this->output_end_tag('div'); - - if ($bc->annotation) { - $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation); - } - $output .= $skipdest; - - $this->init_block_hider_js($bc); - return $output; - } - - /** - * Calls the JS require function to hide a block. - * @param block_contents $bc A block_contents object - * @return void - */ - protected function init_block_hider_js($bc) { - if ($bc->collapsible != block_contents::NOT_HIDEABLE) { - $userpref = 'block' . $bc->blockinstanceid . 'hidden'; - user_preference_allow_ajax_update($userpref, PARAM_BOOL); - $this->page->requires->yui_lib('dom'); - $this->page->requires->yui_lib('event'); - $plaintitle = strip_tags($bc->title); - $this->page->requires->js_function_call('new block_hider', array($bc->id, $userpref, - get_string('hideblocka', 'access', $plaintitle), get_string('showblocka', 'access', $plaintitle), - $this->old_icon_url('t/switch_minus'), $this->old_icon_url('t/switch_plus'))); - } - } - - /** - * Render the contents of a block_list. - * @param array $icons the icon for each item. - * @param array $items the content of each item. - * @return string HTML - */ - public function list_block_contents($icons, $items) { - $row = 0; - $lis = array(); - foreach ($items as $key => $string) { - $item = $this->output_start_tag('li', array('class' => 'r' . $row)); - if ($icons) { - $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]); - } - $item .= $this->output_tag('div', array('class' => 'column c1'), $string); - $item .= $this->output_end_tag('li'); - $lis[] = $item; - $row = 1 - $row; // Flip even/odd. - } - return $this->output_tag('ul', array('class' => 'list'), implode("\n", $lis)); - } - - /** - * Output all the blocks in a particular region. - * @param string $region the name of a region on this page. - * @return string the HTML to be output. - */ - public function blocks_for_region($region) { - $blockcontents = $this->page->blocks->get_content_for_region($region, $this); - - $output = ''; - foreach ($blockcontents as $bc) { - if ($bc instanceof block_contents) { - $output .= $this->block($bc, $region); - } else if ($bc instanceof block_move_target) { - $output .= $this->block_move_target($bc); - } else { - throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.'); - } - } - return $output; - } - - /** - * Output a place where the block that is currently being moved can be dropped. - * @param block_move_target $target with the necessary details. - * @return string the HTML to be output. - */ - public function block_move_target($target) { - return $this->output_tag('a', array('href' => $target->url, 'class' => 'blockmovetarget'), - $this->output_tag('span', array('class' => 'accesshide'), $target->text)); - } - - /** - * Given a html_link object, outputs an tag that uses the object's attributes. - * - * @param mixed $link A html_link object or a string URL (text param required in second case) - * @param string $text A descriptive text for the link. If $link is a html_link, this is not required. - * @return string HTML fragment - */ - public function link($link, $text=null) { - $attributes = array(); - - if (is_a($link, 'html_link')) { - $link = clone($link); - $link->prepare(); - $this->prepare_event_handlers($link); - $attributes['href'] = prepare_url($link->url); - $attributes['class'] = $link->get_classes_string(); - $attributes['title'] = $link->title; - $attributes['id'] = $link->id; - - $text = $link->text; - - } else if (empty($text)) { - throw new coding_exception('$OUTPUT->link() must have a string as second parameter if the first param ($link) is a string'); - - } else { - $attributes['href'] = prepare_url($link); - } - - return $this->output_tag('a', $attributes, $text); - } - - /** - * Print a message along with button choices for Continue/Cancel. Labels default to Yes(Continue)/No(Cancel). - * If a string or moodle_url is given instead of a html_button, method defaults to post and text to Yes/No - * @param string $message The question to ask the user - * @param mixed $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL - * @param mixed $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL - * @return string HTML fragment - */ - public function confirm($message, $continue, $cancel) { - if ($continue instanceof html_form) { - $continue = clone($continue); - } else if (is_string($continue)) { - $continueform = new html_form(); - $continueform->url = new moodle_url($continue); - $continue = $continueform; - } else if ($continue instanceof moodle_url) { - $continueform = new html_form(); - $continueform->url = $continue; - $continue = $continueform; - } else { - throw new coding_exception('The continue param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.'); - } - - if ($cancel instanceof html_form) { - $cancel = clone($cancel); - } else if (is_string($cancel)) { - $cancelform = new html_form(); - $cancelform->url = new moodle_url($cancel); - $cancel = $cancelform; - } else if ($cancel instanceof moodle_url) { - $cancelform = new html_form(); - $cancelform->url = $cancel; - $cancel = $cancelform; - } else { - throw new coding_exception('The cancel param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.'); - } - - if (empty($continue->button->text)) { - $continue->button->text = get_string('yes'); - } - if (empty($cancel->button->text)) { - $cancel->button->text = get_string('no'); - } - - $output = $this->box_start('generalbox', 'notice'); - $output .= $this->output_tag('p', array(), $message); - $output .= $this->output_tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel)); - $output .= $this->box_end(); - return $output; - } - - /** - * Given a html_form object, outputs an tag within a form that uses the object's attributes. - * - * @param html_form $form A html_form object - * @return string HTML fragment - */ - public function button($form) { - if (empty($form->button) or !($form->button instanceof html_button)) { - throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value'); - } - $form = clone($form); - $form->button->prepare(); - - $this->prepare_event_handlers($form->button); - - $buttonattributes = array('class' => $form->button->get_classes_string(), - 'type' => 'submit', - 'value' => $form->button->text, - 'disabled' => $form->button->disabled, - 'id' => $form->button->id); - - $buttonoutput = $this->output_empty_tag('input', $buttonattributes); - - // Removing the button so it doesn't get output again - unset($form->button); - - return $this->form($form, $buttonoutput); - } - - /** - * Given a html_form component and an optional rendered submit button, - * outputs a HTML form with correct divs and inputs and a single submit button. - * This doesn't render any other visible inputs. Use moodleforms for these. - * @param html_form $form A html_form instance - * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button. - * @return string HTML fragment - */ - public function form($form, $contents=null) { - $form = clone($form); - $form->prepare(); - $this->prepare_event_handlers($form); - $buttonoutput = null; - - if (empty($contents) && !empty($form->button)) { - debugging("You probably want to use \$OUTPUT->button(\$form), please read that function's documentation", DEBUG_DEVELOPER); - } else if (empty($contents)) { - $contents = $this->output_empty_tag('input', array('type' => 'submit', 'value' => get_string('ok'))); - } else if (!empty($form->button)) { - $form->button->prepare(); - $buttonoutput = $this->output_start_tag('div', array('id' => "noscript$form->id")); - $this->prepare_event_handlers($form->button); - - $buttonattributes = array('class' => $form->button->get_classes_string(), - 'type' => 'submit', - 'value' => $form->button->text, - 'disabled' => $form->button->disabled, - 'id' => $form->button->id); - - $buttonoutput .= $this->output_empty_tag('input', $buttonattributes); - $buttonoutput .= $this->output_end_tag('div'); - $this->page->requires->js_function_call('hide_item', array("noscript$form->id")); - - } - - $hiddenoutput = ''; - - foreach ($form->url->params() as $var => $val) { - $hiddenoutput .= $this->output_empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val)); - } - - $formattributes = array( - 'method' => $form->method, - 'action' => prepare_url($form->url, true), - 'id' => $form->id, - 'class' => $form->get_classes_string()); - - $divoutput = $this->output_tag('div', array(), $hiddenoutput . $contents . $buttonoutput); - $formoutput = $this->output_tag('form', $formattributes, $divoutput); - $output = $this->output_tag('div', array('class' => 'singlebutton'), $formoutput); - - return $output; - } - - /** - * Returns a string containing a link to the user documentation. - * Also contains an icon by default. Shown to teachers and admin only. - * @param string $path The page link after doc root and language, no leading slash. - * @param string $text The text to be displayed for the link - * @param string $iconpath The path to the icon to be displayed - */ - public function doc_link($path, $text=false, $iconpath=false) { - global $CFG, $OUTPUT; - $icon = new action_icon(); - $icon->linktext = $text; - $icon->image->alt = $text; - $icon->image->add_class('iconhelp'); - $icon->link->url = new moodle_url(get_docs_url($path)); - - if (!empty($iconpath)) { - $icon->image->src = $iconpath; - } else { - $icon->image->src = $this->old_icon_url('docs'); - } - - if (!empty($CFG->doctonewwindow)) { - $icon->actions[] = new popup_action('click', $icon->link->url); - } - - return $this->action_icon($icon); - - } - - /** - * Given a action_icon object, outputs an image linking to an action (URL or AJAX). - * - * @param action_icon $icon An action_icon object - * @return string HTML fragment - */ - public function action_icon($icon) { - $icon = clone($icon); - $icon->prepare(); - $imageoutput = $this->image($icon->image); - - if ($icon->linktext) { - $imageoutput .= $icon->linktext; - } - $icon->link->text = $imageoutput; - - return $this->link($icon->link); - } - - /* - * Centered heading with attached help button (same title text) - * and optional icon attached - * @param help_icon $helpicon A help_icon object - * @param mixed $image An image URL or a html_image object - * @return string HTML fragment - */ - public function heading_with_help($helpicon, $image=false) { - if (!($image instanceof html_image) && !empty($image)) { - $htmlimage = new html_image(); - $htmlimage->src = $image; - $image = $htmlimage; - } - return $this->container($this->image($image) . $this->heading($helpicon->text, 2, 'main help') . $this->help_icon($helpicon), 'heading-with-help'); - } - - /** - * Print a help icon. - * - * @param help_icon $helpicon A help_icon object, subclass of html_link - * - * @return string HTML fragment - */ - public function help_icon($icon) { - global $COURSE; - $icon = clone($icon); - $icon->prepare(); - - $popup = new popup_action('click', $icon->link->url); - $icon->link->add_action($popup); - - $image = null; - - if (!empty($icon->image)) { - $image = $icon->image; - $image->add_class('iconhelp'); - } - - return $this->output_tag('span', array('class' => 'helplink'), $this->link_to_popup($icon->link, $image)); - } - - /** - * Creates and returns a button to a popup window - * - * @param html_link $link Subclass of moodle_html_component - * @param moodle_popup $popup A moodle_popup object - * @param html_image $image An optional image replacing the link text - * - * @return string HTML fragment - */ - public function link_to_popup($link, $image=null) { - $link = clone($link); - $link->prepare(); - - $this->prepare_event_handlers($link); - - if (empty($link->url)) { - throw new coding_exception('Called $OUTPUT->link_to_popup($link) method without $link->url set.'); - } - - $linkurl = prepare_url($link->url); - - $tagoptions = array( - 'title' => $link->title, - 'id' => $link->id, - 'href' => ($linkurl) ? $linkurl : prepare_url($popup->url), - 'class' => $link->get_classes_string()); - - // Use image if one is given - if (!empty($image) && $image instanceof html_image) { - - if (empty($image->alt)) { - $image->alt = $link->text; - } - - $link->text = $this->image($image); - - if (!empty($link->linktext)) { - $link->text = "$link->title   $link->text"; - } - } - - return $this->output_tag('a', $tagoptions, $link->text); - } - - /** - * Creates and returns a spacer image with optional line break. - * - * @param html_image $image Subclass of moodle_html_component - * - * @return string HTML fragment - */ - public function spacer($image) { - $image = clone($image); - $image->prepare(); - $image->add_class('spacer'); - - if (empty($image->src)) { - $image->src = $this->old_icon_url('spacer'); - } - - $output = $this->image($image); - - return $output; - } - - /** - * Creates and returns an image. - * - * @param html_image $image Subclass of moodle_html_component - * - * @return string HTML fragment - */ - public function image($image) { - if ($image === false) { - return false; - } - - $image = clone($image); - $image->prepare(); - - $this->prepare_event_handlers($image); - - $attributes = array('class' => $image->get_classes_string(), - 'src' => prepare_url($image->src), - 'alt' => $image->alt, - 'style' => $image->style, - 'title' => $image->title, - 'id' => $image->id); - - if (!empty($image->height) || !empty($image->width)) { - $attributes['style'] .= $this->prepare_legacy_width_and_height($image); - } - return $this->output_empty_tag('img', $attributes); - } - - /** - * Print the specified user's avatar. - * - * This method can be used in two ways: - *
-     * // Option 1:
-     * $userpic = new user_picture();
-     * // Set properties of $userpic
-     * $OUTPUT->user_picture($userpic);
-     *
-     * // Option 2: (shortcut for simple cases)
-     * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
-     * $OUTPUT->user_picture($user, $COURSE->id);
-     * 
- * - * @param object $userpic Object with at least fields id, picture, imagealt, firstname, lastname - * If any of these are missing, or if a userid is passed, the database is queried. Avoid this - * if at all possible, particularly for reports. It is very bad for performance. - * A user_picture object is a better parameter. - * @param int $courseid courseid Used when constructing the link to the user's profile. Required if $userpic - * is not a user_picture object - * @return string HTML fragment - */ - public function user_picture($userpic, $courseid=null) { - // Instantiate a user_picture object if $user is not already one - if (!($userpic instanceof user_picture)) { - if (empty($courseid)) { - throw new coding_exception('Called $OUTPUT->user_picture with a $user object but no $courseid.'); - } - - $user = $userpic; - $userpic = new user_picture(); - $userpic->user = $user; - $userpic->courseid = $courseid; - } else { - $userpic = clone($userpic); - } - - $userpic->prepare(); - - $output = $this->image($userpic->image); - - if (!empty($userpic->url)) { - $actions = $userpic->get_actions(); - if ($userpic->popup && !empty($actions)) { - $link = new html_link(); - $link->url = $userpic->url; - $link->text = fullname($userpic->user); - $link->title = fullname($userpic->user); - - foreach ($actions as $action) { - $link->add_action($action); - } - $output = $this->link_to_popup($link, $userpic->image); - } else { - $output = $this->link(prepare_url($userpic->url), $output); - } - } - - return $output; - } - - /** - * Prints the 'Update this Modulename' button that appears on module pages. - * - * @param string $cmid the course_module id. - * @param string $modulename the module name, eg. "forum", "quiz" or "workshop" - * @return string the HTML for the button, if this user has permission to edit it, else an empty string. - */ - public function update_module_button($cmid, $modulename) { - global $CFG; - if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) { - $modulename = get_string('modulename', $modulename); - $string = get_string('updatethis', '', $modulename); - - $form = new html_form(); - $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey())); - $form->button->text = $string; - return $this->button($form); - } else { - return ''; - } - } - - /** - * Prints a "Turn editing on/off" button in a form. - * @param moodle_url $url The URL + params to send through when clicking the button - * @return string HTML the button - */ - public function edit_button(moodle_url $url) { - global $USER; - if (!empty($USER->editing)) { - $string = get_string('turneditingoff'); - $edit = '0'; - } else { - $string = get_string('turneditingon'); - $edit = '1'; - } - - $form = new html_form(); - $form->url = $url; - $form->url->param('edit', $edit); - $form->button->text = $string; - - return $this->button($form); - } - - /** - * Outputs a HTML nested list - * - * @param html_list $list A html_list object - * @return string HTML structure - */ - public function htmllist($list) { - $list = clone($list); - $list->prepare(); - - $this->prepare_event_handlers($list); - - if ($list->type == 'ordered') { - $tag = 'ol'; - } else if ($list->type == 'unordered') { - $tag = 'ul'; - } - - $output = $this->output_start_tag($tag, array('class' => $list->get_classes_string())); - - foreach ($list->items as $listitem) { - if ($listitem instanceof html_list) { - $output .= $this->output_start_tag('li', array()); - $output .= $this->htmllist($listitem); - $output .= $this->output_end_tag('li'); - } else if ($listitem instanceof html_list_item) { - $listitem->prepare(); - $this->prepare_event_handlers($listitem); - $output .= $this->output_tag('li', array('class' => $listitem->get_classes_string()), $listitem->value); - } - } - - return $output . $this->output_end_tag($tag); - } - - /** - * Prints a simple button to close a window - * - * @global objec)t - * @param string $text The lang string for the button's label (already output from get_string()) - * @return string|void if $return is true, void otherwise - */ - public function close_window_button($text) { - if (empty($text)) { - $text = get_string('closewindow'); - } - $closeform = new html_form(); - $closeform->url = '#'; - $closeform->button->text = $text; - $closeform->button->add_action('click', 'close_window'); - $closeform->button->prepare(); - return $this->container($this->button($closeform), 'closewindow'); - } - - /** - * Outputs a menu, unless $multiple is true, in which case it - * will render checkboxes. - * - * To surround the menu with a form, simply set moodle_select->form as a - * valid html_form object. Note that this function will NOT automatically - * add a form for non-JS browsers. If you do not set one up, it assumes - * that you are providing your own form in some other way. - * - * You can either call this function with a single moodle_select argument - * or, with a list of parameters, in which case those parameters are sent to - * the moodle_select constructor. - * - * @param moodle_select $select a moodle_select that describes - * the select menu you want output. - * @return string the HTML for the element. Optgroups are ignored, so do not - * pass a html_select_optgroup as a param to this function. - * - * @param html_select_option $option a html_select_option - * @return string the HTML for the - */ - public function radio($option, $name='unnamed') { - if ($option instanceof html_select_optgroup) { - throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.'); - } else if (!($option instanceof html_select_option)) { - throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.'); - } - $option = clone($option); - $option->prepare(); - $option->label->for = $option->id; - $this->prepare_event_handlers($option); - - $output = $this->output_start_tag('span', array('class' => "radiogroup $select->name rb$currentradio")) . "\n"; - $output .= $this->label($option->label); - - if ($option->selected == 'selected') { - $option->selected = 'checked'; - } - - $output .= $this->output_empty_tag('input', array( - 'type' => 'radio', - 'value' => $option->value, - 'name' => $name, - 'alt' => $option->alt, - 'id' => $option->id, - 'class' => $option->get_classes_string(), - 'checked' => $option->selected)); - - $output .= $this->output_end_tag('span'); - - return $output; - } - - /** - * Outputs a element. Optgroups are ignored, so do not - * pass a html_select_optgroup as a param to this function. - * - * @param html_select_option $option a html_select_option - * @return string the HTML for the - */ - public function checkbox($option, $name='unnamed') { - if ($option instanceof html_select_optgroup) { - throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.'); - } else if (!($option instanceof html_select_option)) { - throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.'); - } - $option = clone($option); - $option->prepare(); - - $option->label->for = $option->id; - $this->prepare_event_handlers($option); - - $output = $this->output_start_tag('span', array('class' => "checkbox $name")) . "\n"; - - if ($option->selected == 'selected') { - $option->selected = 'checked'; - } - - $output .= $this->output_empty_tag('input', array( - 'type' => 'checkbox', - 'value' => $option->value, - 'name' => $name, - 'id' => $option->id, - 'alt' => $option->alt, - 'class' => $option->get_classes_string(), - 'checked' => $option->selected)); - $output .= $this->label($option->label); - - $output .= $this->output_end_tag('span'); - - return $output; - } - - /** - * Output an element. If an optgroup element is detected, - * this will recursively output its options as well. - * - * @param mixed $option a html_select_option or moodle_select_optgroup - * @return string the HTML for the - */ - public function select_option($option) { - $option = clone($option); - $option->prepare(); - $this->prepare_event_handlers($option); - - if ($option instanceof html_select_option) { - return $this->output_tag('option', array( - 'value' => $option->value, - 'class' => $option->get_classes_string(), - 'selected' => $option->selected), $option->text); - } else if ($option instanceof html_select_optgroup) { - $output = $this->output_start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string())); - foreach ($option->options as $selectoption) { - $output .= $this->select_option($selectoption); - } - $output .= $this->output_end_tag('optgroup'); - return $output; - } - } - - /** - * Output an element - * - * @param html_field $field a html_field object - * @return string the HTML for the - */ - public function textfield($field) { - $field = clone($field); - $field->prepare(); - $this->prepare_event_handlers($field); - $output = $this->output_start_tag('span', array('class' => "textfield $field->name")); - $output .= $this->output_empty_tag('input', array( - 'type' => 'text', - 'name' => $field->name, - 'id' => $field->id, - 'value' => $field->value, - 'style' => $field->style, - 'alt' => $field->alt, - 'maxlength' => $field->maxlength)); - $output .= $this->output_end_tag('span'); - return $output; - } - - /** - * Outputs a

' . - get_string('moreinformation') . '

'; - $output .= $this->box($message, 'errorbox'); - - if (debugging('', DEBUG_DEVELOPER)) { - if ($showerrordebugwarning) { - $output .= $this->notification('error() is a deprecated function. ' . - 'Please call print_error() instead of error()', 'notifytiny'); - } - if (!empty($debuginfo)) { - $output .= $this->notification($debuginfo, 'notifytiny'); - } - if (!empty($backtrace)) { - $output .= $this->notification('Stack trace: ' . - format_backtrace($backtrace), 'notifytiny'); - } - } - - if (!empty($link)) { - $output .= $this->continue_button($link); - } - - $output .= $this->footer(); - - // Padding to encourage IE to display our error page, rather than its own. - $output .= str_repeat(' ', 512); - - return $output; - } - - /** - * Output a notification (that is, a status message about something that has - * just happened). - * - * @param string $message the message to print out - * @param string $classes normally 'notifyproblem' or 'notifysuccess'. - * @return string the HTML to output. - */ - public function notification($message, $classes = 'notifyproblem') { - return $this->output_tag('div', array('class' => - moodle_renderer_base::prepare_classes($classes)), clean_text($message)); - } - - /** - * Print a continue button that goes to a particular URL. - * - * @param string|moodle_url $link The url the button goes to. - * @return string the HTML to output. - */ - public function continue_button($link) { - if (!is_a($link, 'moodle_url')) { - $link = new moodle_url($link); - } - $form = new html_form(); - $form->url = $link; - $form->values = $link->params(); - $form->button->text = get_string('continue'); - $form->method = 'get'; - - return $this->output_tag('div', array('class' => 'continuebutton') , $this->button($form)); - } - - /** - * Prints a single paging bar to provide access to other pages (usually in a search) - * - * @param string|moodle_url $link The url the button goes to. - * @return string the HTML to output. - */ - public function paging_bar($pagingbar) { - $output = ''; - $pagingbar = clone($pagingbar); - $pagingbar->prepare(); - - if ($pagingbar->totalcount > $pagingbar->perpage) { - $output .= get_string('page') . ':'; - - if (!empty($pagingbar->previouslink)) { - $output .= ' (' . $this->link($pagingbar->previouslink) . ') '; - } - - if (!empty($pagingbar->firstlink)) { - $output .= ' ' . $this->link($pagingbar->firstlink) . ' ...'; - } - - foreach ($pagingbar->pagelinks as $link) { - if ($link instanceof html_link) { - $output .= '  ' . $this->link($link); - } else { - $output .= "  $link"; - } - } - - if (!empty($pagingbar->lastlink)) { - $output .= ' ...' . $this->link($pagingbar->lastlink) . ' '; - } - - if (!empty($pagingbar->nextlink)) { - $output .= '  (' . $this->link($pagingbar->nextlink) . ')'; - } - } - - return $this->output_tag('div', array('class' => 'paging'), $output); - } - - /** - * Render a HTML table - * - * @param object $table {@link html_table} instance containing all the information needed - * @return string the HTML to output. - */ - public function table(html_table $table) { - $table = clone($table); - $table->prepare(); - $attributes = array( - 'id' => $table->id, - 'width' => $table->width, - 'summary' => $table->summary, - 'cellpadding' => $table->cellpadding, - 'cellspacing' => $table->cellspacing, - 'class' => $table->get_classes_string()); - $output = $this->output_start_tag('table', $attributes) . "\n"; - - $countcols = 0; - - if (!empty($table->head)) { - $countcols = count($table->head); - $output .= $this->output_start_tag('thead', array()) . "\n"; - $output .= $this->output_start_tag('tr', array()) . "\n"; - $keys = array_keys($table->head); - $lastkey = end($keys); - foreach ($table->head as $key => $heading) { - $classes = array('header', 'c' . $key); - if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) { - $colspan = $table->headspan[$key]; - $countcols += $table->headspan[$key] - 1; - } else { - $colspan = ''; - } - if ($key == $lastkey) { - $classes[] = 'lastcol'; - } - if (isset($table->colclasses[$key])) { - $classes[] = $table->colclasses[$key]; - } - if ($table->rotateheaders) { - // we need to wrap the heading content - $heading = $this->output_tag('span', '', $heading); - } - $attributes = array( - 'style' => $table->align[$key] . $table->size[$key] . 'white-space:nowrap;', - 'class' => moodle_renderer_base::prepare_classes($classes), - 'scope' => 'col', - 'colspan' => $colspan); - $output .= $this->output_tag('th', $attributes, $heading) . "\n"; - } - $output .= $this->output_end_tag('tr') . "\n"; - $output .= $this->output_end_tag('thead') . "\n"; - } - - if (!empty($table->data)) { - $oddeven = 1; - $keys = array_keys($table->data); - $lastrowkey = end($keys); - $output .= $this->output_start_tag('tbody', array()) . "\n"; - - foreach ($table->data as $key => $row) { - if (($row === 'hr') && ($countcols)) { - $output .= $this->output_tag('td', array('colspan' => $countcols), - $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n"; - } else { - // Convert array rows to html_table_rows and cell strings to html_table_cell objects - if (!($row instanceof html_table_row)) { - $newrow = new html_table_row(); - - foreach ($row as $unused => $item) { - $cell = new html_table_cell(); - $cell->text = $item; - $newrow->cells[] = $cell; - } - $row = $newrow; - } - - $oddeven = $oddeven ? 0 : 1; - if (isset($table->rowclasses[$key])) { - $row->add_classes(array_unique(moodle_html_component::clean_classes($table->rowclasses[$key]))); - } - - $row->add_class('r' . $oddeven); - if ($key == $lastrowkey) { - $row->add_class('lastrow'); - } - - $output .= $this->output_start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n"; - $keys2 = array_keys($row->cells); - $lastkey = end($keys2); - - foreach ($row->cells as $key => $cell) { - if (isset($table->colclasses[$key])) { - $cell->add_classes(array_unique(moodle_html_component::clean_classes($table->colclasses[$key]))); - } - - $cell->add_classes('cell'); - $cell->add_classes('c' . $key); - if ($key == $lastkey) { - $cell->add_classes('lastcol'); - } - $tdstyle = ''; - $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : ''; - $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : ''; - $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : ''; - $tdattributes = array( - 'style' => $tdstyle . $cell->style, - 'colspan' => $cell->colspan, - 'rowspan' => $cell->rowspan, - 'id' => $cell->id, - 'class' => $cell->get_classes_string(), - 'abbr' => $cell->abbr, - 'scope' => $cell->scope); - - $output .= $this->output_tag('td', $tdattributes, $cell->text) . "\n"; - } - } - $output .= $this->output_end_tag('tr') . "\n"; - } - $output .= $this->output_end_tag('tbody') . "\n"; - } - $output .= $this->output_end_tag('table') . "\n"; - - if ($table->rotateheaders && can_use_rotated_text()) { - $this->page->requires->yui_lib('event'); - $this->page->requires->js('course/report/progress/textrotate.js'); - } - - return $output; - } - - /** - * Output the place a skip link goes to. - * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call. - * @return string the HTML to output. - */ - public function skip_link_target($id = '') { - return $this->output_tag('span', array('id' => $id), ''); - } - - /** - * Outputs a heading - * @param string $text The text of the heading - * @param int $level The level of importance of the heading. Defaulting to 2 - * @param string $classes A space-separated list of CSS classes - * @param string $id An optional ID - * @return string the HTML to output. - */ - public function heading($text, $level = 2, $classes = 'main', $id = '') { - $level = (integer) $level; - if ($level < 1 or $level > 6) { - throw new coding_exception('Heading level must be an integer between 1 and 6.'); - } - return $this->output_tag('h' . $level, - array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text); - } - - /** - * Outputs a box. - * @param string $contents The contents of the box - * @param string $classes A space-separated list of CSS classes - * @param string $id An optional ID - * @return string the HTML to output. - */ - public function box($contents, $classes = 'generalbox', $id = '') { - return $this->box_start($classes, $id) . $contents . $this->box_end(); - } - - /** - * Outputs the opening section of a box. - * @param string $classes A space-separated list of CSS classes - * @param string $id An optional ID - * @return string the HTML to output. - */ - public function box_start($classes = 'generalbox', $id = '') { - $this->opencontainers->push('box', $this->output_end_tag('div')); - return $this->output_start_tag('div', array('id' => $id, - 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes))); - } - - /** - * Outputs the closing section of a box. - * @return string the HTML to output. - */ - public function box_end() { - return $this->opencontainers->pop('box'); - } - - /** - * Outputs a container. - * @param string $contents The contents of the box - * @param string $classes A space-separated list of CSS classes - * @param string $id An optional ID - * @return string the HTML to output. - */ - public function container($contents, $classes = '', $id = '') { - return $this->container_start($classes, $id) . $contents . $this->container_end(); - } - - /** - * Outputs the opening section of a container. - * @param string $classes A space-separated list of CSS classes - * @param string $id An optional ID - * @return string the HTML to output. - */ - public function container_start($classes = '', $id = '') { - $this->opencontainers->push('container', $this->output_end_tag('div')); - return $this->output_start_tag('div', array('id' => $id, - 'class' => moodle_renderer_base::prepare_classes($classes))); - } - - /** - * Outputs the closing section of a container. - * @return string the HTML to output. - */ - public function container_end() { - return $this->opencontainers->pop('container'); - } -} - -/// COMPONENTS - -/** - * Base class for classes representing HTML elements, like moodle_select. - * - * Handles the id and class attributes. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class moodle_html_component { - /** - * @var string value to use for the id attribute of this HTML tag. - */ - public $id = ''; - /** - * @var string $alt value to use for the alt attribute of this HTML tag. - */ - public $alt = ''; - /** - * @var string $style value to use for the style attribute of this HTML tag. - */ - public $style = ''; - /** - * @var array class names to add to this HTML element. - */ - public $classes = array(); - /** - * @var string $title The title attributes applicable to any XHTML element - */ - public $title = ''; - /** - * An optional array of component_action objects handling the action part of this component. - * @var array $actions - */ - protected $actions = array(); - /** - * This array of generated ids is kept static to avoid id collisions - * @var array $generated_ids - */ - public static $generated_ids = array(); - - /** - * Ensure some class names are an array. - * @param mixed $classes either an array of class names or a space-separated - * string containing class names. - * @return array the class names as an array. - */ - public static function clean_classes($classes) { - if (is_array($classes)) { - return $classes; - } else { - return explode(' ', trim($classes)); - } - } - - /** - * Set the class name array. - * @param mixed $classes either an array of class names or a space-separated - * string containing class names. - * @return void - */ - public function set_classes($classes) { - $this->classes = self::clean_classes($classes); - } - - /** - * Add a class name to the class names array. - * @param string $class the new class name to add. - * @return void - */ - public function add_class($class) { - $this->classes[] = $class; - } - - /** - * Add a whole lot of class names to the class names array. - * @param mixed $classes either an array of class names or a space-separated - * string containing class names. - * @return void - */ - public function add_classes($classes) { - $this->classes += self::clean_classes($classes); - } - - /** - * Get the class names as a string. - * @return string the class names as a space-separated string. Ready to be put in the class="" attribute. - */ - public function get_classes_string() { - return implode(' ', $this->classes); - } - - /** - * Perform any cleanup or final processing that should be done before an - * instance of this class is output. - * @return void - */ - public function prepare() { - $this->classes = array_unique(self::clean_classes($this->classes)); - } - - /** - * This checks developer do not try to assign a property directly - * if we have a setter for it. Otherwise, the property is set as expected. - * @param string $name The name of the variable to set - * @param mixed $value The value to assign to the variable - * @return void - */ - public function __set($name, $value) { - if ($name == 'class') { - debugging('this way of setting css class has been deprecated. use set_classes() method instead.'); - $this->set_classes($value); - } else { - $this->{$name} = $value; - } - } - - /** - * Adds a JS action to this component. - * Note: the JS function you write must have only two arguments: (string)event and (object|array)args - * If you want to add an instantiated component_action (or one of its subclasses), give the object as the only parameter - * - * @param mixed $event a DOM event (click, mouseover etc.) or a component_action object - * @param string $jsfunction The name of the JS function to call. required if argument 1 is a string (event) - * @param array $jsfunctionargs An optional array of JS arguments to pass to the function - */ - public function add_action($event, $jsfunction=null, $jsfunctionargs=array()) { - if (empty($this->id)) { - $this->generate_id(); - } - - if ($event instanceof component_action) { - $this->actions[] = $event; - } else { - if (empty($jsfunction)) { - throw new coding_exception('moodle_html_component::add_action requires a JS function argument if the first argument is a string event'); - } - $this->actions[] = new component_action($event, $jsfunction, $jsfunctionargs); - } - } - - /** - * Internal method for generating a unique ID for the purpose of event handlers. - */ - protected function generate_id() { - // Generate an id that is not already used. - do { - $newid = get_class($this) . '-' . substr(sha1(microtime() * rand(0, 500)), 0, 6); - } while (in_array($this->id, moodle_html_component::$generated_ids)); - $this->id = $newid; - moodle_html_component::$generated_ids[] = $newid; - } - - /** - * Returns the array of component_actions. - * @return array Component actions - */ - public function get_actions() { - return $this->actions; - } - - /** - * Adds a descriptive label to the component. - * - * This can be used in two ways: - * - *
-     * $component->set_label($elementlabel, $elementid);
-     * // OR
-     * $label = new html_label();
-     * $label->for = $elementid;
-     * $label->text = $elementlabel;
-     * $component->set_label($label);
-     * 
- * - * Use the second form when you need to add additional HTML attributes - * to the label and/or JS actions. - * - * @param mixed $text Either the text of the label or a html_label object - * @param text $for The value of the "for" attribute (the associated element's id) - * @return void - */ - public function set_label($text, $for=null) { - if ($text instanceof html_label) { - $this->label = $text; - } else if (!empty($text)) { - $this->label = new html_label(); - $this->label->for = $for; - if (empty($for) && !empty($this->id)) { - $this->label->for = $this->id; - } - $this->label->text = $text; - } - } -} - - -/** - * This class hold all the information required to describe a element. Default 0. - */ - public $tabindex = 0; - /** - * @var mixed Defaults to false, which means display the select as a dropdown menu. - * If true, display this select as a list box whose size is chosen automatically. - * If an integer, display as list box of that size. - */ - public $listbox = false; - /** - * @var integer if you are using $listbox === true to get an automatically - * sized list box, the size of the list box will be the number of options, - * or this number, whichever is smaller. - */ - public $maxautosize = 10; - /** - * @var boolean if true, allow multiple selection. Only used if $listbox is true, or if - * the select is to be output as checkboxes. - */ - public $multiple = false; - /** - * Another way to use nested menu is to prefix optgroup labels with -- and end the optgroup with -- - * Leave this setting to false if you are using the latter method. - * @var boolean $nested if true, uses $options' keys as option headings (optgroup) - */ - public $nested = false; - /** - * @var html_form $form An optional html_form component - */ - public $form; - /** - * @var help_icon $form An optional help_icon component - */ - public $helpicon; - /** - * @var boolean $rendertype How the select element should be rendered: menu or radio (checkbox is just radio + multiple) - */ - public $rendertype = 'menu'; - - /** - * @see moodle_html_component::prepare() - * @return void - */ - public function prepare() { - global $CFG; - - // name may contain [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading - if (empty($this->id)) { - $this->id = 'menu' . str_replace(array('[', ']'), '', $this->name); - } - - if (empty($this->classes)) { - $this->set_classes(array('menu' . str_replace(array('[', ']'), '', $this->name))); - } - - if (is_null($this->nothinglabel)) { - $this->nothinglabel = get_string('choosedots'); - } - - if (!empty($this->label) && !($this->label instanceof html_label)) { - $label = new html_label(); - $label->text = $this->label; - $label->for = $this->name; - $this->label = $label; - } - - $this->add_class('select'); - - $this->initialise_options(); - parent::prepare(); - } - - /** - * This is a shortcut for making a simple select menu. It lets you specify - * the options, name and selected option in one line of code. - * @param array $options used to initialise {@link $options}. - * @param string $name used to initialise {@link $name}. - * @param string $selected used to initialise {@link $selected}. - * @return moodle_select A moodle_select object with the three common fields initialised. - */ - public static function make($options, $name, $selected = '') { - $menu = new moodle_select(); - $menu->options = $options; - $menu->name = $name; - $menu->selectedvalue = $selected; - return $menu; - } - - /** - * This is a shortcut for making a yes/no select menu. - * @param string $name used to initialise {@link $name}. - * @param string $selected used to initialise {@link $selected}. - * @return moodle_select A menu initialised with yes/no options. - */ - public static function make_yes_no($name, $selected) { - return self::make(array(0 => get_string('no'), 1 => get_string('yes')), $name, $selected); - } - - /** - * 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 - * @return moodle_select A menu initialised with hour options. - */ - public static function make_time_selector($type, $name, $currenttime=0, $step=5) { - - if (!$currenttime) { - $currenttime = time(); - } - $currentdate = usergetdate($currenttime); - $userdatetype = $type; - - switch ($type) { - case 'years': - for ($i=1970; $i<=2020; $i++) { - $timeunits[$i] = $i; - } - $userdatetype = 'year'; - break; - case 'months': - for ($i=1; $i<=12; $i++) { - $timeunits[$i] = userdate(gmmktime(12,0,0,$i,15,2000), "%B"); - } - $userdatetype = 'month'; - break; - case 'days': - for ($i=1; $i<=31; $i++) { - $timeunits[$i] = $i; - } - $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 moodle_select::make_time_selector()."); - } - - $timerselector = self::make($timeunits, $name, $currentdate[$userdatetype]); - $timerselector->label = new html_label(); - $timerselector->label->text = get_string(substr($type, -1), 'form'); - $timerselector->label->for = "menu$timerselector->name"; - $timerselector->label->add_class('accesshide'); - $timerselector->nothinglabel = ''; - - return $timerselector; - } - - /** - * Given an associative array of type => fieldname and an optional timestamp, - * returns an array of moodle_select components representing date/time selectors. - * @param array $selectors Arrays of type => fieldname. Selectors will be returned in the order of the types given - * @param int $currenttime A UNIX timestamp - * @param int $step minute spacing - * @return array Instantiated date/time selectors - */ - public function make_time_selectors($selectors, $currenttime=0, $step=5) { - $selects = array(); - foreach ($selectors as $type => $name) { - $selects[] = moodle_select::make_time_selector($type, $name, $currenttime, $step); - } - return $selects; - } - - /** - * This is a shortcut for making a select popup form. - * @param mixed $baseurl The target URL, string or moodle_url - * @param string $name The variable which this select's options are changing in the URL - * @param array $options A list of value-label pairs for the popup list - * @param string $formid id for the control. Must be unique on the page. Used in the HTML. - * @param string $selected The option that is initially selected - * @return moodle_select A menu initialised as a popup form. - */ - public function make_popup_form($baseurl, $name, $options, $formid, $selected=null) { - global $CFG; - - $selectedurl = null; - - if (!($baseurl instanceof moodle_url)) { - $baseurl = new moodle_url($baseurl); - } - - if (!empty($selected)) { - $selectedurl = $baseurl->out(false, array($name => $selected), false); - } - - if (!($baseurl instanceof moodle_url)) { - $baseurl = new moodle_url($baseurl); - } - - // Replace real value by formatted URLs - foreach ($options as $value => $label) { - $options[$baseurl->out(false, array($name => $value), false)] = $label; - unset($options[$value]); - } - - $select = self::make($options, 'jump', $selectedurl); - - $select->form = new html_form(); - $select->form->id = $formid; - $select->form->method = 'get'; - $select->form->add_class('popupform'); - $select->form->url = new moodle_url($CFG->wwwroot . '/course/jumpto.php', array('sesskey' => sesskey())); - $select->form->button->text = get_string('go'); - - $select->id = $formid . '_jump'; - - $select->add_action('change', 'submit_form_by_id', array('id' => $formid, 'selectid' => $select->id)); - - return $select; - } - - /** - * Override the URLs of the default popup_form, which only supports one base URL - * @param array $options value=>label pairs representing select options - * @return void - */ - public function override_option_values($options) { - global $PAGE; - - $this->initialise_options(); - - reset($options); - - foreach ($this->options as $optkey => $optgroup) { - if ($optgroup instanceof html_select_optgroup) { - foreach ($optgroup->options as $key => $option) { - next($options); - $this->options[$optkey]->options[$key]->value = key($options); - - $optionurl = new moodle_url(key($options)); - - if ($optionurl->compare($PAGE->url, URL_MATCH_PARAMS)) { - $this->options[$optkey]->options[$key]->selected = 'selected'; - } - } - next($options); - } else if ($optgroup instanceof html_select_option) { - next($options); - $this->options[$optkey]->value = key($options); - $optionurl = new moodle_url(key($options)); - - if ($optionurl->compare($PAGE->url, URL_MATCH_PARAMS)) { - $this->options[$optkey]->selected = 'selected'; - } - } - } - } - - /** - * Adds a help icon next to the select menu. - * - * This can be used in two ways: - * - *
-     * $select->set_help_icon($page, $text, $linktext);
-     * // OR
-     * $helpicon = new help_icon();
-     * $helpicon->page = $page;
-     * $helpicon->text = $text;
-     * $helpicon->linktext = $linktext;
-     * $select->set_help_icon($helpicon);
-     * 
- * - * Use the second form when you need to add additional HTML attributes - * to the label and/or JS actions. - * - * @param mixed $page Either the keyword that defines a help page or a help_icon object - * @param text $text The text of the help icon - * @param boolean $linktext Whether or not to show text next to the icon - * @return void - */ - public function set_help_icon($page, $text, $linktext=false) { - if ($page instanceof help_icon) { - $this->helpicon = $page; - } else if (!empty($page)) { - $this->helpicon = new help_icon(); - $this->helpicon->page = $page; - $this->helpicon->text = $text; - $this->helpicon->linktext = $linktext; - } - } - - /** - * Parses the $options array and instantiates html_select_option objects in - * the place of the original value => label pairs. This is useful for when you - * need to setup extra html attributes and actions on individual options before - * the component is sent to the renderer - * @return void; - */ - public function initialise_options() { - // If options are already instantiated objects, stop here - $firstoption = reset($this->options); - if ($firstoption instanceof html_select_option || $firstoption instanceof html_select_optgroup) { - return; - } - - if ($this->rendertype == 'radio' && $this->multiple) { - $this->rendertype = 'checkbox'; - } - - // If nested is on, or if radio/checkbox rendertype is set, remove the default Choose option - if ($this->nested || $this->rendertype == 'radio' || $this->rendertype == 'checkbox') { - $this->nothinglabel = ''; - } - - $options = $this->options; - - $this->options = array(); - - if ($this->nested && $this->rendertype != 'menu') { - throw new coding_exception('moodle_select cannot render nested options as radio buttons or checkboxes.'); - } else if ($this->nested) { - foreach ($options as $section => $values) { - $optgroup = new html_select_optgroup(); - $optgroup->text = $section; - - foreach ($values as $value => $display) { - $option = new html_select_option(); - $option->value = s($value); - $option->text = $display; - if ($display === '') { - $option->text = $value; - } - - if ((string) $value == (string) $this->selectedvalue || - (is_array($this->selectedvalue) && in_array($value, $this->selectedvalue))) { - $option->selected = 'selected'; - } - - $optgroup->options[] = $option; - } - - $this->options[] = $optgroup; - } - } else { - $inoptgroup = false; - $optgroup = false; - - foreach ($options as $value => $display) { - if ($display == '--') { /// we are ending previous optgroup - // $this->options[] = $optgroup; - $inoptgroup = false; - continue; - } else if (substr($display,0,2) == '--') { /// we are starting a new optgroup - if (!empty($optgroup->options)) { - $this->options[] = $optgroup; - } - - $optgroup = new html_select_optgroup(); - $optgroup->text = substr($display,2); // stripping the -- - - $inoptgroup = true; /// everything following will be in an optgroup - continue; - - } else { - // Add $nothing option if there are not optgroups - if ($this->nothinglabel && empty($this->options[0]) && !$inoptgroup) { - $nothingoption = new html_select_option(); - $nothingoption->value = 0; - if (!empty($this->nothingvalue)) { - $nothingoption->value = $this->nothingvalue; - } - $nothingoption->text = $this->nothinglabel; - $this->options = array($nothingoption) + $this->options; - } - - $option = new html_select_option(); - $option->text = $display; - - if ($display === '') { - $option->text = $value; - } - - if ((string) $value == (string) $this->selectedvalue || - (is_array($this->selectedvalue) && in_array($value, $this->selectedvalue))) { - $option->selected = 'selected'; - } - - $option->value = s($value); - - if ($inoptgroup) { - $optgroup->options[] = $option; - } else { - $this->options[] = $option; - } - } - } - - if ($optgroup) { - $this->options[] = $optgroup; - } - } - } -} - -/** - * This class represents a label element - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_label extends moodle_html_component { - /** - * @var string $text The text to display in the label - */ - public $text; - /** - * @var string $for The name of the form field this label is associated with - */ - public $for; - - /** - * @see moodle_html_component::prepare() - * @return void - */ - public function prepare() { - if (empty($this->text)) { - throw new coding_exception('html_label must have a $text value.'); - } - parent::prepare(); - } -} - -/** - * This class represents a select option element - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_select_option extends moodle_html_component { - /** - * @var string $value The value of this option (will be sent with form) - */ - public $value; - /** - * @var string $text The display value of the option - */ - public $text; - /** - * @var boolean $selected Whether or not this option is selected - */ - public $selected = false; - /** - * @var mixed $label The label for that component. String or html_label object - */ - public $label; - - public function __construct() { - $this->label = new html_label(); - } - - /** - * @see moodle_html_component::prepare() - * @return void - */ - public function prepare() { - if (empty($this->text)) { - throw new coding_exception('html_select_option requires a $text value.'); - } - - if (empty($this->label->text)) { - $this->set_label($this->text); - } else if (!($this->label instanceof html_label)) { - $this->set_label($this->label); - } - if (empty($this->id)) { - $this->generate_id(); - } - - parent::prepare(); - } - - /** - * Shortcut for making a checkbox-ready option - * @param string $value The value of the checkbox - * @param boolean $checked - * @param string $label - * @param string $alt - * @return html_select_option A component ready for $OUTPUT->checkbox() - */ - public function make_checkbox($value, $checked, $label='', $alt='') { - $checkbox = new html_select_option(); - $checkbox->value = $value; - $checkbox->selected = $checked; - $checkbox->text = $label; - $checkbox->label->text = $label; - $checkbox->alt = $alt; - return $checkbox; - } -} - -/** - * This class represents a select optgroup element - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_select_optgroup extends moodle_html_component { - /** - * @var string $text The display value of the optgroup - */ - public $text; - /** - * @var array $options An array of html_select_option objects - */ - public $options = array(); - - public function prepare() { - if (empty($this->text)) { - throw new coding_exception('html_select_optgroup requires a $text value.'); - } - if (empty($this->options)) { - throw new coding_exception('html_select_optgroup requires at least one html_select_option object'); - } - parent::prepare(); - } -} - -/** - * This class represents an input field - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_field extends moodle_html_component { - /** - * @var string $name The name attribute of the field - */ - public $name; - /** - * @var string $value The value attribute of the field - */ - public $value; - /** - * @var string $type The type attribute of the field (text, submit, checkbox etc) - */ - public $type; - /** - * @var string $maxlength The maxlength attribute of the field (only applies to text type) - */ - public $maxlength; - /** - * @var mixed $label The label for that component. String or html_label object - */ - public $label; - - public function __construct() { - $this->label = new html_label(); - } - - /** - * @see moodle_html_component::prepare() - * @return void - */ - public function prepare() { - if (empty($this->style)) { - $this->style = 'width: 4em;'; - } - if (empty($this->id)) { - $this->generate_id(); - } - parent::prepare(); - } - - /** - * Shortcut for creating a text input component. - * @param string $name The name of the text field - * @param string $value The value of the text field - * @param string $alt The info to be inserted in the alt tag - * @param int $maxlength Sets the maxlength attribute of the field. Not set by default - * @return html_field The field component - */ - public static function make_text($name='unnamed', $value, $alt, $maxlength=0) { - $field = new html_field(); - if (empty($alt)) { - $alt = get_string('textfield'); - } - $field->type = 'text'; - $field->name = $name; - $field->value = $value; - $field->alt = $alt; - $field->maxlength = $maxlength; - return $field; - } -} - -/** - * This class represents how a block appears on a page. - * - * During output, each block instance is asked to return a block_contents object, - * those are then passed to the $OUTPUT->block function for display. - * - * {@link $contents} should probably be generated using a moodle_block_..._renderer. - * - * Other block-like things that need to appear on the page, for example the - * add new block UI, are also represented as block_contents objects. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class block_contents extends moodle_html_component { - /** @var int used to set $skipid. */ - protected static $idcounter = 1; - - const NOT_HIDEABLE = 0; - const VISIBLE = 1; - const HIDDEN = 2; - - /** - * @param integer $skipid All the blocks (or things that look like blocks) - * printed on a page are given a unique number that can be used to construct - * id="" attributes. This is set automatically be the {@link prepare()} method. - * Do not try to set it manually. - */ - public $skipid; - - /** - * @var integer If this is the contents of a real block, this should be set to - * the block_instance.id. Otherwise this should be set to 0. - */ - public $blockinstanceid = 0; - - /** - * @var integer if this is a real block instance, and there is a corresponding - * block_position.id for the block on this page, this should be set to that id. - * Otherwise it should be 0. - */ - public $blockpositionid = 0; - - /** - * @param array $attributes an array of attribute => value pairs that are put on the - * outer div of this block. {@link $id} and {@link $classes} attributes should be set separately. - */ - public $attributes = array(); - - /** - * @param string $title The title of this block. If this came from user input, - * it should already have had format_string() processing done on it. This will - * be output inside

tags. Please do not cause invalid XHTML. - */ - public $title = ''; - - /** - * @param string $content HTML for the content - */ - public $content = ''; - - /** - * @param array $list an alternative to $content, it you want a list of things with optional icons. - */ - public $footer = ''; - - /** - * Any small print that should appear under the block to explain to the - * teacher about the block, for example 'This is a sticky block that was - * added in the system context.' - * @var string - */ - public $annotation = ''; - - /** - * @var integer one of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether - * the user can toggle whether this block is visible. - */ - public $collapsible = self::NOT_HIDEABLE; - - /** - * A (possibly empty) array of editing controls. Each element of this array - * should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption). - * $icon is the icon name. Fed to $OUTPUT->old_icon_url. - * @var array - */ - public $controls = array(); - - /** - * @see moodle_html_component::prepare() - * @return void - */ - public function prepare() { - $this->skipid = self::$idcounter; - self::$idcounter += 1; - $this->add_class('sideblock'); - if (empty($this->blockinstanceid) || !strip_tags($this->title)) { - $this->collapsible = self::NOT_HIDEABLE; - } - if ($this->collapsible == self::HIDDEN) { - $this->add_class('hidden'); - } - if (!empty($this->controls)) { - $this->add_class('block_with_controls'); - } - parent::prepare(); - } -} - - -/** - * This class represents a target for where a block can go when it is being moved. - * - * This needs to be rendered as a form with the given hidden from fields, and - * clicking anywhere in the form should submit it. The form action should be - * $PAGE->url. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class block_move_target extends moodle_html_component { - /** - * List of hidden form fields. - * @var array - */ - public $url = array(); - /** - * List of hidden form fields. - * @var array - */ - public $text = ''; -} - - -/** - * Holds all the information required to render a by - * {@see moodle_core_renderer::table()} or by an overridden version of that - * method in a subclass. - * - * Example of usage: - * $t = new html_table(); - * ... // set various properties of the object $t as described below - * echo $OUTPUT->table($t); - * - * @copyright 2009 David Mudrak - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_table extends moodle_html_component { - /** - * @var array of headings. The n-th array item is used as a heading of the n-th column. - * - * Example of usage: - * $t->head = array('Student', 'Grade'); - */ - public $head; - /** - * @var array can be used to make a heading span multiple columns - * - * Example of usage: - * $t->headspan = array(2,1); - * - * In this example, {@see html_table:$data} is supposed to have three columns. For the first two columns, - * the same heading is used. Therefore, {@see html_table::$head} should consist of two items. - */ - public $headspan; - /** - * @var array of column alignments. The value is used as CSS 'text-align' property. Therefore, possible - * values are 'left', 'right', 'center' and 'justify'. Specify 'right' or 'left' from the perspective - * of a left-to-right (LTR) language. For RTL, the values are flipped automatically. - * - * Examples of usage: - * $t->align = array(null, 'right'); - * or - * $t->align[1] = 'right'; - * - */ - public $align; - /** - * @var array of column sizes. The value is used as CSS 'size' property. - * - * Examples of usage: - * $t->size = array('50%', '50%'); - * or - * $t->size[1] = '120px'; - */ - public $size; - /** - * @var array of wrapping information. The only possible value is 'nowrap' that sets the - * CSS property 'white-space' to the value 'nowrap' in the given column. - * - * Example of usage: - * $t->wrap = array(null, 'nowrap'); - */ - public $wrap; - /** - * @var array of arrays or html_table_row objects containing the data. Alternatively, if you have - * $head specified, the string 'hr' (for horizontal ruler) can be used - * instead of an array of cells data resulting in a divider rendered. - * - * Example of usage with array of arrays: - * $row1 = array('Harry Potter', '76 %'); - * $row2 = array('Hermione Granger', '100 %'); - * $t->data = array($row1, $row2); - * - * Example with array of html_table_row objects: (used for more fine-grained control) - * $cell1 = new html_table_cell(); - * $cell1->text = 'Harry Potter'; - * $cell1->colspan = 2; - * $row1 = new html_table_row(); - * $row1->cells[] = $cell1; - * $cell2 = new html_table_cell(); - * $cell2->text = 'Hermione Granger'; - * $cell3 = new html_table_cell(); - * $cell3->text = '100 %'; - * $row2 = new html_table_row(); - * $row2->cells = array($cell2, $cell3); - * $t->data = array($row1, $row2); - */ - public $data; - /** - * @var string width of the table, percentage of the page preferred. Defaults to 80% of the page width. - * @deprecated since Moodle 2.0. Styling should be in the CSS. - */ - public $width = null; - /** - * @var string alignment the whole table. Can be 'right', 'left' or 'center' (default). - * @deprecated since Moodle 2.0. Styling should be in the CSS. - */ - public $tablealign = null; - /** - * @var int padding on each cell, in pixels - * @deprecated since Moodle 2.0. Styling should be in the CSS. - */ - public $cellpadding = null; - /** - * @var int spacing between cells, in pixels - * @deprecated since Moodle 2.0. Styling should be in the CSS. - */ - public $cellspacing = null; - /** - * @var array classes to add to particular rows, space-separated string. - * Classes 'r0' or 'r1' are added automatically for every odd or even row, - * respectively. Class 'lastrow' is added automatically for the last row - * in the table. - * - * Example of usage: - * $t->rowclasses[9] = 'tenth' - */ - public $rowclasses; - /** - * @var array classes to add to every cell in a particular column, - * space-separated string. Class 'cell' is added automatically by the renderer. - * Classes 'c0' or 'c1' are added automatically for every odd or even column, - * respectively. Class 'lastcol' is added automatically for all last cells - * in a row. - * - * Example of usage: - * $t->colclasses = array(null, 'grade'); - */ - public $colclasses; - /** - * @var string description of the contents for screen readers. - */ - public $summary; - /** - * @var bool true causes the contents of the heading cells to be rotated 90 degrees. - */ - public $rotateheaders = false; - - /** - * @see moodle_html_component::prepare() - * @return void - */ - public function prepare() { - if (!empty($this->align)) { - foreach ($this->align as $key => $aa) { - if ($aa) { - $this->align[$key] = 'text-align:'. fix_align_rtl($aa) .';'; // Fix for RTL languages - } else { - $this->align[$key] = ''; - } - } - } - if (!empty($this->size)) { - foreach ($this->size as $key => $ss) { - if ($ss) { - $this->size[$key] = 'width:'. $ss .';'; - } else { - $this->size[$key] = ''; - } - } - } - if (!empty($this->wrap)) { - foreach ($this->wrap as $key => $ww) { - if ($ww) { - $this->wrap[$key] = 'white-space:nowrap;'; - } else { - $this->wrap[$key] = ''; - } - } - } - if (!empty($this->head)) { - foreach ($this->head as $key => $val) { - if (!isset($this->align[$key])) { - $this->align[$key] = ''; - } - if (!isset($this->size[$key])) { - $this->size[$key] = ''; - } - if (!isset($this->wrap[$key])) { - $this->wrap[$key] = ''; - } - - } - } - if (empty($this->classes)) { // must be done before align - $this->set_classes(array('generaltable')); - } - if (!empty($this->tablealign)) { - $this->add_class('boxalign' . $this->tablealign); - } - if (!empty($this->rotateheaders)) { - $this->add_class('rotateheaders'); - } else { - $this->rotateheaders = false; // Makes life easier later. - } - parent::prepare(); - } - /** - * @param string $name The name of the variable to set - * @param mixed $value The value to assign to the variable - * @return void - */ - public function __set($name, $value) { - if ($name == 'rowclass') { - debugging('rowclass[] has been deprecated for html_table ' . - 'and should be replaced with rowclasses[]. please fix the code.'); - $this->rowclasses = $value; - } else { - parent::__set($name, $value); - } - } -} - -/** - * Component representing a table row. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_table_row extends moodle_html_component { - /** - * @var array $cells Array of html_table_cell objects - */ - public $cells = array(); - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - parent::prepare(); - } -} - -/** - * Component representing a table cell. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_table_cell extends moodle_html_component { - /** - * @var string $text The contents of the cell - */ - public $text; - /** - * @var string $abbr Abbreviated version of the contents of the cell - */ - public $abbr = ''; - /** - * @var int $colspan Number of columns this cell should span - */ - public $colspan = ''; - /** - * @var int $rowspan Number of rows this cell should span - */ - public $rowspan = ''; - /** - * @var string $scope Defines a way to associate header cells and data cells in a table - */ - public $scope = ''; - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - parent::prepare(); - } -} - -/** - * Component representing a XHTML link. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_link extends moodle_html_component { - /** - * URL can be simple text or a moodle_url object - * @var mixed $url - */ - public $url; - - /** - * @var string $text The text that will appear between the link tags - */ - public $text; - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - // We can't accept an empty text value - if (empty($this->text)) { - throw new coding_exception('A html_link must have a descriptive text value!'); - } - - parent::prepare(); - } - - /** - * Shortcut for creating a link component. - * @param mixed $url String or moodle_url - * @param string $text The text of the link - * @return html_link The link component - */ - public function make($url, $text) { - $link = new html_link(); - $link->url = $url; - $link->text = $text; - return $link; - } -} - -/** - * Component representing a help icon. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class help_icon extends moodle_html_component { - /** - * @var html_link $link A html_link object that will hold the URL info - */ - public $link; - /** - * @var string $text A descriptive text - */ - public $text; - /** - * @var string $page The keyword that defines a help page - */ - public $page; - /** - * @var string $module Which module is the page defined in - */ - public $module = 'moodle'; - /** - * @var boolean $linktext Whether or not to show text next to the icon - */ - public $linktext = false; - /** - * @var mixed $image The help icon. Can be set to true (will use default help icon), - * false (will not use any icon), the URL to an image, or a full - * html_image object. - */ - public $image; - - /** - * Constructor: sets up the other components in case they are needed - * @return void - */ - public function __construct() { - $this->link = new html_link(); - $this->image = new html_image(); - } - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - global $COURSE, $OUTPUT; - - if (empty($this->page)) { - throw new coding_exception('A help_icon object requires a $page parameter'); - } - - if (empty($this->text)) { - throw new coding_exception('A help_icon object requires a $text parameter'); - } - - $this->link->text = $this->text; - - // fix for MDL-7734 - $this->link->url = new moodle_url('/help.php', array('module' => $this->module, 'file' => $this->page .'.html')); - - // fix for MDL-7734 - if (!empty($COURSE->lang)) { - $this->link->url->param('forcelang', $COURSE->lang); - } - - // Catch references to the old text.html and emoticons.html help files that - // were renamed in MDL-13233. - if (in_array($this->page, array('text', 'emoticons', 'richtext'))) { - $oldname = $this->page; - $this->page .= '2'; - debugging("You are referring to the old help file '$oldname'. " . - "This was renamed to '$this->page' because of MDL-13233. " . - "Please update your code.", DEBUG_DEVELOPER); - } - - if ($this->module == '') { - $this->module = 'moodle'; - } - - // Warn users about new window for Accessibility - $this->title = get_string('helpprefix2', '', trim($this->text, ". \t")) .' ('.get_string('newwindow').')'; - - // Prepare image and linktext - if ($this->image && !($this->image instanceof html_image)) { - $image = fullclone($this->image); - $this->image = new html_image(); - - if ($image instanceof moodle_url) { - $this->image->src = $image->out(); - } else if ($image === true) { - $this->image->src = $OUTPUT->old_icon_url('help'); - } else if (is_string($image)) { - $this->image->src = $image; - } - $this->image->alt = $this->text; - - if ($this->linktext) { - $this->image->alt = get_string('helpwiththis'); - } else { - $this->image->alt = $this->title; - } - $this->image->add_class('iconhelp'); - } else if (empty($this->image->src)) { - if (!($this->image instanceof html_image)) { - $this->image = new html_image(); - } - $this->image->src = $OUTPUT->old_icon_url('help'); - } - - parent::prepare(); - } - - public static function make_scale_menu($courseid, $scale) { - $helpbutton = new help_icon(); - $strscales = get_string('scales'); - $helpbutton->image->alt = $scale->name; - $helpbutton->link->url = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scale->id)); - $popupaction = new popup_action('click', $helpbutton->url, 'ratingscale', $popupparams); - $popupaction->width = 500; - $popupaction->height = 400; - $helpbutton->link->add_action($popupaction); - $helpbutton->link->title = $scale->name; - return $helpbutton; - } -} - - -/** - * Component representing a XHTML button (input of type 'button'). - * The renderer will either output it as a button with an onclick event, - * or as a form with hidden inputs. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_button extends moodle_html_component { - /** - * @var string $text - */ - public $text; - - /** - * @var boolean $disabled Whether or not this button is disabled - */ - public $disabled = false; - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - $this->add_class('singlebutton'); - - if (empty($this->text)) { - throw new coding_exception('A html_button must have a text value!'); - } - - if ($this->disabled) { - $this->disabled = 'disabled'; - } - - parent::prepare(); - } -} - -/** - * Component representing an icon linking to a Moodle page. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class action_icon extends moodle_html_component { - /** - * @var string $linktext Optional text to display next to the icon - */ - public $linktext; - /** - * @var html_image $image The icon - */ - public $image; - /** - * @var html_link $link The link - */ - public $link; - - /** - * Constructor: sets up the other components in case they are needed - * @return void - */ - public function __construct() { - $this->image = new html_image(); - $this->link = new html_link(); - } - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - $this->image->add_class('action-icon'); - - parent::prepare(); - - if (empty($this->image->src)) { - throw new coding_exception('action_icon->image->src must not be empty'); - } - - if (empty($this->image->alt) && !empty($this->linktext)) { - $this->image->alt = $this->linktext; - } else if (empty($this->image->alt)) { - debugging('action_icon->image->alt should not be empty.', DEBUG_DEVELOPER); - } - } -} - -/** - * Component representing an image. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_image extends moodle_html_component { - /** - * @var string $alt A descriptive text - */ - public $alt = HTML_ATTR_EMPTY; - /** - * @var string $src The path to the image being used - */ - public $src; - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - $this->add_class('image'); - parent::prepare(); - } -} - -/** - * Component representing a user picture. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class user_picture extends moodle_html_component { - /** - * @var mixed $user A userid or a user object with at least fields id, picture, imagealrt, firstname and lastname set. - */ - public $user; - /** - * @var int $courseid The course id. Used when constructing the link to the user's profile. - */ - public $courseid; - /** - * @var html_image $image A custom image used as the user picture. - */ - public $image; - /** - * @var mixed $url False: picture not enclosed in a link. True: default link. moodle_url: custom link. - */ - public $url; - /** - * @var int $size Size in pixels. Special values are (true/1 = 100px) and (false/0 = 35px) for backward compatibility - */ - public $size; - /** - * @var boolean $alttext add non-blank alt-text to the image. (Default true, set to false for purely - */ - public $alttext = true; - /** - * @var boolean $popup Whether or not to open the link in a popup window - */ - public $popup = false; - - /** - * Constructor: sets up the other components in case they are needed - * @return void - */ - public function __construct() { - $this->image = new html_image(); - } - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - global $CFG, $DB, $OUTPUT; - - if (empty($this->user)) { - throw new coding_exception('A user_picture object must have a $user object before being rendered.'); - } - - if (empty($this->courseid)) { - throw new coding_exception('A user_picture object must have a courseid value before being rendered.'); - } - - if (!($this->image instanceof html_image)) { - debugging('user_picture::image must be an instance of html_image', DEBUG_DEVELOPER); - } - - $needrec = false; - // only touch the DB if we are missing data... - if (is_object($this->user)) { - // Note - both picture and imagealt _can_ be empty - // what we are trying to see here is if they have been fetched - // from the DB. We should use isset() _except_ that some installs - // have those fields as nullable, and isset() will return false - // on null. The only safe thing is to ask array_key_exists() - // which works on objects. property_exists() isn't quite - // what we want here... - if (! (array_key_exists('picture', $this->user) - && ($this->alttext && array_key_exists('imagealt', $this->user) - || (isset($this->user->firstname) && isset($this->user->lastname)))) ) { - $needrec = true; - $this->user = $this->user->id; - } - } else { - if ($this->alttext) { - // we need firstname, lastname, imagealt, can't escape... - $needrec = true; - } else { - $userobj = new StdClass; // fake it to save DB traffic - $userobj->id = $this->user; - $userobj->picture = $this->image->src; - $this->user = clone($userobj); - unset($userobj); - } - } - if ($needrec) { - $this->user = $DB->get_record('user', array('id' => $this->user), 'id,firstname,lastname,imagealt'); - } - - if ($this->url === true) { - $this->url = new moodle_url('/user/view.php', array('id' => $this->user->id, 'course' => $this->courseid)); - } - - if (!empty($this->url) && $this->popup) { - $this->add_action(new popup_action('click', $this->url)); - } - - if (empty($this->size)) { - $file = 'f2'; - $this->size = 35; - } else if ($this->size === true or $this->size == 1) { - $file = 'f1'; - $this->size = 100; - } else if ($this->size >= 50) { - $file = 'f1'; - } else { - $file = 'f2'; - } - - if (!empty($this->size)) { - $this->image->width = $this->size; - $this->image->height = $this->size; - } - - $this->add_class('userpicture'); - - if (empty($this->image->src) && !empty($this->user->picture)) { - $this->image->src = $this->user->picture; - } - - if (!empty($this->image->src)) { - require_once($CFG->libdir.'/filelib.php'); - $this->image->src = new moodle_url(get_file_url($this->user->id.'/'.$file.'.jpg', null, 'user')); - } else { // Print default user pictures (use theme version if available) - $this->add_class('defaultuserpic'); - $this->image->src = $OUTPUT->old_icon_url('u/' . $file); - } - - if ($this->alttext) { - if (!empty($this->user->imagealt)) { - $this->image->alt = $this->user->imagealt; - } else { - $this->image->alt = get_string('pictureof','',fullname($this->user)); - } - } - - parent::prepare(); - } -} - -/** - * Component representing a textarea. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_textarea extends moodle_html_component { - /** - * @param string $name Name to use for the textarea element. - */ - public $name; - /** - * @param string $value Initial content to display in the textarea. - */ - public $value; - /** - * @param int $rows Number of rows to display (minimum of 10 when $height is non-null) - */ - public $rows; - /** - * @param int $cols Number of columns to display (minimum of 65 when $width is non-null) - */ - public $cols; - /** - * @param bool $usehtmleditor Enables the use of the htmleditor for this field. - */ - public $usehtmleditor; - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - $this->add_class('form-textarea'); - - if (empty($this->id)) { - $this->id = "edit-$this->name"; - } - - if ($this->usehtmleditor) { - editors_head_setup(); - $editor = get_preferred_texteditor(FORMAT_HTML); - $editor->use_editor($this->id, array('legacy'=>true)); - $this->value = htmlspecialchars($value); - } - - parent::prepare(); - } -} - -/** - * Component representing a simple form wrapper. Its purpose is mainly to enclose - * a submit input with the appropriate action and hidden inputs. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_form extends moodle_html_component { - /** - * @var string $method post or get - */ - public $method = 'post'; - /** - * If a string is given, it will be converted to a moodle_url during prepare() - * @var mixed $url A moodle_url including params or a string - */ - public $url; - /** - * @var array $params Optional array of parameters. Ignored if $url instanceof moodle_url - */ - public $params = array(); - /** - * @var boolean $showbutton If true, the submit button will always be shown even if JavaScript is available - */ - public $showbutton = false; - /** - * @var string $targetwindow The name of the target page to open the linked page in. - */ - public $targetwindow = 'self'; - /** - * @var html_button $button A submit button - */ - public $button; - - /** - * Constructor: sets up the other components in case they are needed - * @return void - */ - public function __construct() { - static $yes; - $this->button = new html_button(); - if (!isset($yes)) { - $yes = get_string('yes'); - $this->button->text = $yes; - } - } - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - - if (empty($this->url)) { - throw new coding_exception('A html_form must have a $url value (string or moodle_url).'); - } - - if (!($this->url instanceof moodle_url)) { - $this->url = new moodle_url($this->url, $this->params); - } - - if ($this->method == 'post') { - $this->url->param('sesskey', sesskey()); - } - - parent::prepare(); - } -} - -/** - * Component representing a paging bar. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class moodle_paging_bar extends moodle_html_component { - /** - * @var int $maxdisplay The maximum number of pagelinks to display - */ - public $maxdisplay = 18; - /** - * @var int $totalcount post or get - */ - public $totalcount; - /** - * @var int $page The page you are currently viewing - */ - public $page = 0; - /** - * @var int $perpage The number of entries that should be shown per page - */ - public $perpage; - /** - * @var string $baseurl If this is a string then it is the url which will be appended with $pagevar, an equals sign and the page number. - * If this is a moodle_url object then the pagevar param will be replaced by the page no, for each page. - */ - public $baseurl; - /** - * @var string $pagevar This is the variable name that you use for the page number in your code (ie. 'tablepage', 'blogpage', etc) - */ - public $pagevar = 'page'; - /** - * @var bool $nocurr do not display the current page as a link - */ - public $nocurr; - /** - * @var html_link $previouslink A HTML link representing the "previous" page - */ - public $previouslink = null; - /** - * @var html_link $nextlink A HTML link representing the "next" page - */ - public $nextlink = null; - /** - * @var html_link $firstlink A HTML link representing the first page - */ - public $firstlink = null; - /** - * @var html_link $lastlink A HTML link representing the last page - */ - public $lastlink = null; - /** - * @var array $pagelinks An array of html_links. One of them is just a string: the current page - */ - public $pagelinks = array(); - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - if (empty($this->totalcount)) { - throw new coding_exception('moodle_paging_bar requires a totalcount value.'); - } - if (!isset($this->page) || is_null($this->page)) { - throw new coding_exception('moodle_paging_bar requires a page value.'); - } - if (empty($this->perpage)) { - throw new coding_exception('moodle_paging_bar requires a perpage value.'); - } - if (empty($this->baseurl)) { - throw new coding_exception('moodle_paging_bar requires a baseurl value.'); - } - if (!($this->baseurl instanceof moodle_url)) { - $this->baseurl = new moodle_url($this->baseurl); - } - - if ($this->totalcount > $this->perpage) { - $pagenum = $this->page - 1; - - if ($this->page > 0) { - $this->previouslink = new html_link(); - $this->previouslink->add_class('previous'); - $this->previouslink->url = clone($this->baseurl); - $this->previouslink->url->param($this->pagevar, $pagenum); - $this->previouslink->text = get_string('previous'); - } - - if ($this->perpage > 0) { - $lastpage = ceil($this->totalcount / $this->perpage); - } else { - $lastpage = 1; - } - - if ($this->page > 15) { - $startpage = $this->page - 10; - - $this->firstlink = new html_link(); - $this->firstlink->url = clone($this->baseurl); - $this->firstlink->url->param($this->pagevar, 0); - $this->firstlink->text = 1; - $this->firstlink->add_class('first'); - } else { - $startpage = 0; - } - - $currpage = $startpage; - $displaycount = $displaypage = 0; - - while ($displaycount < $this->maxdisplay and $currpage < $lastpage) { - $displaypage = $currpage + 1; - - if ($this->page == $currpage && empty($this->nocurr)) { - $this->pagelinks[] = $displaypage; - } else { - $pagelink = new html_link(); - $pagelink->url = clone($this->baseurl); - $pagelink->url->param($this->pagevar, $currpage); - $pagelink->text = $displaypage; - $this->pagelinks[] = $pagelink; - } - - $displaycount++; - $currpage++; - } - - if ($currpage < $lastpage) { - $lastpageactual = $lastpage - 1; - $this->lastlink = new html_link(); - $this->lastlink->url = clone($this->baseurl); - $this->lastlink->url->param($this->pagevar, $lastpageactual); - $this->lastlink->text = $lastpage; - $this->lastlink->add_class('last'); - } - - $pagenum = $this->page + 1; - - if ($pagenum != $displaypage) { - $this->nextlink = new html_link(); - $this->nextlink->url = clone($this->baseurl); - $this->nextlink->url->param($this->pagevar, $pagenum); - $this->nextlink->text = get_string('next'); - $this->nextlink->add_class('next'); - } - } - } - - /** - * Shortcut for initialising a moodle_paging_bar with only the required params. - * - * @param int $totalcount Thetotal number of entries available to be paged through - * @param int $page The page you are currently viewing - * @param int $perpage The number of entries that should be shown per page - * @param mixed $baseurl If this is a string then it is the url which will be appended with $pagevar, an equals sign and the page number. - * If this is a moodle_url object then the pagevar param will be replaced by the page no, for each page. - */ - public function make($totalcount, $page, $perpage, $baseurl) { - $pagingbar = new moodle_paging_bar(); - $pagingbar->totalcount = $totalcount; - $pagingbar->page = $page; - $pagingbar->perpage = $perpage; - $pagingbar->baseurl = $baseurl; - return $pagingbar; - } -} - -/** - * Component representing a list. - * - * The advantage of using this object instead of a flat array is that you can load it - * with metadata (CSS classes, event handlers etc.) which can be used by the renderers. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_list extends moodle_html_component { - - /** - * @var array $items An array of html_list_item or html_list objects - */ - public $items = array(); - - /** - * @var string $type The type of list (ordered|unordered), definition type not yet supported - */ - public $type = 'unordered'; - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - parent::prepare(); - } - - /** - * This function takes a nested array of data and maps it into this list's $items array - * as proper html_list_item and html_list objects, with appropriate metadata. - * - * @param array $tree A nested array (array keys are ignored); - * @param int $row Used in identifying the iteration level and in ul classes - * @return void - */ - public function load_data($tree, $level=0) { - - $this->add_class("list-$level"); - - foreach ($tree as $key => $element) { - if (is_array($element)) { - $newhtmllist = new html_list(); - $newhtmllist->load_data($element, $level + 1); - $this->items[] = $newhtmllist; - } else { - $listitem = new html_list_item(); - $listitem->value = $element; - $listitem->add_class("list-item-$level-$key"); - $this->items[] = $listitem; - } - } - } - - /** - * Adds a html_list_item or html_list to this list. - * If the param is a string, a html_list_item will be added. - * @param mixed $item String, html_list or html_list_item object - * @return void - */ - public function add_item($item) { - if ($item instanceof html_list_item || $item instanceof html_list) { - $this->items[] = $item; - } else { - $listitem = new html_list_item(); - $listitem->value = $item; - $this->items[] = $item; - } - } -} - -/** - * Component representing a list item. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class html_list_item extends moodle_html_component { - /** - * @var string $value The value of the list item - */ - public $value; - - /** - * @see lib/moodle_html_component#prepare() - * @return void - */ - public function prepare() { - parent::prepare(); - } -} - -/// ACTIONS - -/** - * Helper class used by other components that involve an action on the page (URL or JS). - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class component_action { - - /** - * The DOM event that will trigger this action when caught - * @var string $event DOM event - */ - public $event; - - /** - * The JS function you create must have two arguments: - * 1. The event object - * 2. An object/array of arguments ($jsfunctionargs) - * @var string $jsfunction A function name to call when the button is clicked - */ - public $jsfunction = false; - - /** - * @var array $jsfunctionargs An array of arguments to pass to the JS function - */ - public $jsfunctionargs = array(); - - /** - * Constructor - * @param string $event DOM event - * @param moodle_url $url A moodle_url object, required if no jsfunction is given - * @param string $method 'post' or 'get' - * @param string $jsfunction An optional JS function. Required if jsfunctionargs is given - * @param array $jsfunctionargs An array of arguments to pass to the jsfunction - * @return void - */ - public function __construct($event, $jsfunction, $jsfunctionargs=array()) { - $this->event = $event; - - $this->jsfunction = $jsfunction; - $this->jsfunctionargs = $jsfunctionargs; - - if (!empty($this->jsfunctionargs)) { - if (empty($this->jsfunction)) { - throw new coding_exception('The component_action object needs a jsfunction value to pass the jsfunctionargs to.'); - } - } - } -} - -/** - * Component action for a popup window. - * - * @copyright 2009 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class popup_action extends component_action { - /** - * @var array $params An array of parameters that will be passed to the openpopup JS function - */ - public $params = array( - 'height' => 400, - 'width' => 500, - 'top' => 0, - 'left' => 0, - 'menubar' => false, - 'location' => false, - 'scrollbars' => true, - 'resizable' => true, - 'toolbar' => true, - 'status' => true, - 'directories' => false, - 'fullscreen' => false, - 'dependent' => true); - - /** - * Constructor - * @param string $event DOM event - * @param moodle_url $url A moodle_url object, required if no jsfunction is given - * @param string $method 'post' or 'get' - * @param array $params An array of popup parameters - * @return void - */ - public function __construct($event, $url, $name='popup', $params=array()) { - global $CFG; - $this->name = $name; - - $url = new moodle_url($url); - - if ($this->name) { - $_name = $this->name; - if (($_name = preg_replace("/\s/", '_', $_name)) != $this->name) { - throw new coding_exception('The $name of a popup window shouldn\'t contain spaces - string modified. '. $this->name .' changed to '. $_name); - $this->name = $_name; - } - } else { - $this->name = 'popup'; - } - - foreach ($this->params as $var => $val) { - if (array_key_exists($var, $params)) { - $this->params[$var] = $params[$var]; - } - } - parent::__construct($event, 'openpopup', array('url' => $url->out(false, array(), false), 'name' => $name, 'options' => $this->get_js_options($params))); - } - - /** - * Returns a string of concatenated option->value pairs used by JS to call the popup window, - * based on this object's variables - * - * @return string String of option->value pairs for JS popup function. - */ - public function get_js_options() { - $jsoptions = ''; - - foreach ($this->params as $var => $val) { - if (is_string($val) || is_int($val)) { - $jsoptions .= "$var=$val,"; - } elseif (is_bool($val)) { - $jsoptions .= ($val) ? "$var," : "$var=0,"; - } - } - - $jsoptions = substr($jsoptions, 0, strlen($jsoptions) - 1); - - return $jsoptions; - } -} - -/// RENDERERS - -/** - * A renderer that generates output for command-line scripts. - * - * The implementation of this renderer is probably incomplete. - * - * @copyright 2009 Tim Hunt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 2.0 - */ -class cli_core_renderer extends moodle_core_renderer { - /** - * Returns the page header. - * @return string HTML fragment - */ - public function header() { - output_starting_hook(); - return $this->page->heading . "\n"; - } - - /** - * Returns a template fragment representing a Heading. - * @param string $text The text of the heading - * @param int $level The level of importance of the heading - * @param string $classes A space-separated list of CSS classes - * @param string $id An optional ID - * @return string A template fragment for a heading - */ - public function heading($text, $level, $classes = 'main', $id = '') { - $text .= "\n"; - switch ($level) { - case 1: - return '=>' . $text; - case 2: - return '-->' . $text; - default: - return $text; - } - } - - /** - * Returns a template fragment representing a fatal error. - * @param string $message The message to output - * @param string $moreinfourl URL where more info can be found about the error - * @param string $link Link for the Continue button - * @param array $backtrace The execution backtrace - * @param string $debuginfo Debugging information - * @param bool $showerrordebugwarning Whether or not to show a debugging warning - * @return string A template fragment for a fatal error - */ - public function fatal_error($message, $moreinfourl, $link, $backtrace, - $debuginfo = null, $showerrordebugwarning = false) { - $output = "!!! $message !!!\n"; - - if (debugging('', DEBUG_DEVELOPER)) { - if (!empty($debuginfo)) { - $this->notification($debuginfo, 'notifytiny'); - } - if (!empty($backtrace)) { - $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny'); - } - } - } - - /** - * Returns a template fragment representing a notification. - * @param string $message The message to include - * @param string $classes A space-separated list of CSS classes - * @return string A template fragment for a notification - */ - public function notification($message, $classes = 'notifyproblem') { - $message = clean_text($message); - if ($classes === 'notifysuccess') { - return "++ $message ++\n"; - } - return "!! $message !!\n"; - } -} - - /** * Output CSS while replacing constants/variables. See MDL-6798 for details * diff --git a/lib/outputpixfinders.php b/lib/outputpixfinders.php new file mode 100644 index 00000000000..decae9b2e15 --- /dev/null +++ b/lib/outputpixfinders.php @@ -0,0 +1,221 @@ +. + +/** + * Interface and classes for icon finders. + * + * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML + * for an overview. + * + * @package moodlecore + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * An icon finder is responsible for working out the correct URL for an icon. + * + * A icon finder must also have a constructor that takes a theme object. + * (See {@link standard_icon_finder::__construct} for an example.) + * + * Note that we are planning to change the Moodle icon naming convention before + * the Moodle 2.0 release. Therefore, this API will probably change. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +interface icon_finder { + /** + * Return the URL for an icon identified as in pre-Moodle 2.0 code. + * + * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif"; + * then old_icon_url('i/course'); will return the equivalent URL that is correct now. + * + * @param string $iconname the name of the icon. + * @return string the URL for that icon. + */ + public function old_icon_url($iconname); + + /** + * Return the URL for an icon identified as in pre-Moodle 2.0 code. + * + * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif"; + * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now. + * + * @param string $iconname the name of the icon. + * @param string $module the module the icon belongs to. + * @return string the URL for that icon. + */ + public function mod_icon_url($iconname, $module); +} + +/** + * This icon finder implements the old scheme that was used when themes that had + * $THEME->custompix = false. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class pix_icon_finder implements icon_finder { + /** + * Constructor + * @param theme_config $theme the theme we are finding icons for (which is irrelevant). + */ + public function __construct($theme) { + } + + /** + * Implement interface method. + * @param string $iconname the name of the icon. + * @return string the URL for that icon. + */ + public function old_icon_url($iconname) { + global $CFG; + if (file_exists($CFG->dirroot . '/pix/' . $iconname . '.png')) { + return $CFG->httpswwwroot . '/pix/' . $iconname . '.png'; + } else { + return $CFG->httpswwwroot . '/pix/' . $iconname . '.gif'; + } + } + + /** + * Implement interface method. + * @param string $iconname the name of the icon. + * @param string $module the module the icon belongs to. + * @return string the URL for that icon. + */ + public function mod_icon_url($iconname, $module) { + global $CFG; + if (file_exists($CFG->dirroot . '/mod/' . $module . '/' . $iconname . '.png')) { + return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.png'; + } else { + return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.gif'; + } + } +} + + +/** + * This icon finder implements the old scheme that was used for themes that had + * $THEME->custompix = true. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class theme_icon_finder implements icon_finder { + protected $themename; + /** + * Constructor + * @param theme_config $theme the theme we are finding icons for. + */ + public function __construct($theme) { + $this->themename = $theme->name; + } + + /** + * Implement interface method. + * @param string $iconname the name of the icon. + * @return string the URL for that icon. + */ + public function old_icon_url($iconname) { + global $CFG; + if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/' . $iconname . '.png')) { + return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.png'; + } else { + return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.gif'; + } + } + + /** + * Implement interface method. + * @param string $iconname the name of the icon. + * @param string $module the module the icon belongs to. + * @return string the URL for that icon. + */ + public function mod_icon_url($iconname, $module) { + global $CFG; + if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png')) { + return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png'; + } else { + return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.gif'; + } + } +} + + +/** + * This icon finder implements the algorithm in pix/smartpix.php. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class smartpix_icon_finder extends pix_icon_finder { + protected $places = array(); + + /** + * Constructor + * @param theme_config $theme the theme we are finding icons for. + */ + public function __construct($theme) { + global $CFG; + $this->places[$CFG->themedir . '/' . $theme->name . '/pix/'] = + $CFG->httpsthemewww . '/' . $theme->name . '/pix/'; + if (!empty($theme->parent)) { + $this->places[$CFG->themedir . '/' . $theme->parent . '/pix/'] = + $CFG->httpsthemewww . '/' . $theme->parent . '/pix/'; + } + } + + /** + * Implement interface method. + * @param string $iconname the name of the icon. + * @return string the URL for that icon. + */ + public function old_icon_url($iconname) { + foreach ($this->places as $dirroot => $urlroot) { + if (file_exists($dirroot . $iconname . '.png')) { + return $dirroot . $iconname . '.png'; + } else if (file_exists($dirroot . $iconname . '.gif')) { + return $dirroot . $iconname . '.gif'; + } + } + return parent::old_icon_url($iconname); + } + + /** + * Implement interface method. + * @param string $iconname the name of the icon. + * @param string $module the module the icon belongs to. + * @return string the URL for that icon. + */ + public function mod_icon_url($iconname, $module) { + foreach ($this->places as $dirroot => $urlroot) { + if (file_exists($dirroot . 'mod/' . $iconname . '.png')) { + return $dirroot . 'mod/' . $iconname . '.png'; + } else if (file_exists($dirroot . 'mod/' . $iconname . '.gif')) { + return $dirroot . 'mod/' . $iconname . '.gif'; + } + } + return parent::old_icon_url($iconname, $module); + } +} + + diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php new file mode 100644 index 00000000000..c12040bce6b --- /dev/null +++ b/lib/outputrenderers.php @@ -0,0 +1,2254 @@ +. + +/** + * Classes for rendering HTML output for Moodle. + * + * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML + * for an overview. + * + * @package moodlecore + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Simple base class for Moodle renderers. + * + * Tracks the xhtml_container_stack to use, which is passed in in the constructor. + * + * Also has methods to facilitate generating HTML output. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class moodle_renderer_base { + /** @var xhtml_container_stack the xhtml_container_stack to use. */ + protected $opencontainers; + /** @var moodle_page the page we are rendering for. */ + protected $page; + + /** + * Constructor + * @param moodle_page $page the page we are doing output for. + */ + public function __construct($page) { + $this->opencontainers = $page->opencontainers; + $this->page = $page; + } + + /** + * Have we started output yet? + * @return boolean true if the header has been printed. + */ + public function has_started() { + return $this->page->state >= moodle_page::STATE_IN_BODY; + } + + /** + * Outputs a tag with attributes and contents + * @param string $tagname The name of tag ('a', 'img', 'span' etc.) + * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.) + * @param string $contents What goes between the opening and closing tags + * @return string HTML fragment + */ + protected function output_tag($tagname, $attributes, $contents) { + return $this->output_start_tag($tagname, $attributes) . $contents . + $this->output_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 + */ + protected function output_start_tag($tagname, $attributes) { + return '<' . $tagname . $this->output_attributes($attributes) . '>'; + } + + /** + * Outputs a closing tag + * @param string $tagname The name of tag ('a', 'img', 'span' etc.) + * @return string HTML fragment + */ + protected function output_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 + */ + protected function output_empty_tag($tagname, $attributes) { + return '<' . $tagname . $this->output_attributes($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 + */ + protected function output_attribute($name, $value) { + if (is_array($value)) { + debugging("Passed an array for the HTML attribute $name", DEBUG_DEVELOPER); + } + + $value = trim($value); + if ($value == HTML_ATTR_EMPTY) { + return ' ' . $name . '=""'; + } else if ($value || is_numeric($value)) { // We want 0 to be output. + 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 + */ + protected function output_attributes($attributes) { + if (empty($attributes)) { + $attributes = array(); + } + $output = ''; + foreach ($attributes as $name => $value) { + $output .= $this->output_attribute($name, $value); + } + return $output; + } + + /** + * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value + * @param mixed $classes Space-separated string or array of classes + * @return string HTML class attribute value + */ + public static function prepare_classes($classes) { + if (is_array($classes)) { + return implode(' ', array_unique($classes)); + } + return $classes; + } + + /** + * Return the URL for an icon identified as in pre-Moodle 2.0 code. + * + * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif"; + * then old_icon_url('i/course'); will return the equivalent URL that is correct now. + * + * @param string $iconname the name of the icon. + * @return string the URL for that icon. + */ + public function old_icon_url($iconname) { + return $this->page->theme->old_icon_url($iconname); + } + + /** + * Return the URL for an icon identified as in pre-Moodle 2.0 code. + * + * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif"; + * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now. + * + * @param string $iconname the name of the icon. + * @param string $module the module the icon belongs to. + * @return string the URL for that icon. + */ + public function mod_icon_url($iconname, $module) { + return $this->page->theme->mod_icon_url($iconname, $module); + } + + /** + * A helper function that takes a moodle_html_component subclass as param. + * If that component has an id attribute and an array of valid component_action objects, + * it sets up the appropriate event handlers. + * + * @param moodle_html_component $component + * @return void; + */ + protected function prepare_event_handlers(&$component) { + $actions = $component->get_actions(); + if (!empty($actions) && is_array($actions) && $actions[0] instanceof component_action) { + foreach ($actions as $action) { + if (!empty($action->jsfunction)) { + $this->page->requires->event_handler($component->id, $action->event, $action->jsfunction, $action->jsfunctionargs); + } + } + } + } + + /** + * Given a moodle_html_component with height and/or width set, translates them + * to appropriate CSS rules. + * + * @param moodle_html_component $component + * @return string CSS rules + */ + protected function prepare_legacy_width_and_height($component) { + $output = ''; + if (!empty($component->height)) { + // We need a more intelligent way to handle these warnings. If $component->height have come from + // somewhere in deprecatedlib.php, then there is no point outputting a warning here. + // debugging('Explicit height given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER); + $output .= "height: {$component->height}px;"; + } + if (!empty($component->width)) { + // debugging('Explicit width given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER); + $output .= "width: {$component->width}px;"; + } + return $output; + } +} + + +/** + * This is the templated renderer which copies the API of another class, replacing + * all methods calls with instantiation of a template. + * + * When the method method_name is called, this class will search for a template + * called method_name.php in the folders in $searchpaths, taking the first one + * that it finds. Then it will set up variables for each of the arguments of that + * method, and render the template. This is implemented in the {@link __call()} + * PHP magic method. + * + * Methods like print_box_start and print_box_end are handles specially, and + * implemented in terms of the print_box.php method. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class template_renderer extends moodle_renderer_base { + /** @var ReflectionClass information about the class whose API we are copying. */ + protected $copiedclass; + /** @var array of places to search for templates. */ + protected $searchpaths; + protected $rendererfactory; + + /** + * Magic word used when breaking apart container templates to implement + * _start and _end methods. + */ + const CONTENTSTOKEN = '-@#-Contents-go-here-#@-'; + + /** + * Constructor + * @param string $copiedclass the name of a class whose API we should be copying. + * @param array $searchpaths a list of folders to search for templates in. + * @param moodle_page $page the page we are doing output for. + */ + public function __construct($copiedclass, $searchpaths, $page) { + parent::__construct($page); + $this->copiedclass = new ReflectionClass($copiedclass); + $this->searchpaths = $searchpaths; + } + + /** + * PHP magic method implementation. Do not use this method directly. + * @param string $method The method to call + * @param array $arguments The arguments to pass to the method + * @return mixed The return value of the called method + */ + public function __call($method, $arguments) { + if (substr($method, -6) == '_start') { + return $this->process_start(substr($method, 0, -6), $arguments); + } else if (substr($method, -4) == '_end') { + return $this->process_end(substr($method, 0, -4), $arguments); + } else { + return $this->process_template($method, $arguments); + } + } + + /** + * Render the template for a given method of the renderer class we are copying, + * using the arguments passed. + * @param string $method the method that was called. + * @param array $arguments the arguments that were passed to it. + * @return string the HTML to be output. + */ + protected function process_template($method, $arguments) { + if (!$this->copiedclass->hasMethod($method) || + !$this->copiedclass->getMethod($method)->isPublic()) { + throw new coding_exception('Unknown method ' . $method); + } + + // Find the template file for this method. + $template = $this->find_template($method); + + // Use the reflection API to find out what variable names the arguments + // should be stored in, and fill in any missing ones with the defaults. + $namedarguments = array(); + $expectedparams = $this->copiedclass->getMethod($method)->getParameters(); + foreach ($expectedparams as $param) { + $paramname = $param->getName(); + if (!empty($arguments)) { + $namedarguments[$paramname] = array_shift($arguments); + } else if ($param->isDefaultValueAvailable()) { + $namedarguments[$paramname] = $param->getDefaultValue(); + } else { + throw new coding_exception('Missing required argument ' . $paramname); + } + } + + // Actually render the template. + return $this->render_template($template, $namedarguments); + } + + /** + * Actually do the work of rendering the template. + * @param string $_template the full path to the template file. + * @param array $_namedarguments an array variable name => value, the variables + * that should be available to the template. + * @return string the HTML to be output. + */ + protected function render_template($_template, $_namedarguments) { + // Note, we intentionally break the coding guidelines with regards to + // local variable names used in this function, so that they do not clash + // with the names of any variables being passed to the template. + + global $CFG, $SITE, $THEME, $USER; + // The next lines are a bit tricky. The point is, here we are in a method + // of a renderer class, and this object may, or may not, be the same as + // the global $OUTPUT object. When rendering the template, we want to use + // this object. However, people writing Moodle code expect the current + // renderer to be called $OUTPUT, not $this, so define a variable called + // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE. + $OUTPUT = $this; + $PAGE = $this->page; + $COURSE = $this->page->course; + + // And the parameters from the function call. + extract($_namedarguments); + + // Include the template, capturing the output. + ob_start(); + include($_template); + $_result = ob_get_contents(); + ob_end_clean(); + + return $_result; + } + + /** + * Searches the folders in {@link $searchpaths} to try to find a template for + * this method name. Throws an exception if one cannot be found. + * @param string $method the method name. + * @return string the full path of the template to use. + */ + protected function find_template($method) { + foreach ($this->searchpaths as $path) { + $filename = $path . '/' . $method . '.php'; + if (file_exists($filename)) { + return $filename; + } + } + throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method); + } + + /** + * Handle methods like print_box_start by using the print_box template, + * splitting the result, pushing the end onto the stack, then returning the start. + * @param string $method the method that was called, with _start stripped off. + * @param array $arguments the arguments that were passed to it. + * @return string the HTML to be output. + */ + protected function process_start($method, $arguments) { + array_unshift($arguments, self::CONTENTSTOKEN); + $html = $this->process_template($method, $arguments); + list($start, $end) = explode(self::CONTENTSTOKEN, $html, 2); + $this->opencontainers->push($method, $end); + return $start; + } + + /** + * Handle methods like print_box_end, we just need to pop the end HTML from + * the stack. + * @param string $method the method that was called, with _end stripped off. + * @param array $arguments not used. Assumed to be irrelevant. + * @return string the HTML to be output. + */ + protected function process_end($method, $arguments) { + return $this->opencontainers->pop($method); + } + + /** + * @return array the list of paths where this class searches for templates. + */ + public function get_search_paths() { + return $this->searchpaths; + } + + /** + * @return string the name of the class whose API we are copying. + */ + public function get_copied_class() { + return $this->copiedclass->getName(); + } +} + +/** + * The standard implementation of the moodle_core_renderer interface. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class moodle_core_renderer extends moodle_renderer_base { + /** @var string used in {@link header()}. */ + const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%'; + /** @var string used in {@link header()}. */ + const END_HTML_TOKEN = '%%ENDHTML%%'; + /** @var string used in {@link header()}. */ + const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]'; + /** @var string used to pass information from {@link doctype()} to {@link standard_head_html()}. */ + protected $contenttype; + /** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */ + protected $metarefreshtag = ''; + + /** + * Get the DOCTYPE declaration that should be used with this page. Designed to + * be called in theme layout.php files. + * @return string the DOCTYPE declaration (and any XML prologue) that should be used. + */ + public function doctype() { + global $CFG; + + $doctype = '' . "\n"; + $this->contenttype = 'text/html; charset=utf-8'; + + if (empty($CFG->xmlstrictheaders)) { + return $doctype; + } + + // We want to serve the page with an XML content type, to force well-formedness errors to be reported. + $prolog = '' . "\n"; + if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) { + // Firefox and other browsers that can cope natively with XHTML. + $this->contenttype = 'application/xhtml+xml; charset=utf-8'; + + } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) { + // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet. + $this->contenttype = 'application/xml; charset=utf-8'; + $prolog .= 'httpswwwroot . '/lib/xhtml.xsl"?>' . "\n"; + + } else { + $prolog = ''; + } + + return $prolog . $doctype; + } + + /** + * The attributes that should be added to the tag. Designed to + * be called in theme layout.php files. + * @return string HTML fragment. + */ + public function htmlattributes() { + return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"'; + } + + /** + * The standard tags (meta tags, links to stylesheets and JavaScript, etc.) + * that should be included in the tag. Designed to be called in theme + * layout.php files. + * @return string HTML fragment. + */ + public function standard_head_html() { + global $CFG; + $output = ''; + $output .= '' . "\n"; + $output .= '' . "\n"; + if (!$this->page->cacheable) { + $output .= '' . "\n"; + $output .= '' . "\n"; + } + // This is only set by the {@link redirect()} method + $output .= $this->metarefreshtag; + + // Check if a periodic refresh delay has been set and make sure we arn't + // already meta refreshing + if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) { + $output .= ''; + } + + $this->page->requires->js('lib/javascript-static.js')->in_head(); + $this->page->requires->js('lib/javascript-deprecated.js')->in_head(); + $this->page->requires->js('lib/javascript-mod.php')->in_head(); + $this->page->requires->js('lib/overlib/overlib.js')->in_head(); + $this->page->requires->js('lib/overlib/overlib_cssstyle.js')->in_head(); + $this->page->requires->js('lib/cookies.js')->in_head(); + $this->page->requires->js_function_call('setTimeout', Array('fix_column_widths()', 20)); + + $focus = $this->page->focuscontrol; + if (!empty($focus)) { + if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) { + // This is a horrifically bad way to handle focus but it is passed in + // through messy formslib::moodleform + $this->page->requires->js_function_call('old_onload_focus', Array($matches[1], $matches[2])); + } else if (strpos($focus, '.')!==false) { + // Old style of focus, bad way to do it + debugging('This code is using the old style focus event, Please update this code to focus on an element id or the moodleform focus method.', DEBUG_DEVELOPER); + $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2)); + } else { + // Focus element with given id + $this->page->requires->js_function_call('focuscontrol', Array($focus)); + } + } + + // Add the meta tags from the themes if any were requested. + $output .= $this->page->theme->get_meta_tags($this->page); + + // Get any HTML from the page_requirements_manager. + $output .= $this->page->requires->get_head_code(); + + // List alternate versions. + foreach ($this->page->alternateversions as $type => $alt) { + $output .= $this->output_empty_tag('link', array('rel' => 'alternate', + 'type' => $type, 'title' => $alt->title, 'href' => $alt->url)); + } + + return $output; + } + + /** + * The standard tags (typically skip links) that should be output just inside + * the start of the tag. Designed to be called in theme layout.php files. + * @return string HTML fragment. + */ + public function standard_top_of_body_html() { + return $this->page->requires->get_top_of_body_code(); + } + + /** + * The standard tags (typically performance information and validation links, + * if we are in developer debug mode) that should be output in the footer area + * of the page. Designed to be called in theme layout.php files. + * @return string HTML fragment. + */ + public function standard_footer_html() { + global $CFG; + + // This function is normally called from a layout.php file in {@link header()} + // but some of the content won't be known until later, so we return a placeholder + // for now. This will be replaced with the real content in {@link footer()}. + $output = self::PERFORMANCE_INFO_TOKEN; + if (!empty($CFG->debugpageinfo)) { + $output .= '
This page is: ' . $this->page->debug_summary() . '
'; + } + if (!empty($CFG->debugvalidators)) { + $output .= ''; + } + return $output; + } + + /** + * The standard tags (typically script tags that are not needed earlier) that + * should be output after everything else, . Designed to be called in theme layout.php files. + * @return string HTML fragment. + */ + public function standard_end_of_body_html() { + // This function is normally called from a layout.php file in {@link header()} + // but some of the content won't be known until later, so we return a placeholder + // for now. This will be replaced with the real content in {@link footer()}. + echo self::END_HTML_TOKEN; + } + + /** + * Return the standard string that says whether you are logged in (and switched + * roles/logged in as another user). + * @return string HTML fragment. + */ + public function login_info() { + global $USER; + return user_login_string($this->page->course, $USER); + } + + /** + * Return the 'back' link that normally appears in the footer. + * @return string HTML fragment. + */ + public function home_link() { + global $CFG, $SITE; + + if ($this->page->pagetype == 'site-index') { + // Special case for site home page - please do not remove + return ''; + + } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) { + // Special case for during install/upgrade. + return ''; + + } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) { + return ''; + + } else { + return ''; + } + } + + /** + * Redirects the user by any means possible given the current state + * + * This function should not be called directly, it should always be called using + * the redirect function in lib/weblib.php + * + * The redirect function should really only be called before page output has started + * however it will allow itself to be called during the state STATE_IN_BODY + * + * @param string $encodedurl The URL to send to encoded if required + * @param string $message The message to display to the user if any + * @param int $delay The delay before redirecting a user, if $message has been + * set this is a requirement and defaults to 3, set to 0 no delay + * @param boolean $debugdisableredirect this redirect has been disabled for + * debugging purposes. Display a message that explains, and don't + * trigger the redirect. + * @return string The HTML to display to the user before dying, may contain + * meta refresh, javascript refresh, and may have set header redirects + */ + public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) { + global $CFG; + $url = str_replace('&', '&', $encodedurl); + + switch ($this->page->state) { + case moodle_page::STATE_BEFORE_HEADER : + // No output yet it is safe to delivery the full arsenal of redirect methods + if (!$debugdisableredirect) { + // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time. + $this->metarefreshtag = ''."\n"; + $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3); + } + $output = $this->header(); + break; + case moodle_page::STATE_PRINTING_HEADER : + // We should hopefully never get here + throw new coding_exception('You cannot redirect while printing the page header'); + break; + case moodle_page::STATE_IN_BODY : + // We really shouldn't be here but we can deal with this + debugging("You should really redirect before you start page output"); + if (!$debugdisableredirect) { + $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay); + } + $output = $this->opencontainers->pop_all_but_last(); + break; + case moodle_page::STATE_DONE : + // Too late to be calling redirect now + throw new coding_exception('You cannot redirect after the entire page has been generated'); + break; + } + $output .= $this->notification($message, 'redirectmessage'); + $output .= ''. get_string('continue') .''; + if ($debugdisableredirect) { + $output .= '

Error output, so disabling automatic redirect.

'; + } + $output .= $this->footer(); + return $output; + } + + /** + * Start output by sending the HTTP headers, and printing the HTML + * and the start of the . + * + * To control what is printed, you should set properties on $PAGE. If you + * are familiar with the old {@link print_header()} function from Moodle 1.9 + * you will find that there are properties on $PAGE that correspond to most + * of the old parameters to could be passed to print_header. + * + * Not that, in due course, the remaining $navigation, $menu parameters here + * will be replaced by more properties of $PAGE, but that is still to do. + * + * @param string $navigation legacy, like the old parameter to print_header. Will be + * removed when there is a $PAGE->... replacement. + * @param string $menu legacy, like the old parameter to print_header. Will be + * removed when there is a $PAGE->... replacement. + * @return string HTML that you must output this, preferably immediately. + */ + public function header($navigation = '', $menu='') { + // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation + global $USER, $CFG; + + $this->page->set_state(moodle_page::STATE_PRINTING_HEADER); + + // Find the appropriate page template, based on $this->page->generaltype. + $templatefile = $this->page->theme->template_for_page($this->page->generaltype); + if ($templatefile) { + // Render the template. + $template = $this->render_page_template($templatefile, $menu, $navigation); + } else { + // New style template not found, fall back to using header.html and footer.html. + $template = $this->handle_legacy_theme($navigation, $menu); + } + + // Slice the template output into header and footer. + $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN); + if ($cutpos === false) { + throw new coding_exception('Layout template ' . $templatefile . + ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".'); + } + $header = substr($template, 0, $cutpos); + $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN)); + + if (empty($this->contenttype)) { + debugging('The layout template did not call $OUTPUT->doctype()'); + $this->doctype(); + } + + send_headers($this->contenttype, $this->page->cacheable); + $this->opencontainers->push('header/footer', $footer); + $this->page->set_state(moodle_page::STATE_IN_BODY); + return $header . $this->skip_link_target(); + } + + /** + * Renders and outputs the page template. + * @param string $templatefile The name of the template's file + * @param array $menu The menu that will be used in the included file + * @param array $navigation The navigation that will be used in the included file + * @return string HTML code + */ + protected function render_page_template($templatefile, $menu, $navigation) { + global $CFG, $SITE, $THEME, $USER; + // The next lines are a bit tricky. The point is, here we are in a method + // of a renderer class, and this object may, or may not, be the same as + // the global $OUTPUT object. When rendering the template, we want to use + // this object. However, people writing Moodle code expect the current + // renderer to be called $OUTPUT, not $this, so define a variable called + // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE. + $OUTPUT = $this; + $PAGE = $this->page; + $COURSE = $this->page->course; + + ob_start(); + include($templatefile); + $template = ob_get_contents(); + ob_end_clean(); + return $template; + } + + /** + * Renders and outputs a legacy template. + * @param array $navigation The navigation that will be used in the included file + * @param array $menu The menu that will be used in the included file + * @return string HTML code + */ + protected function handle_legacy_theme($navigation, $menu) { + global $CFG, $SITE, $USER; + // Set a pretend global from the properties of this class. + // See the comment in render_page_template for a fuller explanation. + $COURSE = $this->page->course; + $THEME = $this->page->theme; + + // Set up local variables that header.html expects. + $direction = $this->htmlattributes(); + $title = $this->page->title; + $heading = $this->page->heading; + $focus = $this->page->focuscontrol; + $button = $this->page->button; + $pageid = $this->page->pagetype; + $pageclass = $this->page->bodyclasses; + $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"'; + $home = $this->page->generaltype == 'home'; + + $meta = $this->standard_head_html(); + // The next line is a nasty hack. having set $meta to standard_head_html, we have already + // got the contents of include($CFG->javascript). However, legacy themes are going to + // include($CFG->javascript) again. We want to make sure that when they do, nothing is output. + $CFG->javascript = $CFG->libdir . '/emptyfile.php'; + + // Set up local variables that footer.html expects. + $homelink = $this->home_link(); + $loggedinas = $this->login_info(); + $course = $this->page->course; + $performanceinfo = self::PERFORMANCE_INFO_TOKEN; + + if (!$menu && $navigation) { + $menu = $loggedinas; + } + + if (!empty($this->page->theme->layouttable)) { + $lt = $this->page->theme->layouttable; + } else { + $lt = array('left', 'middle', 'right'); + } + + if (!empty($this->page->theme->block_l_max_width)) { + $preferredwidthleft = $this->page->theme->block_l_max_width; + } else { + $preferredwidthleft = 210; + } + if (!empty($this->page->theme->block_r_max_width)) { + $preferredwidthright = $this->page->theme->block_r_max_width; + } else { + $preferredwidthright = 210; + } + + ob_start(); + include($this->page->theme->dir . '/header.html'); + + echo '
'; + foreach ($lt as $column) { + if ($column == 'left' && $this->page->blocks->region_has_content(BLOCK_POS_LEFT, $this)) { + echo ''; + + } else if ($column == 'middle') { + echo ''; + + } else if ($column == 'right' && $this->page->blocks->region_has_content(BLOCK_POS_RIGHT, $this)) { + echo ''; + } + } + echo '
'; + echo $this->container_start(); + echo $this->blocks_for_region(BLOCK_POS_LEFT); + echo $this->container_end(); + echo ''; + echo $this->container_start(); + echo $this->skip_link_target(); + echo self::MAIN_CONTENT_TOKEN; + echo $this->container_end(); + echo ''; + echo $this->container_start(); + echo $this->blocks_for_region(BLOCK_POS_RIGHT); + echo $this->container_end(); + echo '
'; + + $menu = str_replace('navmenu', 'navmenufooter', $menu); + include($THEME->dir . '/footer.html'); + + $output = ob_get_contents(); + ob_end_clean(); + + // Put in the start of body code. Bit of a hack, put it in before the first + //
standard_top_of_body_html() . + substr($output, $divpos); + + // Put in the end token before the end of body. + $output = str_replace('', self::END_HTML_TOKEN . '', $output); + + // Make sure we use the correct doctype. + $output = preg_replace('/()/s', $this->doctype(), $output); + + return $output; + } + + /** + * Outputs the page's footer + * @return string HTML fragment + */ + public function footer() { + $output = $this->opencontainers->pop_all_but_last(true); + + $footer = $this->opencontainers->pop('header/footer'); + + // Provide some performance info if required + $performanceinfo = ''; + if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { + $perf = get_performance_info(); + if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) { + error_log("PERF: " . $perf['txt']); + } + if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) { + $performanceinfo = $perf['html']; + } + } + $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer); + + $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer); + + $this->page->set_state(moodle_page::STATE_DONE); + + + return $output . $footer; + } + + /** + * Output the row of editing icons for a block, as defined by the controls array. + * @param array $controls an array like {@link block_contents::$controls}. + * @return HTML fragment. + */ + public function block_controls($controls) { + if (empty($controls)) { + return ''; + } + $controlshtml = array(); + foreach ($controls as $control) { + $controlshtml[] = $this->output_tag('a', array('class' => 'icon', + 'title' => $control['caption'], 'href' => $control['url']), + $this->output_empty_tag('img', array('src' => $this->old_icon_url($control['icon']), + 'alt' => $control['caption']))); + } + return $this->output_tag('div', array('class' => 'commands'), implode('', $controlshtml)); + } + + /** + * Prints a nice side block with an optional header. + * + * The content is described + * by a {@link block_contents} object. + * + * @param block_contents $bc HTML for the content + * @param string $region the region the block is appearing in. + * @return string the HTML to be output. + */ + function block($bc, $region) { + $bc = clone($bc); // Avoid messing up the object passed in. + $bc->prepare(); + + $skiptitle = strip_tags($bc->title); + if (empty($skiptitle)) { + $output = ''; + $skipdest = ''; + } else { + $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'), + get_string('skipa', 'access', $skiptitle)); + $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), ''); + } + + $bc->attributes['id'] = $bc->id; + $bc->attributes['class'] = $bc->get_classes_string(); + $output .= $this->output_start_tag('div', $bc->attributes); + + $controlshtml = $this->block_controls($bc->controls); + + $title = ''; + if ($bc->title) { + $title = $this->output_tag('h2', null, $bc->title); + } + + if ($title || $controlshtml) { + $output .= $this->output_tag('div', array('class' => 'header'), + $this->output_tag('div', array('class' => 'title'), + $title . $controlshtml)); + } + + $output .= $this->output_start_tag('div', array('class' => 'content')); + $output .= $bc->content; + + if ($bc->footer) { + $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer); + } + + $output .= $this->output_end_tag('div'); + $output .= $this->output_end_tag('div'); + + if ($bc->annotation) { + $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation); + } + $output .= $skipdest; + + $this->init_block_hider_js($bc); + return $output; + } + + /** + * Calls the JS require function to hide a block. + * @param block_contents $bc A block_contents object + * @return void + */ + protected function init_block_hider_js($bc) { + if ($bc->collapsible != block_contents::NOT_HIDEABLE) { + $userpref = 'block' . $bc->blockinstanceid . 'hidden'; + user_preference_allow_ajax_update($userpref, PARAM_BOOL); + $this->page->requires->yui_lib('dom'); + $this->page->requires->yui_lib('event'); + $plaintitle = strip_tags($bc->title); + $this->page->requires->js_function_call('new block_hider', array($bc->id, $userpref, + get_string('hideblocka', 'access', $plaintitle), get_string('showblocka', 'access', $plaintitle), + $this->old_icon_url('t/switch_minus'), $this->old_icon_url('t/switch_plus'))); + } + } + + /** + * Render the contents of a block_list. + * @param array $icons the icon for each item. + * @param array $items the content of each item. + * @return string HTML + */ + public function list_block_contents($icons, $items) { + $row = 0; + $lis = array(); + foreach ($items as $key => $string) { + $item = $this->output_start_tag('li', array('class' => 'r' . $row)); + if ($icons) { + $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]); + } + $item .= $this->output_tag('div', array('class' => 'column c1'), $string); + $item .= $this->output_end_tag('li'); + $lis[] = $item; + $row = 1 - $row; // Flip even/odd. + } + return $this->output_tag('ul', array('class' => 'list'), implode("\n", $lis)); + } + + /** + * Output all the blocks in a particular region. + * @param string $region the name of a region on this page. + * @return string the HTML to be output. + */ + public function blocks_for_region($region) { + $blockcontents = $this->page->blocks->get_content_for_region($region, $this); + + $output = ''; + foreach ($blockcontents as $bc) { + if ($bc instanceof block_contents) { + $output .= $this->block($bc, $region); + } else if ($bc instanceof block_move_target) { + $output .= $this->block_move_target($bc); + } else { + throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.'); + } + } + return $output; + } + + /** + * Output a place where the block that is currently being moved can be dropped. + * @param block_move_target $target with the necessary details. + * @return string the HTML to be output. + */ + public function block_move_target($target) { + return $this->output_tag('a', array('href' => $target->url, 'class' => 'blockmovetarget'), + $this->output_tag('span', array('class' => 'accesshide'), $target->text)); + } + + /** + * Given a html_link object, outputs an tag that uses the object's attributes. + * + * @param mixed $link A html_link object or a string URL (text param required in second case) + * @param string $text A descriptive text for the link. If $link is a html_link, this is not required. + * @return string HTML fragment + */ + public function link($link, $text=null) { + $attributes = array(); + + if (is_a($link, 'html_link')) { + $link = clone($link); + $link->prepare(); + $this->prepare_event_handlers($link); + $attributes['href'] = prepare_url($link->url); + $attributes['class'] = $link->get_classes_string(); + $attributes['title'] = $link->title; + $attributes['id'] = $link->id; + + $text = $link->text; + + } else if (empty($text)) { + throw new coding_exception('$OUTPUT->link() must have a string as second parameter if the first param ($link) is a string'); + + } else { + $attributes['href'] = prepare_url($link); + } + + return $this->output_tag('a', $attributes, $text); + } + + /** + * Print a message along with button choices for Continue/Cancel. Labels default to Yes(Continue)/No(Cancel). + * If a string or moodle_url is given instead of a html_button, method defaults to post and text to Yes/No + * @param string $message The question to ask the user + * @param mixed $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL + * @param mixed $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL + * @return string HTML fragment + */ + public function confirm($message, $continue, $cancel) { + if ($continue instanceof html_form) { + $continue = clone($continue); + } else if (is_string($continue)) { + $continueform = new html_form(); + $continueform->url = new moodle_url($continue); + $continue = $continueform; + } else if ($continue instanceof moodle_url) { + $continueform = new html_form(); + $continueform->url = $continue; + $continue = $continueform; + } else { + throw new coding_exception('The continue param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.'); + } + + if ($cancel instanceof html_form) { + $cancel = clone($cancel); + } else if (is_string($cancel)) { + $cancelform = new html_form(); + $cancelform->url = new moodle_url($cancel); + $cancel = $cancelform; + } else if ($cancel instanceof moodle_url) { + $cancelform = new html_form(); + $cancelform->url = $cancel; + $cancel = $cancelform; + } else { + throw new coding_exception('The cancel param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.'); + } + + if (empty($continue->button->text)) { + $continue->button->text = get_string('yes'); + } + if (empty($cancel->button->text)) { + $cancel->button->text = get_string('no'); + } + + $output = $this->box_start('generalbox', 'notice'); + $output .= $this->output_tag('p', array(), $message); + $output .= $this->output_tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel)); + $output .= $this->box_end(); + return $output; + } + + /** + * Given a html_form object, outputs an tag within a form that uses the object's attributes. + * + * @param html_form $form A html_form object + * @return string HTML fragment + */ + public function button($form) { + if (empty($form->button) or !($form->button instanceof html_button)) { + throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value'); + } + $form = clone($form); + $form->button->prepare(); + + $this->prepare_event_handlers($form->button); + + $buttonattributes = array('class' => $form->button->get_classes_string(), + 'type' => 'submit', + 'value' => $form->button->text, + 'disabled' => $form->button->disabled, + 'id' => $form->button->id); + + $buttonoutput = $this->output_empty_tag('input', $buttonattributes); + + // Removing the button so it doesn't get output again + unset($form->button); + + return $this->form($form, $buttonoutput); + } + + /** + * Given a html_form component and an optional rendered submit button, + * outputs a HTML form with correct divs and inputs and a single submit button. + * This doesn't render any other visible inputs. Use moodleforms for these. + * @param html_form $form A html_form instance + * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button. + * @return string HTML fragment + */ + public function form($form, $contents=null) { + $form = clone($form); + $form->prepare(); + $this->prepare_event_handlers($form); + $buttonoutput = null; + + if (empty($contents) && !empty($form->button)) { + debugging("You probably want to use \$OUTPUT->button(\$form), please read that function's documentation", DEBUG_DEVELOPER); + } else if (empty($contents)) { + $contents = $this->output_empty_tag('input', array('type' => 'submit', 'value' => get_string('ok'))); + } else if (!empty($form->button)) { + $form->button->prepare(); + $buttonoutput = $this->output_start_tag('div', array('id' => "noscript$form->id")); + $this->prepare_event_handlers($form->button); + + $buttonattributes = array('class' => $form->button->get_classes_string(), + 'type' => 'submit', + 'value' => $form->button->text, + 'disabled' => $form->button->disabled, + 'id' => $form->button->id); + + $buttonoutput .= $this->output_empty_tag('input', $buttonattributes); + $buttonoutput .= $this->output_end_tag('div'); + $this->page->requires->js_function_call('hide_item', array("noscript$form->id")); + + } + + $hiddenoutput = ''; + + foreach ($form->url->params() as $var => $val) { + $hiddenoutput .= $this->output_empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val)); + } + + $formattributes = array( + 'method' => $form->method, + 'action' => prepare_url($form->url, true), + 'id' => $form->id, + 'class' => $form->get_classes_string()); + + $divoutput = $this->output_tag('div', array(), $hiddenoutput . $contents . $buttonoutput); + $formoutput = $this->output_tag('form', $formattributes, $divoutput); + $output = $this->output_tag('div', array('class' => 'singlebutton'), $formoutput); + + return $output; + } + + /** + * Returns a string containing a link to the user documentation. + * Also contains an icon by default. Shown to teachers and admin only. + * @param string $path The page link after doc root and language, no leading slash. + * @param string $text The text to be displayed for the link + * @param string $iconpath The path to the icon to be displayed + */ + public function doc_link($path, $text=false, $iconpath=false) { + global $CFG, $OUTPUT; + $icon = new action_icon(); + $icon->linktext = $text; + $icon->image->alt = $text; + $icon->image->add_class('iconhelp'); + $icon->link->url = new moodle_url(get_docs_url($path)); + + if (!empty($iconpath)) { + $icon->image->src = $iconpath; + } else { + $icon->image->src = $this->old_icon_url('docs'); + } + + if (!empty($CFG->doctonewwindow)) { + $icon->actions[] = new popup_action('click', $icon->link->url); + } + + return $this->action_icon($icon); + + } + + /** + * Given a action_icon object, outputs an image linking to an action (URL or AJAX). + * + * @param action_icon $icon An action_icon object + * @return string HTML fragment + */ + public function action_icon($icon) { + $icon = clone($icon); + $icon->prepare(); + $imageoutput = $this->image($icon->image); + + if ($icon->linktext) { + $imageoutput .= $icon->linktext; + } + $icon->link->text = $imageoutput; + + return $this->link($icon->link); + } + + /* + * Centered heading with attached help button (same title text) + * and optional icon attached + * @param help_icon $helpicon A help_icon object + * @param mixed $image An image URL or a html_image object + * @return string HTML fragment + */ + public function heading_with_help($helpicon, $image=false) { + if (!($image instanceof html_image) && !empty($image)) { + $htmlimage = new html_image(); + $htmlimage->src = $image; + $image = $htmlimage; + } + return $this->container($this->image($image) . $this->heading($helpicon->text, 2, 'main help') . $this->help_icon($helpicon), 'heading-with-help'); + } + + /** + * Print a help icon. + * + * @param help_icon $helpicon A help_icon object, subclass of html_link + * + * @return string HTML fragment + */ + public function help_icon($icon) { + global $COURSE; + $icon = clone($icon); + $icon->prepare(); + + $popup = new popup_action('click', $icon->link->url); + $icon->link->add_action($popup); + + $image = null; + + if (!empty($icon->image)) { + $image = $icon->image; + $image->add_class('iconhelp'); + } + + return $this->output_tag('span', array('class' => 'helplink'), $this->link_to_popup($icon->link, $image)); + } + + /** + * Creates and returns a button to a popup window + * + * @param html_link $link Subclass of moodle_html_component + * @param moodle_popup $popup A moodle_popup object + * @param html_image $image An optional image replacing the link text + * + * @return string HTML fragment + */ + public function link_to_popup($link, $image=null) { + $link = clone($link); + $link->prepare(); + + $this->prepare_event_handlers($link); + + if (empty($link->url)) { + throw new coding_exception('Called $OUTPUT->link_to_popup($link) method without $link->url set.'); + } + + $linkurl = prepare_url($link->url); + + $tagoptions = array( + 'title' => $link->title, + 'id' => $link->id, + 'href' => ($linkurl) ? $linkurl : prepare_url($popup->url), + 'class' => $link->get_classes_string()); + + // Use image if one is given + if (!empty($image) && $image instanceof html_image) { + + if (empty($image->alt)) { + $image->alt = $link->text; + } + + $link->text = $this->image($image); + + if (!empty($link->linktext)) { + $link->text = "$link->title   $link->text"; + } + } + + return $this->output_tag('a', $tagoptions, $link->text); + } + + /** + * Creates and returns a spacer image with optional line break. + * + * @param html_image $image Subclass of moodle_html_component + * + * @return string HTML fragment + */ + public function spacer($image) { + $image = clone($image); + $image->prepare(); + $image->add_class('spacer'); + + if (empty($image->src)) { + $image->src = $this->old_icon_url('spacer'); + } + + $output = $this->image($image); + + return $output; + } + + /** + * Creates and returns an image. + * + * @param html_image $image Subclass of moodle_html_component + * + * @return string HTML fragment + */ + public function image($image) { + if ($image === false) { + return false; + } + + $image = clone($image); + $image->prepare(); + + $this->prepare_event_handlers($image); + + $attributes = array('class' => $image->get_classes_string(), + 'src' => prepare_url($image->src), + 'alt' => $image->alt, + 'style' => $image->style, + 'title' => $image->title, + 'id' => $image->id); + + if (!empty($image->height) || !empty($image->width)) { + $attributes['style'] .= $this->prepare_legacy_width_and_height($image); + } + return $this->output_empty_tag('img', $attributes); + } + + /** + * Print the specified user's avatar. + * + * This method can be used in two ways: + *
+     * // Option 1:
+     * $userpic = new user_picture();
+     * // Set properties of $userpic
+     * $OUTPUT->user_picture($userpic);
+     *
+     * // Option 2: (shortcut for simple cases)
+     * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
+     * $OUTPUT->user_picture($user, $COURSE->id);
+     * 
+ * + * @param object $userpic Object with at least fields id, picture, imagealt, firstname, lastname + * If any of these are missing, or if a userid is passed, the database is queried. Avoid this + * if at all possible, particularly for reports. It is very bad for performance. + * A user_picture object is a better parameter. + * @param int $courseid courseid Used when constructing the link to the user's profile. Required if $userpic + * is not a user_picture object + * @return string HTML fragment + */ + public function user_picture($userpic, $courseid=null) { + // Instantiate a user_picture object if $user is not already one + if (!($userpic instanceof user_picture)) { + if (empty($courseid)) { + throw new coding_exception('Called $OUTPUT->user_picture with a $user object but no $courseid.'); + } + + $user = $userpic; + $userpic = new user_picture(); + $userpic->user = $user; + $userpic->courseid = $courseid; + } else { + $userpic = clone($userpic); + } + + $userpic->prepare(); + + $output = $this->image($userpic->image); + + if (!empty($userpic->url)) { + $actions = $userpic->get_actions(); + if ($userpic->popup && !empty($actions)) { + $link = new html_link(); + $link->url = $userpic->url; + $link->text = fullname($userpic->user); + $link->title = fullname($userpic->user); + + foreach ($actions as $action) { + $link->add_action($action); + } + $output = $this->link_to_popup($link, $userpic->image); + } else { + $output = $this->link(prepare_url($userpic->url), $output); + } + } + + return $output; + } + + /** + * Prints the 'Update this Modulename' button that appears on module pages. + * + * @param string $cmid the course_module id. + * @param string $modulename the module name, eg. "forum", "quiz" or "workshop" + * @return string the HTML for the button, if this user has permission to edit it, else an empty string. + */ + public function update_module_button($cmid, $modulename) { + global $CFG; + if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) { + $modulename = get_string('modulename', $modulename); + $string = get_string('updatethis', '', $modulename); + + $form = new html_form(); + $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey())); + $form->button->text = $string; + return $this->button($form); + } else { + return ''; + } + } + + /** + * Prints a "Turn editing on/off" button in a form. + * @param moodle_url $url The URL + params to send through when clicking the button + * @return string HTML the button + */ + public function edit_button(moodle_url $url) { + global $USER; + if (!empty($USER->editing)) { + $string = get_string('turneditingoff'); + $edit = '0'; + } else { + $string = get_string('turneditingon'); + $edit = '1'; + } + + $form = new html_form(); + $form->url = $url; + $form->url->param('edit', $edit); + $form->button->text = $string; + + return $this->button($form); + } + + /** + * Outputs a HTML nested list + * + * @param html_list $list A html_list object + * @return string HTML structure + */ + public function htmllist($list) { + $list = clone($list); + $list->prepare(); + + $this->prepare_event_handlers($list); + + if ($list->type == 'ordered') { + $tag = 'ol'; + } else if ($list->type == 'unordered') { + $tag = 'ul'; + } + + $output = $this->output_start_tag($tag, array('class' => $list->get_classes_string())); + + foreach ($list->items as $listitem) { + if ($listitem instanceof html_list) { + $output .= $this->output_start_tag('li', array()); + $output .= $this->htmllist($listitem); + $output .= $this->output_end_tag('li'); + } else if ($listitem instanceof html_list_item) { + $listitem->prepare(); + $this->prepare_event_handlers($listitem); + $output .= $this->output_tag('li', array('class' => $listitem->get_classes_string()), $listitem->value); + } + } + + return $output . $this->output_end_tag($tag); + } + + /** + * Prints a simple button to close a window + * + * @global objec)t + * @param string $text The lang string for the button's label (already output from get_string()) + * @return string|void if $return is true, void otherwise + */ + public function close_window_button($text) { + if (empty($text)) { + $text = get_string('closewindow'); + } + $closeform = new html_form(); + $closeform->url = '#'; + $closeform->button->text = $text; + $closeform->button->add_action('click', 'close_window'); + $closeform->button->prepare(); + return $this->container($this->button($closeform), 'closewindow'); + } + + /** + * Outputs a menu, unless $multiple is true, in which case it + * will render checkboxes. + * + * To surround the menu with a form, simply set moodle_select->form as a + * valid html_form object. Note that this function will NOT automatically + * add a form for non-JS browsers. If you do not set one up, it assumes + * that you are providing your own form in some other way. + * + * You can either call this function with a single moodle_select argument + * or, with a list of parameters, in which case those parameters are sent to + * the moodle_select constructor. + * + * @param moodle_select $select a moodle_select that describes + * the select menu you want output. + * @return string the HTML for the element. Optgroups are ignored, so do not + * pass a html_select_optgroup as a param to this function. + * + * @param html_select_option $option a html_select_option + * @return string the HTML for the + */ + public function radio($option, $name='unnamed') { + if ($option instanceof html_select_optgroup) { + throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.'); + } else if (!($option instanceof html_select_option)) { + throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.'); + } + $option = clone($option); + $option->prepare(); + $option->label->for = $option->id; + $this->prepare_event_handlers($option); + + $output = $this->output_start_tag('span', array('class' => "radiogroup $select->name rb$currentradio")) . "\n"; + $output .= $this->label($option->label); + + if ($option->selected == 'selected') { + $option->selected = 'checked'; + } + + $output .= $this->output_empty_tag('input', array( + 'type' => 'radio', + 'value' => $option->value, + 'name' => $name, + 'alt' => $option->alt, + 'id' => $option->id, + 'class' => $option->get_classes_string(), + 'checked' => $option->selected)); + + $output .= $this->output_end_tag('span'); + + return $output; + } + + /** + * Outputs a element. Optgroups are ignored, so do not + * pass a html_select_optgroup as a param to this function. + * + * @param html_select_option $option a html_select_option + * @return string the HTML for the + */ + public function checkbox($option, $name='unnamed') { + if ($option instanceof html_select_optgroup) { + throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.'); + } else if (!($option instanceof html_select_option)) { + throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.'); + } + $option = clone($option); + $option->prepare(); + + $option->label->for = $option->id; + $this->prepare_event_handlers($option); + + $output = $this->output_start_tag('span', array('class' => "checkbox $name")) . "\n"; + + if ($option->selected == 'selected') { + $option->selected = 'checked'; + } + + $output .= $this->output_empty_tag('input', array( + 'type' => 'checkbox', + 'value' => $option->value, + 'name' => $name, + 'id' => $option->id, + 'alt' => $option->alt, + 'class' => $option->get_classes_string(), + 'checked' => $option->selected)); + $output .= $this->label($option->label); + + $output .= $this->output_end_tag('span'); + + return $output; + } + + /** + * Output an element. If an optgroup element is detected, + * this will recursively output its options as well. + * + * @param mixed $option a html_select_option or moodle_select_optgroup + * @return string the HTML for the + */ + public function select_option($option) { + $option = clone($option); + $option->prepare(); + $this->prepare_event_handlers($option); + + if ($option instanceof html_select_option) { + return $this->output_tag('option', array( + 'value' => $option->value, + 'class' => $option->get_classes_string(), + 'selected' => $option->selected), $option->text); + } else if ($option instanceof html_select_optgroup) { + $output = $this->output_start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string())); + foreach ($option->options as $selectoption) { + $output .= $this->select_option($selectoption); + } + $output .= $this->output_end_tag('optgroup'); + return $output; + } + } + + /** + * Output an element + * + * @param html_field $field a html_field object + * @return string the HTML for the + */ + public function textfield($field) { + $field = clone($field); + $field->prepare(); + $this->prepare_event_handlers($field); + $output = $this->output_start_tag('span', array('class' => "textfield $field->name")); + $output .= $this->output_empty_tag('input', array( + 'type' => 'text', + 'name' => $field->name, + 'id' => $field->id, + 'value' => $field->value, + 'style' => $field->style, + 'alt' => $field->alt, + 'maxlength' => $field->maxlength)); + $output .= $this->output_end_tag('span'); + return $output; + } + + /** + * Outputs a

' . + get_string('moreinformation') . '

'; + $output .= $this->box($message, 'errorbox'); + + if (debugging('', DEBUG_DEVELOPER)) { + if ($showerrordebugwarning) { + $output .= $this->notification('error() is a deprecated function. ' . + 'Please call print_error() instead of error()', 'notifytiny'); + } + if (!empty($debuginfo)) { + $output .= $this->notification($debuginfo, 'notifytiny'); + } + if (!empty($backtrace)) { + $output .= $this->notification('Stack trace: ' . + format_backtrace($backtrace), 'notifytiny'); + } + } + + if (!empty($link)) { + $output .= $this->continue_button($link); + } + + $output .= $this->footer(); + + // Padding to encourage IE to display our error page, rather than its own. + $output .= str_repeat(' ', 512); + + return $output; + } + + /** + * Output a notification (that is, a status message about something that has + * just happened). + * + * @param string $message the message to print out + * @param string $classes normally 'notifyproblem' or 'notifysuccess'. + * @return string the HTML to output. + */ + public function notification($message, $classes = 'notifyproblem') { + return $this->output_tag('div', array('class' => + moodle_renderer_base::prepare_classes($classes)), clean_text($message)); + } + + /** + * Print a continue button that goes to a particular URL. + * + * @param string|moodle_url $link The url the button goes to. + * @return string the HTML to output. + */ + public function continue_button($link) { + if (!is_a($link, 'moodle_url')) { + $link = new moodle_url($link); + } + $form = new html_form(); + $form->url = $link; + $form->values = $link->params(); + $form->button->text = get_string('continue'); + $form->method = 'get'; + + return $this->output_tag('div', array('class' => 'continuebutton') , $this->button($form)); + } + + /** + * Prints a single paging bar to provide access to other pages (usually in a search) + * + * @param string|moodle_url $link The url the button goes to. + * @return string the HTML to output. + */ + public function paging_bar($pagingbar) { + $output = ''; + $pagingbar = clone($pagingbar); + $pagingbar->prepare(); + + if ($pagingbar->totalcount > $pagingbar->perpage) { + $output .= get_string('page') . ':'; + + if (!empty($pagingbar->previouslink)) { + $output .= ' (' . $this->link($pagingbar->previouslink) . ') '; + } + + if (!empty($pagingbar->firstlink)) { + $output .= ' ' . $this->link($pagingbar->firstlink) . ' ...'; + } + + foreach ($pagingbar->pagelinks as $link) { + if ($link instanceof html_link) { + $output .= '  ' . $this->link($link); + } else { + $output .= "  $link"; + } + } + + if (!empty($pagingbar->lastlink)) { + $output .= ' ...' . $this->link($pagingbar->lastlink) . ' '; + } + + if (!empty($pagingbar->nextlink)) { + $output .= '  (' . $this->link($pagingbar->nextlink) . ')'; + } + } + + return $this->output_tag('div', array('class' => 'paging'), $output); + } + + /** + * Render a HTML table + * + * @param object $table {@link html_table} instance containing all the information needed + * @return string the HTML to output. + */ + public function table(html_table $table) { + $table = clone($table); + $table->prepare(); + $attributes = array( + 'id' => $table->id, + 'width' => $table->width, + 'summary' => $table->summary, + 'cellpadding' => $table->cellpadding, + 'cellspacing' => $table->cellspacing, + 'class' => $table->get_classes_string()); + $output = $this->output_start_tag('table', $attributes) . "\n"; + + $countcols = 0; + + if (!empty($table->head)) { + $countcols = count($table->head); + $output .= $this->output_start_tag('thead', array()) . "\n"; + $output .= $this->output_start_tag('tr', array()) . "\n"; + $keys = array_keys($table->head); + $lastkey = end($keys); + foreach ($table->head as $key => $heading) { + $classes = array('header', 'c' . $key); + if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) { + $colspan = $table->headspan[$key]; + $countcols += $table->headspan[$key] - 1; + } else { + $colspan = ''; + } + if ($key == $lastkey) { + $classes[] = 'lastcol'; + } + if (isset($table->colclasses[$key])) { + $classes[] = $table->colclasses[$key]; + } + if ($table->rotateheaders) { + // we need to wrap the heading content + $heading = $this->output_tag('span', '', $heading); + } + $attributes = array( + 'style' => $table->align[$key] . $table->size[$key] . 'white-space:nowrap;', + 'class' => moodle_renderer_base::prepare_classes($classes), + 'scope' => 'col', + 'colspan' => $colspan); + $output .= $this->output_tag('th', $attributes, $heading) . "\n"; + } + $output .= $this->output_end_tag('tr') . "\n"; + $output .= $this->output_end_tag('thead') . "\n"; + } + + if (!empty($table->data)) { + $oddeven = 1; + $keys = array_keys($table->data); + $lastrowkey = end($keys); + $output .= $this->output_start_tag('tbody', array()) . "\n"; + + foreach ($table->data as $key => $row) { + if (($row === 'hr') && ($countcols)) { + $output .= $this->output_tag('td', array('colspan' => $countcols), + $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n"; + } else { + // Convert array rows to html_table_rows and cell strings to html_table_cell objects + if (!($row instanceof html_table_row)) { + $newrow = new html_table_row(); + + foreach ($row as $unused => $item) { + $cell = new html_table_cell(); + $cell->text = $item; + $newrow->cells[] = $cell; + } + $row = $newrow; + } + + $oddeven = $oddeven ? 0 : 1; + if (isset($table->rowclasses[$key])) { + $row->add_classes(array_unique(moodle_html_component::clean_classes($table->rowclasses[$key]))); + } + + $row->add_class('r' . $oddeven); + if ($key == $lastrowkey) { + $row->add_class('lastrow'); + } + + $output .= $this->output_start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n"; + $keys2 = array_keys($row->cells); + $lastkey = end($keys2); + + foreach ($row->cells as $key => $cell) { + if (isset($table->colclasses[$key])) { + $cell->add_classes(array_unique(moodle_html_component::clean_classes($table->colclasses[$key]))); + } + + $cell->add_classes('cell'); + $cell->add_classes('c' . $key); + if ($key == $lastkey) { + $cell->add_classes('lastcol'); + } + $tdstyle = ''; + $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : ''; + $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : ''; + $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : ''; + $tdattributes = array( + 'style' => $tdstyle . $cell->style, + 'colspan' => $cell->colspan, + 'rowspan' => $cell->rowspan, + 'id' => $cell->id, + 'class' => $cell->get_classes_string(), + 'abbr' => $cell->abbr, + 'scope' => $cell->scope); + + $output .= $this->output_tag('td', $tdattributes, $cell->text) . "\n"; + } + } + $output .= $this->output_end_tag('tr') . "\n"; + } + $output .= $this->output_end_tag('tbody') . "\n"; + } + $output .= $this->output_end_tag('table') . "\n"; + + if ($table->rotateheaders && can_use_rotated_text()) { + $this->page->requires->yui_lib('event'); + $this->page->requires->js('course/report/progress/textrotate.js'); + } + + return $output; + } + + /** + * Output the place a skip link goes to. + * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call. + * @return string the HTML to output. + */ + public function skip_link_target($id = '') { + return $this->output_tag('span', array('id' => $id), ''); + } + + /** + * Outputs a heading + * @param string $text The text of the heading + * @param int $level The level of importance of the heading. Defaulting to 2 + * @param string $classes A space-separated list of CSS classes + * @param string $id An optional ID + * @return string the HTML to output. + */ + public function heading($text, $level = 2, $classes = 'main', $id = '') { + $level = (integer) $level; + if ($level < 1 or $level > 6) { + throw new coding_exception('Heading level must be an integer between 1 and 6.'); + } + return $this->output_tag('h' . $level, + array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text); + } + + /** + * Outputs a box. + * @param string $contents The contents of the box + * @param string $classes A space-separated list of CSS classes + * @param string $id An optional ID + * @return string the HTML to output. + */ + public function box($contents, $classes = 'generalbox', $id = '') { + return $this->box_start($classes, $id) . $contents . $this->box_end(); + } + + /** + * Outputs the opening section of a box. + * @param string $classes A space-separated list of CSS classes + * @param string $id An optional ID + * @return string the HTML to output. + */ + public function box_start($classes = 'generalbox', $id = '') { + $this->opencontainers->push('box', $this->output_end_tag('div')); + return $this->output_start_tag('div', array('id' => $id, + 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes))); + } + + /** + * Outputs the closing section of a box. + * @return string the HTML to output. + */ + public function box_end() { + return $this->opencontainers->pop('box'); + } + + /** + * Outputs a container. + * @param string $contents The contents of the box + * @param string $classes A space-separated list of CSS classes + * @param string $id An optional ID + * @return string the HTML to output. + */ + public function container($contents, $classes = '', $id = '') { + return $this->container_start($classes, $id) . $contents . $this->container_end(); + } + + /** + * Outputs the opening section of a container. + * @param string $classes A space-separated list of CSS classes + * @param string $id An optional ID + * @return string the HTML to output. + */ + public function container_start($classes = '', $id = '') { + $this->opencontainers->push('container', $this->output_end_tag('div')); + return $this->output_start_tag('div', array('id' => $id, + 'class' => moodle_renderer_base::prepare_classes($classes))); + } + + /** + * Outputs the closing section of a container. + * @return string the HTML to output. + */ + public function container_end() { + return $this->opencontainers->pop('container'); + } +} + + +/// RENDERERS + +/** + * A renderer that generates output for command-line scripts. + * + * The implementation of this renderer is probably incomplete. + * + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 2.0 + */ +class cli_core_renderer extends moodle_core_renderer { + /** + * Returns the page header. + * @return string HTML fragment + */ + public function header() { + output_starting_hook(); + return $this->page->heading . "\n"; + } + + /** + * Returns a template fragment representing a Heading. + * @param string $text The text of the heading + * @param int $level The level of importance of the heading + * @param string $classes A space-separated list of CSS classes + * @param string $id An optional ID + * @return string A template fragment for a heading + */ + public function heading($text, $level, $classes = 'main', $id = '') { + $text .= "\n"; + switch ($level) { + case 1: + return '=>' . $text; + case 2: + return '-->' . $text; + default: + return $text; + } + } + + /** + * Returns a template fragment representing a fatal error. + * @param string $message The message to output + * @param string $moreinfourl URL where more info can be found about the error + * @param string $link Link for the Continue button + * @param array $backtrace The execution backtrace + * @param string $debuginfo Debugging information + * @param bool $showerrordebugwarning Whether or not to show a debugging warning + * @return string A template fragment for a fatal error + */ + public function fatal_error($message, $moreinfourl, $link, $backtrace, + $debuginfo = null, $showerrordebugwarning = false) { + $output = "!!! $message !!!\n"; + + if (debugging('', DEBUG_DEVELOPER)) { + if (!empty($debuginfo)) { + $this->notification($debuginfo, 'notifytiny'); + } + if (!empty($backtrace)) { + $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny'); + } + } + } + + /** + * Returns a template fragment representing a notification. + * @param string $message The message to include + * @param string $classes A space-separated list of CSS classes + * @return string A template fragment for a notification + */ + public function notification($message, $classes = 'notifyproblem') { + $message = clean_text($message); + if ($classes === 'notifysuccess') { + return "++ $message ++\n"; + } + return "!! $message !!\n"; + } +} +