output MDL-25597 Added three new config settings that allow you to add additional...
[moodle.git] / lib / outputrenderers.php
CommitLineData
d9c8f425 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * Classes for rendering HTML output for Moodle.
20 *
21 * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
22 * for an overview.
23 *
78bfb562
PS
24 * @package core
25 * @subpackage lib
26 * @copyright 2009 Tim Hunt
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
d9c8f425 28 */
29
78bfb562
PS
30defined('MOODLE_INTERNAL') || die();
31
d9c8f425 32/**
33 * Simple base class for Moodle renderers.
34 *
35 * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
36 *
37 * Also has methods to facilitate generating HTML output.
38 *
39 * @copyright 2009 Tim Hunt
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41 * @since Moodle 2.0
42 */
78946b9b 43class renderer_base {
d9c8f425 44 /** @var xhtml_container_stack the xhtml_container_stack to use. */
45 protected $opencontainers;
46 /** @var moodle_page the page we are rendering for. */
47 protected $page;
996b1e0c 48 /** @var requested rendering target */
c927e35c 49 protected $target;
d9c8f425 50
51 /**
52 * Constructor
53 * @param moodle_page $page the page we are doing output for.
c927e35c 54 * @param string $target one of rendering target constants
d9c8f425 55 */
c927e35c 56 public function __construct(moodle_page $page, $target) {
d9c8f425 57 $this->opencontainers = $page->opencontainers;
58 $this->page = $page;
c927e35c 59 $this->target = $target;
d9c8f425 60 }
61
62 /**
5d0c95a5 63 * Returns rendered widget.
996b1e0c 64 * @param renderable $widget instance with renderable interface
5d0c95a5 65 * @return string
d9c8f425 66 */
5d0c95a5 67 public function render(renderable $widget) {
2cf81209 68 $rendermethod = 'render_'.get_class($widget);
5d0c95a5
PS
69 if (method_exists($this, $rendermethod)) {
70 return $this->$rendermethod($widget);
71 }
72 throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.');
d9c8f425 73 }
74
75 /**
5d0c95a5 76 * Adds JS handlers needed for event execution for one html element id
5d0c95a5 77 * @param component_action $actions
c80877aa
PS
78 * @param string $id
79 * @return string id of element, either original submitted or random new if not supplied
d9c8f425 80 */
c80877aa
PS
81 public function add_action_handler(component_action $action, $id=null) {
82 if (!$id) {
83 $id = html_writer::random_id($action->event);
84 }
d96d8f03 85 $this->page->requires->event_handler("#$id", $action->event, $action->jsfunction, $action->jsfunctionargs);
c80877aa 86 return $id;
d9c8f425 87 }
88
89 /**
5d0c95a5
PS
90 * Have we started output yet?
91 * @return boolean true if the header has been printed.
d9c8f425 92 */
5d0c95a5
PS
93 public function has_started() {
94 return $this->page->state >= moodle_page::STATE_IN_BODY;
d9c8f425 95 }
96
97 /**
98 * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
99 * @param mixed $classes Space-separated string or array of classes
100 * @return string HTML class attribute value
101 */
102 public static function prepare_classes($classes) {
103 if (is_array($classes)) {
104 return implode(' ', array_unique($classes));
105 }
106 return $classes;
107 }
108
d9c8f425 109 /**
897e902b
PS
110 * Return the moodle_url for an image.
111 * The exact image location and extension is determined
112 * automatically by searching for gif|png|jpg|jpeg, please
113 * note there can not be diferent images with the different
114 * extension. The imagename is for historical reasons
115 * a relative path name, it may be changed later for core
116 * images. It is recommended to not use subdirectories
117 * in plugin and theme pix directories.
d9c8f425 118 *
897e902b
PS
119 * There are three types of images:
120 * 1/ theme images - stored in theme/mytheme/pix/,
121 * use component 'theme'
122 * 2/ core images - stored in /pix/,
123 * overridden via theme/mytheme/pix_core/
124 * 3/ plugin images - stored in mod/mymodule/pix,
125 * overridden via theme/mytheme/pix_plugins/mod/mymodule/,
126 * example: pix_url('comment', 'mod_glossary')
127 *
128 * @param string $imagename the pathname of the image
129 * @param string $component full plugin name (aka component) or 'theme'
78946b9b 130 * @return moodle_url
d9c8f425 131 */
c927e35c 132 public function pix_url($imagename, $component = 'moodle') {
c39e5ba2 133 return $this->page->theme->pix_url($imagename, $component);
d9c8f425 134 }
d9c8f425 135}
136
c927e35c 137
75590935
PS
138/**
139 * Basis for all plugin renderers.
140 *
c927e35c
PS
141 * @author Petr Skoda (skodak)
142 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
143 * @since Moodle 2.0
75590935
PS
144 */
145class plugin_renderer_base extends renderer_base {
146 /**
147 * A reference to the current general renderer probably {@see core_renderer}
148 * @var renderer_base
149 */
150 protected $output;
151
152 /**
996b1e0c 153 * Constructor method, calls the parent constructor
75590935 154 * @param moodle_page $page
c927e35c 155 * @param string $target one of rendering target constants
75590935 156 */
c927e35c
PS
157 public function __construct(moodle_page $page, $target) {
158 $this->output = $page->get_renderer('core', null, $target);
159 parent::__construct($page, $target);
75590935 160 }
ff5265c6 161
5d0c95a5
PS
162 /**
163 * Returns rendered widget.
71c03ac1 164 * @param renderable $widget instance with renderable interface
5d0c95a5
PS
165 * @return string
166 */
167 public function render(renderable $widget) {
168 $rendermethod = 'render_'.get_class($widget);
169 if (method_exists($this, $rendermethod)) {
170 return $this->$rendermethod($widget);
171 }
172 // pass to core renderer if method not found here
469bf7a4 173 return $this->output->render($widget);
5d0c95a5
PS
174 }
175
ff5265c6
PS
176 /**
177 * Magic method used to pass calls otherwise meant for the standard renderer
996b1e0c 178 * to it to ensure we don't go causing unnecessary grief.
ff5265c6
PS
179 *
180 * @param string $method
181 * @param array $arguments
182 * @return mixed
183 */
184 public function __call($method, $arguments) {
37b5b18e
PS
185 if (method_exists('renderer_base', $method)) {
186 throw new coding_exception('Protected method called against '.__CLASS__.' :: '.$method);
187 }
ff5265c6
PS
188 if (method_exists($this->output, $method)) {
189 return call_user_func_array(array($this->output, $method), $arguments);
190 } else {
191 throw new coding_exception('Unknown method called against '.__CLASS__.' :: '.$method);
192 }
193 }
75590935 194}
d9c8f425 195
c927e35c 196
d9c8f425 197/**
78946b9b 198 * The standard implementation of the core_renderer interface.
d9c8f425 199 *
200 * @copyright 2009 Tim Hunt
201 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
202 * @since Moodle 2.0
203 */
78946b9b 204class core_renderer extends renderer_base {
d9c8f425 205 /** @var string used in {@link header()}. */
206 const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
207 /** @var string used in {@link header()}. */
208 const END_HTML_TOKEN = '%%ENDHTML%%';
209 /** @var string used in {@link header()}. */
210 const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
211 /** @var string used to pass information from {@link doctype()} to {@link standard_head_html()}. */
212 protected $contenttype;
213 /** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */
214 protected $metarefreshtag = '';
215
216 /**
217 * Get the DOCTYPE declaration that should be used with this page. Designed to
218 * be called in theme layout.php files.
219 * @return string the DOCTYPE declaration (and any XML prologue) that should be used.
220 */
221 public function doctype() {
222 global $CFG;
223
224 $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
225 $this->contenttype = 'text/html; charset=utf-8';
226
227 if (empty($CFG->xmlstrictheaders)) {
228 return $doctype;
229 }
230
231 // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
232 $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
233 if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
234 // Firefox and other browsers that can cope natively with XHTML.
235 $this->contenttype = 'application/xhtml+xml; charset=utf-8';
236
237 } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
238 // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet.
239 $this->contenttype = 'application/xml; charset=utf-8';
240 $prolog .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
241
242 } else {
243 $prolog = '';
244 }
245
246 return $prolog . $doctype;
247 }
248
249 /**
250 * The attributes that should be added to the <html> tag. Designed to
251 * be called in theme layout.php files.
252 * @return string HTML fragment.
253 */
254 public function htmlattributes() {
255 return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
256 }
257
258 /**
259 * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
260 * that should be included in the <head> tag. Designed to be called in theme
261 * layout.php files.
262 * @return string HTML fragment.
263 */
264 public function standard_head_html() {
b5bbeaf0 265 global $CFG, $SESSION;
d9c8f425 266 $output = '';
267 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
268 $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
269 if (!$this->page->cacheable) {
270 $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
271 $output .= '<meta http-equiv="expires" content="0" />' . "\n";
272 }
273 // This is only set by the {@link redirect()} method
274 $output .= $this->metarefreshtag;
275
276 // Check if a periodic refresh delay has been set and make sure we arn't
277 // already meta refreshing
278 if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
279 $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
280 }
281
7d2a0492 282 $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
d9c8f425 283
284 $focus = $this->page->focuscontrol;
285 if (!empty($focus)) {
286 if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
287 // This is a horrifically bad way to handle focus but it is passed in
288 // through messy formslib::moodleform
7d2a0492 289 $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
d9c8f425 290 } else if (strpos($focus, '.')!==false) {
291 // Old style of focus, bad way to do it
292 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);
293 $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
294 } else {
295 // Focus element with given id
7d2a0492 296 $this->page->requires->js_function_call('focuscontrol', array($focus));
d9c8f425 297 }
298 }
299
78946b9b
PS
300 // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins;
301 // any other custom CSS can not be overridden via themes and is highly discouraged
efaa4c08 302 $urls = $this->page->theme->css_urls($this->page);
78946b9b 303 foreach ($urls as $url) {
c0467479 304 $this->page->requires->css_theme($url);
78946b9b
PS
305 }
306
04c01408 307 // Get the theme javascript head and footer
04c01408 308 $jsurl = $this->page->theme->javascript_url(true);
baeb20bb
PS
309 $this->page->requires->js($jsurl, true);
310 $jsurl = $this->page->theme->javascript_url(false);
8ce04d51 311 $this->page->requires->js($jsurl);
5d0c95a5 312
78946b9b 313 // Perform a browser environment check for the flash version. Should only run once per login session.
2c0d7839 314 if (!NO_MOODLE_COOKIES && isloggedin() && !empty($CFG->excludeoldflashclients) && empty($SESSION->flashversion)) {
4abd5b9b
PS
315 $this->page->requires->js('/lib/swfobject/swfobject.js');
316 $this->page->requires->js_init_call('M.core_flashdetect.init');
b5bbeaf0 317 }
318
d9c8f425 319 // Get any HTML from the page_requirements_manager.
945f19f7 320 $output .= $this->page->requires->get_head_code($this->page, $this);
d9c8f425 321
322 // List alternate versions.
323 foreach ($this->page->alternateversions as $type => $alt) {
5d0c95a5 324 $output .= html_writer::empty_tag('link', array('rel' => 'alternate',
d9c8f425 325 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
326 }
90e920c7
SH
327
328 if (!empty($CFG->additionalhtmlhead)) {
329 $output .= "\n".$CFG->additionalhtmlhead;
330 }
d9c8f425 331
332 return $output;
333 }
334
335 /**
336 * The standard tags (typically skip links) that should be output just inside
337 * the start of the <body> tag. Designed to be called in theme layout.php files.
338 * @return string HTML fragment.
339 */
340 public function standard_top_of_body_html() {
90e920c7
SH
341 global $CFG;
342 $output = $this->page->requires->get_top_of_body_code();
343 if (!empty($CFG->additionalhtmltopofbody)) {
344 $output .= "\n".$CFG->additionalhtmltopofbody;
345 }
346 return $output;
d9c8f425 347 }
348
349 /**
350 * The standard tags (typically performance information and validation links,
351 * if we are in developer debug mode) that should be output in the footer area
352 * of the page. Designed to be called in theme layout.php files.
353 * @return string HTML fragment.
354 */
355 public function standard_footer_html() {
356 global $CFG;
357
358 // This function is normally called from a layout.php file in {@link header()}
359 // but some of the content won't be known until later, so we return a placeholder
360 // for now. This will be replaced with the real content in {@link footer()}.
361 $output = self::PERFORMANCE_INFO_TOKEN;
ee8df661
SH
362 if ($this->page->legacythemeinuse) {
363 // The legacy theme is in use print the notification
364 $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse'));
365 }
d9c8f425 366 if (!empty($CFG->debugpageinfo)) {
d4c3f025 367 $output .= '<div class="performanceinfo pageinfo">This page is: ' . $this->page->debug_summary() . '</div>';
d9c8f425 368 }
1b396b18 369 if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) { // Only in developer mode
ba6c97ee
MD
370 $output .= '<div class="purgecaches"><a href="'.$CFG->wwwroot.'/admin/purgecaches.php?confirm=1&amp;sesskey='.sesskey().'">'.get_string('purgecaches', 'admin').'</a></div>';
371 }
d9c8f425 372 if (!empty($CFG->debugvalidators)) {
373 $output .= '<div class="validators"><ul>
374 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
375 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
376 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=0&amp;warnp2n3e=1&amp;url1=' . urlencode(qualified_me()) . '">WCAG 1 (2,3) Check</a></li>
377 </ul></div>';
378 }
90e920c7
SH
379 if (!empty($CFG->additionalhtmlfooter)) {
380 $output .= "\n".$CFG->additionalhtmlfooter;
381 }
d9c8f425 382 return $output;
383 }
384
385 /**
386 * The standard tags (typically script tags that are not needed earlier) that
387 * should be output after everything else, . Designed to be called in theme layout.php files.
388 * @return string HTML fragment.
389 */
390 public function standard_end_of_body_html() {
391 // This function is normally called from a layout.php file in {@link header()}
392 // but some of the content won't be known until later, so we return a placeholder
393 // for now. This will be replaced with the real content in {@link footer()}.
2138d893 394 return self::END_HTML_TOKEN;
d9c8f425 395 }
396
397 /**
398 * Return the standard string that says whether you are logged in (and switched
399 * roles/logged in as another user).
400 * @return string HTML fragment.
401 */
402 public function login_info() {
8f0fe0b8 403 global $USER, $CFG, $DB, $SESSION;
4bcc5118 404
244a32c6
PS
405 if (during_initial_install()) {
406 return '';
407 }
4bcc5118 408
2778744b 409 $loginapge = ((string)$this->page->url === get_login_url());
244a32c6 410 $course = $this->page->course;
4bcc5118 411
244a32c6
PS
412 if (session_is_loggedinas()) {
413 $realuser = session_get_realuser();
414 $fullname = fullname($realuser, true);
e884f63a 415 $realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&amp;sesskey=".sesskey()."\">$fullname</a>] ";
244a32c6
PS
416 } else {
417 $realuserinfo = '';
418 }
4bcc5118 419
244a32c6 420 $loginurl = get_login_url();
4bcc5118 421
244a32c6
PS
422 if (empty($course->id)) {
423 // $course->id is not defined during installation
424 return '';
4f0c2d00 425 } else if (isloggedin()) {
244a32c6 426 $context = get_context_instance(CONTEXT_COURSE, $course->id);
4bcc5118 427
244a32c6 428 $fullname = fullname($USER, true);
03d9401e
MD
429 // Since Moodle 2.0 this link always goes to the public profile page (not the course profile page)
430 $username = "<a href=\"$CFG->wwwroot/user/profile.php?id=$USER->id\">$fullname</a>";
244a32c6 431 if (is_mnet_remote_user($USER) and $idprovider = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid))) {
4aea3cc7 432 $username .= " from <a href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>";
244a32c6 433 }
b3df1764 434 if (isguestuser()) {
2778744b
PS
435 $loggedinas = $realuserinfo.get_string('loggedinasguest');
436 if (!$loginapge) {
437 $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
438 }
f5c1e621 439 } else if (is_role_switched($course->id)) { // Has switched roles
244a32c6
PS
440 $rolename = '';
441 if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
442 $rolename = ': '.format_string($role->name);
443 }
444 $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename.
4aea3cc7 445 " (<a href=\"$CFG->wwwroot/course/view.php?id=$course->id&amp;switchrole=0&amp;sesskey=".sesskey()."\">".get_string('switchrolereturn').'</a>)';
244a32c6
PS
446 } else {
447 $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username).' '.
4aea3cc7 448 " (<a href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)';
244a32c6
PS
449 }
450 } else {
2778744b
PS
451 $loggedinas = get_string('loggedinnot', 'moodle');
452 if (!$loginapge) {
453 $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
454 }
244a32c6 455 }
4bcc5118 456
244a32c6 457 $loggedinas = '<div class="logininfo">'.$loggedinas.'</div>';
4bcc5118 458
244a32c6
PS
459 if (isset($SESSION->justloggedin)) {
460 unset($SESSION->justloggedin);
461 if (!empty($CFG->displayloginfailures)) {
b3df1764 462 if (!isguestuser()) {
244a32c6
PS
463 if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) {
464 $loggedinas .= '&nbsp;<div class="loginfailures">';
465 if (empty($count->accounts)) {
466 $loggedinas .= get_string('failedloginattempts', '', $count);
467 } else {
468 $loggedinas .= get_string('failedloginattemptsall', '', $count);
469 }
470 if (has_capability('coursereport/log:view', get_context_instance(CONTEXT_SYSTEM))) {
471 $loggedinas .= ' (<a href="'.$CFG->wwwroot.'/course/report/log/index.php'.
472 '?chooselog=1&amp;id=1&amp;modid=site_errors">'.get_string('logs').'</a>)';
473 }
474 $loggedinas .= '</div>';
475 }
476 }
477 }
478 }
4bcc5118 479
244a32c6 480 return $loggedinas;
d9c8f425 481 }
482
483 /**
484 * Return the 'back' link that normally appears in the footer.
485 * @return string HTML fragment.
486 */
487 public function home_link() {
488 global $CFG, $SITE;
489
490 if ($this->page->pagetype == 'site-index') {
491 // Special case for site home page - please do not remove
492 return '<div class="sitelink">' .
34dff6aa 493 '<a title="Moodle" href="http://moodle.org/">' .
53228896 494 '<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
d9c8f425 495
496 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
497 // Special case for during install/upgrade.
498 return '<div class="sitelink">'.
34dff6aa 499 '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
53228896 500 '<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
d9c8f425 501
502 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
503 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
504 get_string('home') . '</a></div>';
505
506 } else {
507 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
508 format_string($this->page->course->shortname) . '</a></div>';
509 }
510 }
511
512 /**
513 * Redirects the user by any means possible given the current state
514 *
515 * This function should not be called directly, it should always be called using
516 * the redirect function in lib/weblib.php
517 *
518 * The redirect function should really only be called before page output has started
519 * however it will allow itself to be called during the state STATE_IN_BODY
520 *
521 * @param string $encodedurl The URL to send to encoded if required
522 * @param string $message The message to display to the user if any
523 * @param int $delay The delay before redirecting a user, if $message has been
524 * set this is a requirement and defaults to 3, set to 0 no delay
525 * @param boolean $debugdisableredirect this redirect has been disabled for
526 * debugging purposes. Display a message that explains, and don't
527 * trigger the redirect.
528 * @return string The HTML to display to the user before dying, may contain
529 * meta refresh, javascript refresh, and may have set header redirects
530 */
531 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
532 global $CFG;
533 $url = str_replace('&amp;', '&', $encodedurl);
534
535 switch ($this->page->state) {
536 case moodle_page::STATE_BEFORE_HEADER :
537 // No output yet it is safe to delivery the full arsenal of redirect methods
538 if (!$debugdisableredirect) {
539 // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
540 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
593f9b87 541 $this->page->requires->js_function_call('document.location.replace', array($url), false, ($delay + 3));
d9c8f425 542 }
543 $output = $this->header();
544 break;
545 case moodle_page::STATE_PRINTING_HEADER :
546 // We should hopefully never get here
547 throw new coding_exception('You cannot redirect while printing the page header');
548 break;
549 case moodle_page::STATE_IN_BODY :
550 // We really shouldn't be here but we can deal with this
551 debugging("You should really redirect before you start page output");
552 if (!$debugdisableredirect) {
593f9b87 553 $this->page->requires->js_function_call('document.location.replace', array($url), false, $delay);
d9c8f425 554 }
555 $output = $this->opencontainers->pop_all_but_last();
556 break;
557 case moodle_page::STATE_DONE :
558 // Too late to be calling redirect now
559 throw new coding_exception('You cannot redirect after the entire page has been generated');
560 break;
561 }
562 $output .= $this->notification($message, 'redirectmessage');
3ab2e357 563 $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>';
d9c8f425 564 if ($debugdisableredirect) {
565 $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
566 }
567 $output .= $this->footer();
568 return $output;
569 }
570
571 /**
572 * Start output by sending the HTTP headers, and printing the HTML <head>
573 * and the start of the <body>.
574 *
575 * To control what is printed, you should set properties on $PAGE. If you
576 * are familiar with the old {@link print_header()} function from Moodle 1.9
577 * you will find that there are properties on $PAGE that correspond to most
578 * of the old parameters to could be passed to print_header.
579 *
580 * Not that, in due course, the remaining $navigation, $menu parameters here
581 * will be replaced by more properties of $PAGE, but that is still to do.
582 *
d9c8f425 583 * @return string HTML that you must output this, preferably immediately.
584 */
e120c61d 585 public function header() {
d9c8f425 586 global $USER, $CFG;
587
e884f63a
PS
588 if (session_is_loggedinas()) {
589 $this->page->add_body_class('userloggedinas');
590 }
591
d9c8f425 592 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
593
78946b9b
PS
594 // Find the appropriate page layout file, based on $this->page->pagelayout.
595 $layoutfile = $this->page->theme->layout_file($this->page->pagelayout);
596 // Render the layout using the layout file.
597 $rendered = $this->render_page_layout($layoutfile);
67e84a7f 598
78946b9b
PS
599 // Slice the rendered output into header and footer.
600 $cutpos = strpos($rendered, self::MAIN_CONTENT_TOKEN);
d9c8f425 601 if ($cutpos === false) {
78946b9b 602 throw new coding_exception('page layout file ' . $layoutfile .
d9c8f425 603 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
604 }
78946b9b
PS
605 $header = substr($rendered, 0, $cutpos);
606 $footer = substr($rendered, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
d9c8f425 607
608 if (empty($this->contenttype)) {
78946b9b 609 debugging('The page layout file did not call $OUTPUT->doctype()');
67e84a7f 610 $header = $this->doctype() . $header;
d9c8f425 611 }
612
613 send_headers($this->contenttype, $this->page->cacheable);
67e84a7f 614
d9c8f425 615 $this->opencontainers->push('header/footer', $footer);
616 $this->page->set_state(moodle_page::STATE_IN_BODY);
67e84a7f 617
29ba64e5 618 return $header . $this->skip_link_target('maincontent');
d9c8f425 619 }
620
621 /**
78946b9b
PS
622 * Renders and outputs the page layout file.
623 * @param string $layoutfile The name of the layout file
d9c8f425 624 * @return string HTML code
625 */
78946b9b 626 protected function render_page_layout($layoutfile) {
92e01ab7 627 global $CFG, $SITE, $USER;
d9c8f425 628 // The next lines are a bit tricky. The point is, here we are in a method
629 // of a renderer class, and this object may, or may not, be the same as
78946b9b 630 // the global $OUTPUT object. When rendering the page layout file, we want to use
d9c8f425 631 // this object. However, people writing Moodle code expect the current
632 // renderer to be called $OUTPUT, not $this, so define a variable called
633 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
634 $OUTPUT = $this;
635 $PAGE = $this->page;
636 $COURSE = $this->page->course;
637
d9c8f425 638 ob_start();
78946b9b
PS
639 include($layoutfile);
640 $rendered = ob_get_contents();
d9c8f425 641 ob_end_clean();
78946b9b 642 return $rendered;
d9c8f425 643 }
644
645 /**
646 * Outputs the page's footer
647 * @return string HTML fragment
648 */
649 public function footer() {
d5a8d9aa 650 global $CFG, $DB;
0f0801f4 651
f6794ace 652 $output = $this->container_end_all(true);
4d2ee4c2 653
d9c8f425 654 $footer = $this->opencontainers->pop('header/footer');
655
d5a8d9aa 656 if (debugging() and $DB and $DB->is_transaction_started()) {
03221650 657 // TODO: MDL-20625 print warning - transaction will be rolled back
d5a8d9aa
PS
658 }
659
d9c8f425 660 // Provide some performance info if required
661 $performanceinfo = '';
662 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
663 $perf = get_performance_info();
664 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
665 error_log("PERF: " . $perf['txt']);
666 }
667 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
668 $performanceinfo = $perf['html'];
669 }
670 }
671 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
672
673 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
674
675 $this->page->set_state(moodle_page::STATE_DONE);
d9c8f425 676
677 return $output . $footer;
678 }
679
f6794ace
PS
680 /**
681 * Close all but the last open container. This is useful in places like error
682 * handling, where you want to close all the open containers (apart from <body>)
683 * before outputting the error message.
684 * @param bool $shouldbenone assert that the stack should be empty now - causes a
685 * developer debug warning if it isn't.
686 * @return string the HTML required to close any open containers inside <body>.
687 */
688 public function container_end_all($shouldbenone = false) {
689 return $this->opencontainers->pop_all_but_last($shouldbenone);
690 }
691
244a32c6
PS
692 /**
693 * Returns lang menu or '', this method also checks forcing of languages in courses.
694 * @return string
695 */
696 public function lang_menu() {
697 global $CFG;
698
699 if (empty($CFG->langmenu)) {
700 return '';
701 }
702
703 if ($this->page->course != SITEID and !empty($this->page->course->lang)) {
704 // do not show lang menu if language forced
705 return '';
706 }
707
708 $currlang = current_language();
1f96e907 709 $langs = get_string_manager()->get_list_of_translations();
4bcc5118 710
244a32c6
PS
711 if (count($langs) < 2) {
712 return '';
713 }
714
a9967cf5
PS
715 $s = new single_select($this->page->url, 'lang', $langs, $currlang, null);
716 $s->label = get_accesshide(get_string('language'));
717 $s->class = 'langmenu';
718 return $this->render($s);
244a32c6
PS
719 }
720
d9c8f425 721 /**
722 * Output the row of editing icons for a block, as defined by the controls array.
723 * @param array $controls an array like {@link block_contents::$controls}.
724 * @return HTML fragment.
725 */
726 public function block_controls($controls) {
727 if (empty($controls)) {
728 return '';
729 }
730 $controlshtml = array();
731 foreach ($controls as $control) {
f4ed6fc4 732 $controlshtml[] = html_writer::tag('a',
26acc814 733 html_writer::empty_tag('img', array('src' => $this->pix_url($control['icon'])->out(false), 'alt' => $control['caption'])),
f4ed6fc4 734 array('class' => 'icon','title' => $control['caption'], 'href' => $control['url']));
d9c8f425 735 }
26acc814 736 return html_writer::tag('div', implode('', $controlshtml), array('class' => 'commands'));
d9c8f425 737 }
738
739 /**
740 * Prints a nice side block with an optional header.
741 *
742 * The content is described
743 * by a {@link block_contents} object.
744 *
cbb54cce
SH
745 * <div id="inst{$instanceid}" class="block_{$blockname} block">
746 * <div class="header"></div>
747 * <div class="content">
748 * ...CONTENT...
749 * <div class="footer">
750 * </div>
751 * </div>
752 * <div class="annotation">
753 * </div>
754 * </div>
755 *
d9c8f425 756 * @param block_contents $bc HTML for the content
757 * @param string $region the region the block is appearing in.
758 * @return string the HTML to be output.
759 */
dd72b308 760 function block(block_contents $bc, $region) {
d9c8f425 761 $bc = clone($bc); // Avoid messing up the object passed in.
dd72b308
PS
762 if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
763 $bc->collapsible = block_contents::NOT_HIDEABLE;
764 }
765 if ($bc->collapsible == block_contents::HIDDEN) {
766 $bc->add_class('hidden');
767 }
768 if (!empty($bc->controls)) {
769 $bc->add_class('block_with_controls');
770 }
d9c8f425 771
772 $skiptitle = strip_tags($bc->title);
773 if (empty($skiptitle)) {
774 $output = '';
775 $skipdest = '';
776 } else {
26acc814
PS
777 $output = html_writer::tag('a', get_string('skipa', 'access', $skiptitle), array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'));
778 $skipdest = html_writer::tag('span', '', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'));
d9c8f425 779 }
4d2ee4c2 780
5d0c95a5 781 $output .= html_writer::start_tag('div', $bc->attributes);
d9c8f425 782
9f5c39b5
SH
783 $output .= $this->block_header($bc);
784 $output .= $this->block_content($bc);
785
786 $output .= html_writer::end_tag('div');
787
788 $output .= $this->block_annotation($bc);
789
790 $output .= $skipdest;
791
792 $this->init_block_hider_js($bc);
793 return $output;
794 }
795
796 /**
797 * Produces a header for a block
fa7f2a45 798 *
9f5c39b5
SH
799 * @param block_contents $bc
800 * @return string
801 */
802 protected function block_header(block_contents $bc) {
d9c8f425 803
804 $title = '';
805 if ($bc->title) {
26acc814 806 $title = html_writer::tag('h2', $bc->title, null);
d9c8f425 807 }
808
9f5c39b5
SH
809 $controlshtml = $this->block_controls($bc->controls);
810
811 $output = '';
d9c8f425 812 if ($title || $controlshtml) {
46de77b6 813 $output .= html_writer::tag('div', html_writer::tag('div', html_writer::tag('div', '', array('class'=>'block_action')). $title . $controlshtml, array('class' => 'title')), array('class' => 'header'));
d9c8f425 814 }
9f5c39b5
SH
815 return $output;
816 }
d9c8f425 817
9f5c39b5
SH
818 /**
819 * Produces the content area for a block
820 *
821 * @param block_contents $bc
822 * @return string
823 */
824 protected function block_content(block_contents $bc) {
825 $output = html_writer::start_tag('div', array('class' => 'content'));
826 if (!$bc->title && !$this->block_controls($bc->controls)) {
46de77b6
SH
827 $output .= html_writer::tag('div', '', array('class'=>'block_action notitle'));
828 }
d9c8f425 829 $output .= $bc->content;
9f5c39b5
SH
830 $output .= $this->block_footer($bc);
831 $output .= html_writer::end_tag('div');
fa7f2a45 832
9f5c39b5
SH
833 return $output;
834 }
d9c8f425 835
9f5c39b5
SH
836 /**
837 * Produces the footer for a block
838 *
839 * @param block_contents $bc
840 * @return string
841 */
842 protected function block_footer(block_contents $bc) {
843 $output = '';
d9c8f425 844 if ($bc->footer) {
26acc814 845 $output .= html_writer::tag('div', $bc->footer, array('class' => 'footer'));
d9c8f425 846 }
9f5c39b5
SH
847 return $output;
848 }
d9c8f425 849
9f5c39b5
SH
850 /**
851 * Produces the annotation for a block
852 *
853 * @param block_contents $bc
854 * @return string
855 */
856 protected function block_annotation(block_contents $bc) {
857 $output = '';
d9c8f425 858 if ($bc->annotation) {
26acc814 859 $output .= html_writer::tag('div', $bc->annotation, array('class' => 'blockannotation'));
d9c8f425 860 }
d9c8f425 861 return $output;
862 }
863
864 /**
865 * Calls the JS require function to hide a block.
866 * @param block_contents $bc A block_contents object
867 * @return void
868 */
dd72b308
PS
869 protected function init_block_hider_js(block_contents $bc) {
870 if (!empty($bc->attributes['id']) and $bc->collapsible != block_contents::NOT_HIDEABLE) {
cbb54cce
SH
871 $config = new stdClass;
872 $config->id = $bc->attributes['id'];
873 $config->title = strip_tags($bc->title);
874 $config->preference = 'block' . $bc->blockinstanceid . 'hidden';
875 $config->tooltipVisible = get_string('hideblocka', 'access', $config->title);
876 $config->tooltipHidden = get_string('showblocka', 'access', $config->title);
877
878 $this->page->requires->js_init_call('M.util.init_block_hider', array($config));
879 user_preference_allow_ajax_update($config->preference, PARAM_BOOL);
d9c8f425 880 }
881 }
882
883 /**
884 * Render the contents of a block_list.
885 * @param array $icons the icon for each item.
886 * @param array $items the content of each item.
887 * @return string HTML
888 */
889 public function list_block_contents($icons, $items) {
890 $row = 0;
891 $lis = array();
892 foreach ($items as $key => $string) {
5d0c95a5 893 $item = html_writer::start_tag('li', array('class' => 'r' . $row));
2c5ec833 894 if (!empty($icons[$key])) { //test if the content has an assigned icon
26acc814 895 $item .= html_writer::tag('div', $icons[$key], array('class' => 'icon column c0'));
d9c8f425 896 }
26acc814 897 $item .= html_writer::tag('div', $string, array('class' => 'column c1'));
5d0c95a5 898 $item .= html_writer::end_tag('li');
d9c8f425 899 $lis[] = $item;
900 $row = 1 - $row; // Flip even/odd.
901 }
f2a04fc1 902 return html_writer::tag('ul', implode("\n", $lis), array('class' => 'unlist'));
d9c8f425 903 }
904
905 /**
906 * Output all the blocks in a particular region.
907 * @param string $region the name of a region on this page.
908 * @return string the HTML to be output.
909 */
910 public function blocks_for_region($region) {
911 $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
912
913 $output = '';
914 foreach ($blockcontents as $bc) {
915 if ($bc instanceof block_contents) {
916 $output .= $this->block($bc, $region);
917 } else if ($bc instanceof block_move_target) {
918 $output .= $this->block_move_target($bc);
919 } else {
920 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
921 }
922 }
923 return $output;
924 }
925
926 /**
927 * Output a place where the block that is currently being moved can be dropped.
928 * @param block_move_target $target with the necessary details.
929 * @return string the HTML to be output.
930 */
931 public function block_move_target($target) {
6ee744b3 932 return html_writer::tag('a', html_writer::tag('span', $target->text, array('class' => 'accesshide')), array('href' => $target->url, 'class' => 'blockmovetarget'));
d9c8f425 933 }
934
574fbea4 935 /**
996b1e0c 936 * Renders a special html link with attached action
574fbea4
PS
937 *
938 * @param string|moodle_url $url
939 * @param string $text HTML fragment
940 * @param component_action $action
11820bac 941 * @param array $attributes associative array of html link attributes + disabled
574fbea4
PS
942 * @return HTML fragment
943 */
c63923bd 944 public function action_link($url, $text, component_action $action = null, array $attributes=null) {
574fbea4
PS
945 if (!($url instanceof moodle_url)) {
946 $url = new moodle_url($url);
947 }
948 $link = new action_link($url, $text, $action, $attributes);
949
f14b641b 950 return $this->render($link);
574fbea4
PS
951 }
952
953 /**
954 * Implementation of action_link rendering
955 * @param action_link $link
956 * @return string HTML fragment
957 */
958 protected function render_action_link(action_link $link) {
959 global $CFG;
960
961 // A disabled link is rendered as formatted text
962 if (!empty($link->attributes['disabled'])) {
963 // do not use div here due to nesting restriction in xhtml strict
964 return html_writer::tag('span', $link->text, array('class'=>'currentlink'));
965 }
11820bac 966
574fbea4
PS
967 $attributes = $link->attributes;
968 unset($link->attributes['disabled']);
969 $attributes['href'] = $link->url;
970
971 if ($link->actions) {
f14b641b 972 if (empty($attributes['id'])) {
574fbea4
PS
973 $id = html_writer::random_id('action_link');
974 $attributes['id'] = $id;
975 } else {
976 $id = $attributes['id'];
977 }
978 foreach ($link->actions as $action) {
c80877aa 979 $this->add_action_handler($action, $id);
574fbea4
PS
980 }
981 }
982
26acc814 983 return html_writer::tag('a', $link->text, $attributes);
574fbea4
PS
984 }
985
c63923bd
PS
986
987 /**
988 * Similar to action_link, image is used instead of the text
989 *
990 * @param string|moodle_url $url A string URL or moodel_url
991 * @param pix_icon $pixicon
992 * @param component_action $action
993 * @param array $attributes associative array of html link attributes + disabled
994 * @param bool $linktext show title next to image in link
995 * @return string HTML fragment
996 */
997 public function action_icon($url, pix_icon $pixicon, component_action $action = null, array $attributes = null, $linktext=false) {
998 if (!($url instanceof moodle_url)) {
999 $url = new moodle_url($url);
1000 }
1001 $attributes = (array)$attributes;
1002
524645e7 1003 if (empty($attributes['class'])) {
c63923bd
PS
1004 // let ppl override the class via $options
1005 $attributes['class'] = 'action-icon';
1006 }
1007
1008 $icon = $this->render($pixicon);
1009
1010 if ($linktext) {
1011 $text = $pixicon->attributes['alt'];
1012 } else {
1013 $text = '';
1014 }
1015
1016 return $this->action_link($url, $text.$icon, $action, $attributes);
1017 }
1018
d9c8f425 1019 /**
0b634d75 1020 * Print a message along with button choices for Continue/Cancel
1021 *
4ed85790 1022 * If a string or moodle_url is given instead of a single_button, method defaults to post.
0b634d75 1023 *
d9c8f425 1024 * @param string $message The question to ask the user
3ba60ee1
PS
1025 * @param single_button|moodle_url|string $continue The single_button component representing the Continue answer. Can also be a moodle_url or string URL
1026 * @param single_button|moodle_url|string $cancel The single_button component representing the Cancel answer. Can also be a moodle_url or string URL
d9c8f425 1027 * @return string HTML fragment
1028 */
1029 public function confirm($message, $continue, $cancel) {
4871a238 1030 if ($continue instanceof single_button) {
11820bac 1031 // ok
4871a238
PS
1032 } else if (is_string($continue)) {
1033 $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post');
1034 } else if ($continue instanceof moodle_url) {
26eab8d4 1035 $continue = new single_button($continue, get_string('continue'), 'post');
d9c8f425 1036 } else {
4ed85790 1037 throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
d9c8f425 1038 }
1039
4871a238 1040 if ($cancel instanceof single_button) {
11820bac 1041 // ok
4871a238
PS
1042 } else if (is_string($cancel)) {
1043 $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get');
1044 } else if ($cancel instanceof moodle_url) {
26eab8d4 1045 $cancel = new single_button($cancel, get_string('cancel'), 'get');
d9c8f425 1046 } else {
4ed85790 1047 throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
d9c8f425 1048 }
1049
d9c8f425 1050 $output = $this->box_start('generalbox', 'notice');
26acc814
PS
1051 $output .= html_writer::tag('p', $message);
1052 $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons'));
d9c8f425 1053 $output .= $this->box_end();
1054 return $output;
1055 }
1056
3cd5305f 1057 /**
3ba60ee1 1058 * Returns a form with a single button.
3cd5305f 1059 *
3ba60ee1 1060 * @param string|moodle_url $url
3cd5305f
PS
1061 * @param string $label button text
1062 * @param string $method get or post submit method
3ba60ee1 1063 * @param array $options associative array {disabled, title, etc.}
3cd5305f
PS
1064 * @return string HTML fragment
1065 */
3ba60ee1 1066 public function single_button($url, $label, $method='post', array $options=null) {
574fbea4
PS
1067 if (!($url instanceof moodle_url)) {
1068 $url = new moodle_url($url);
3ba60ee1 1069 }
574fbea4
PS
1070 $button = new single_button($url, $label, $method);
1071
3ba60ee1
PS
1072 foreach ((array)$options as $key=>$value) {
1073 if (array_key_exists($key, $button)) {
1074 $button->$key = $value;
1075 }
3cd5305f
PS
1076 }
1077
3ba60ee1 1078 return $this->render($button);
3cd5305f
PS
1079 }
1080
d9c8f425 1081 /**
3ba60ee1
PS
1082 * Internal implementation of single_button rendering
1083 * @param single_button $button
d9c8f425 1084 * @return string HTML fragment
1085 */
3ba60ee1
PS
1086 protected function render_single_button(single_button $button) {
1087 $attributes = array('type' => 'submit',
1088 'value' => $button->label,
db09524d 1089 'disabled' => $button->disabled ? 'disabled' : null,
3ba60ee1
PS
1090 'title' => $button->tooltip);
1091
1092 if ($button->actions) {
1093 $id = html_writer::random_id('single_button');
1094 $attributes['id'] = $id;
1095 foreach ($button->actions as $action) {
c80877aa 1096 $this->add_action_handler($action, $id);
3ba60ee1 1097 }
d9c8f425 1098 }
d9c8f425 1099
3ba60ee1
PS
1100 // first the input element
1101 $output = html_writer::empty_tag('input', $attributes);
d9c8f425 1102
3ba60ee1
PS
1103 // then hidden fields
1104 $params = $button->url->params();
1105 if ($button->method === 'post') {
1106 $params['sesskey'] = sesskey();
1107 }
1108 foreach ($params as $var => $val) {
1109 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1110 }
d9c8f425 1111
3ba60ee1 1112 // then div wrapper for xhtml strictness
26acc814 1113 $output = html_writer::tag('div', $output);
d9c8f425 1114
3ba60ee1 1115 // now the form itself around it
eb788065 1116 $url = $button->url->out_omit_querystring(); // url without params
a6855934
PS
1117 if ($url === '') {
1118 $url = '#'; // there has to be always some action
1119 }
3ba60ee1 1120 $attributes = array('method' => $button->method,
a6855934 1121 'action' => $url,
3ba60ee1 1122 'id' => $button->formid);
26acc814 1123 $output = html_writer::tag('form', $output, $attributes);
d9c8f425 1124
3ba60ee1 1125 // and finally one more wrapper with class
26acc814 1126 return html_writer::tag('div', $output, array('class' => $button->class));
d9c8f425 1127 }
1128
a9967cf5 1129 /**
ab08be98 1130 * Returns a form with a single select widget.
a9967cf5
PS
1131 * @param moodle_url $url form action target, includes hidden fields
1132 * @param string $name name of selection field - the changing parameter in url
1133 * @param array $options list of options
1134 * @param string $selected selected element
1135 * @param array $nothing
f8dab966 1136 * @param string $formid
a9967cf5
PS
1137 * @return string HTML fragment
1138 */
f8dab966 1139 public function single_select($url, $name, array $options, $selected='', $nothing=array(''=>'choosedots'), $formid=null) {
a9967cf5
PS
1140 if (!($url instanceof moodle_url)) {
1141 $url = new moodle_url($url);
1142 }
f8dab966 1143 $select = new single_select($url, $name, $options, $selected, $nothing, $formid);
a9967cf5
PS
1144
1145 return $this->render($select);
1146 }
1147
1148 /**
1149 * Internal implementation of single_select rendering
1150 * @param single_select $select
1151 * @return string HTML fragment
1152 */
1153 protected function render_single_select(single_select $select) {
1154 $select = clone($select);
1155 if (empty($select->formid)) {
1156 $select->formid = html_writer::random_id('single_select_f');
1157 }
1158
1159 $output = '';
1160 $params = $select->url->params();
1161 if ($select->method === 'post') {
1162 $params['sesskey'] = sesskey();
1163 }
1164 foreach ($params as $name=>$value) {
1165 $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$value));
1166 }
1167
1168 if (empty($select->attributes['id'])) {
1169 $select->attributes['id'] = html_writer::random_id('single_select');
1170 }
1171
0b2cb132
PS
1172 if ($select->disabled) {
1173 $select->attributes['disabled'] = 'disabled';
1174 }
4d10e579 1175
a9967cf5
PS
1176 if ($select->tooltip) {
1177 $select->attributes['title'] = $select->tooltip;
1178 }
1179
1180 if ($select->label) {
995f2d51 1181 $output .= html_writer::label($select->label, $select->attributes['id']);
a9967cf5
PS
1182 }
1183
1184 if ($select->helpicon instanceof help_icon) {
1185 $output .= $this->render($select->helpicon);
259c165d
PS
1186 } else if ($select->helpicon instanceof old_help_icon) {
1187 $output .= $this->render($select->helpicon);
a9967cf5
PS
1188 }
1189
1190 $output .= html_writer::select($select->options, $select->name, $select->selected, $select->nothing, $select->attributes);
1191
1192 $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go')));
fb0f16a7 1193 $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('style'=>'inline'));
a9967cf5
PS
1194
1195 $nothing = empty($select->nothing) ? false : key($select->nothing);
edc28287 1196 $this->page->requires->js_init_call('M.util.init_select_autosubmit', array($select->formid, $select->attributes['id'], $nothing));
a9967cf5
PS
1197
1198 // then div wrapper for xhtml strictness
26acc814 1199 $output = html_writer::tag('div', $output);
a9967cf5
PS
1200
1201 // now the form itself around it
1202 $formattributes = array('method' => $select->method,
1203 'action' => $select->url->out_omit_querystring(),
1204 'id' => $select->formid);
26acc814 1205 $output = html_writer::tag('form', $output, $formattributes);
4d10e579
PS
1206
1207 // and finally one more wrapper with class
26acc814 1208 return html_writer::tag('div', $output, array('class' => $select->class));
4d10e579
PS
1209 }
1210
1211 /**
ab08be98 1212 * Returns a form with a url select widget.
4d10e579
PS
1213 * @param array $urls list of urls - array('/course/view.php?id=1'=>'Frontpage', ....)
1214 * @param string $selected selected element
1215 * @param array $nothing
1216 * @param string $formid
1217 * @return string HTML fragment
1218 */
1219 public function url_select(array $urls, $selected, $nothing=array(''=>'choosedots'), $formid=null) {
1220 $select = new url_select($urls, $selected, $nothing, $formid);
1221 return $this->render($select);
1222 }
1223
1224 /**
ab08be98 1225 * Internal implementation of url_select rendering
4d10e579
PS
1226 * @param single_select $select
1227 * @return string HTML fragment
1228 */
1229 protected function render_url_select(url_select $select) {
c422efcf
PS
1230 global $CFG;
1231
4d10e579
PS
1232 $select = clone($select);
1233 if (empty($select->formid)) {
1234 $select->formid = html_writer::random_id('url_select_f');
1235 }
1236
1237 if (empty($select->attributes['id'])) {
1238 $select->attributes['id'] = html_writer::random_id('url_select');
1239 }
1240
1241 if ($select->disabled) {
1242 $select->attributes['disabled'] = 'disabled';
1243 }
1244
1245 if ($select->tooltip) {
1246 $select->attributes['title'] = $select->tooltip;
1247 }
1248
1249 $output = '';
1250
1251 if ($select->label) {
995f2d51 1252 $output .= html_writer::label($select->label, $select->attributes['id']);
4d10e579
PS
1253 }
1254
1255 if ($select->helpicon instanceof help_icon) {
1256 $output .= $this->render($select->helpicon);
259c165d
PS
1257 } else if ($select->helpicon instanceof old_help_icon) {
1258 $output .= $this->render($select->helpicon);
4d10e579
PS
1259 }
1260
d4dcfc6b
DM
1261 // For security reasons, the script course/jumpto.php requires URL starting with '/'. To keep
1262 // backward compatibility, we are removing heading $CFG->wwwroot from URLs here.
c422efcf
PS
1263 $urls = array();
1264 foreach ($select->urls as $k=>$v) {
d4dcfc6b
DM
1265 if (is_array($v)) {
1266 // optgroup structure
1267 foreach ($v as $optgrouptitle => $optgroupoptions) {
1268 foreach ($optgroupoptions as $optionurl => $optiontitle) {
1269 if (empty($optionurl)) {
1270 $safeoptionurl = '';
1271 } else if (strpos($optionurl, $CFG->wwwroot.'/') === 0) {
1272 // debugging('URLs passed to url_select should be in local relative form - please fix the code.', DEBUG_DEVELOPER);
1273 $safeoptionurl = str_replace($CFG->wwwroot, '', $optionurl);
1274 } else if (strpos($optionurl, '/') !== 0) {
1275 debugging("Invalid url_select urls parameter inside optgroup: url '$optionurl' is not local relative url!");
1276 continue;
1277 } else {
1278 $safeoptionurl = $optionurl;
1279 }
1280 $urls[$k][$optgrouptitle][$safeoptionurl] = $optiontitle;
1281 }
1282 }
1283 } else {
1284 // plain list structure
1285 if (empty($k)) {
1286 // nothing selected option
1287 } else if (strpos($k, $CFG->wwwroot.'/') === 0) {
1288 $k = str_replace($CFG->wwwroot, '', $k);
1289 } else if (strpos($k, '/') !== 0) {
1290 debugging("Invalid url_select urls parameter: url '$k' is not local relative url!");
1291 continue;
1292 }
1293 $urls[$k] = $v;
1294 }
1295 }
1296 $selected = $select->selected;
1297 if (!empty($selected)) {
1298 if (strpos($select->selected, $CFG->wwwroot.'/') === 0) {
1299 $selected = str_replace($CFG->wwwroot, '', $selected);
1300 } else if (strpos($selected, '/') !== 0) {
1301 debugging("Invalid value of parameter 'selected': url '$selected' is not local relative url!");
c422efcf 1302 }
c422efcf
PS
1303 }
1304
4d10e579 1305 $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()));
d4dcfc6b 1306 $output .= html_writer::select($urls, 'jump', $selected, $select->nothing, $select->attributes);
4d10e579 1307
15e48a1a
SM
1308 if (!$select->showbutton) {
1309 $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go')));
1310 $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('style'=>'inline'));
1311 $nothing = empty($select->nothing) ? false : key($select->nothing);
1312 $output .= $this->page->requires->js_init_call('M.util.init_url_select', array($select->formid, $select->attributes['id'], $nothing));
1313 } else {
1314 $output .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>$select->showbutton));
1315 }
4d10e579
PS
1316
1317 // then div wrapper for xhtml strictness
26acc814 1318 $output = html_writer::tag('div', $output);
4d10e579
PS
1319
1320 // now the form itself around it
1321 $formattributes = array('method' => 'post',
1322 'action' => new moodle_url('/course/jumpto.php'),
1323 'id' => $select->formid);
26acc814 1324 $output = html_writer::tag('form', $output, $formattributes);
a9967cf5
PS
1325
1326 // and finally one more wrapper with class
26acc814 1327 return html_writer::tag('div', $output, array('class' => $select->class));
a9967cf5
PS
1328 }
1329
d9c8f425 1330 /**
1331 * Returns a string containing a link to the user documentation.
1332 * Also contains an icon by default. Shown to teachers and admin only.
1333 * @param string $path The page link after doc root and language, no leading slash.
1334 * @param string $text The text to be displayed for the link
996b1e0c 1335 * @return string
d9c8f425 1336 */
010e02b4 1337 public function doc_link($path, $text = '') {
8ae8bf8a
PS
1338 global $CFG;
1339
000c278c 1340 $icon = $this->pix_icon('docs', $text, 'moodle', array('class'=>'iconhelp'));
8ae8bf8a 1341
000c278c 1342 $url = new moodle_url(get_docs_url($path));
8ae8bf8a 1343
c80877aa 1344 $attributes = array('href'=>$url);
d9c8f425 1345 if (!empty($CFG->doctonewwindow)) {
c80877aa 1346 $attributes['id'] = $this->add_action_handler(new popup_action('click', $url));
d9c8f425 1347 }
1adaa404 1348
26acc814 1349 return html_writer::tag('a', $icon.$text, $attributes);
d9c8f425 1350 }
1351
000c278c
PS
1352 /**
1353 * Render icon
1354 * @param string $pix short pix name
1355 * @param string $alt mandatory alt attribute
eb557002 1356 * @param string $component standard compoennt name like 'moodle', 'mod_forum', etc.
000c278c
PS
1357 * @param array $attributes htm lattributes
1358 * @return string HTML fragment
1359 */
1360 public function pix_icon($pix, $alt, $component='moodle', array $attributes = null) {
1361 $icon = new pix_icon($pix, $alt, $component, $attributes);
1362 return $this->render($icon);
1363 }
1364
1365 /**
1366 * Render icon
1367 * @param pix_icon $icon
1368 * @return string HTML fragment
1369 */
ce0110bf 1370 protected function render_pix_icon(pix_icon $icon) {
000c278c
PS
1371 $attributes = $icon->attributes;
1372 $attributes['src'] = $this->pix_url($icon->pix, $icon->component);
c80877aa 1373 return html_writer::empty_tag('img', $attributes);
000c278c
PS
1374 }
1375
d63c5073
DM
1376 /**
1377 * Render emoticon
1378 * @param pix_emoticon $emoticon
1379 * @return string HTML fragment
1380 */
1381 protected function render_pix_emoticon(pix_emoticon $emoticon) {
1382 $attributes = $emoticon->attributes;
1383 $attributes['src'] = $this->pix_url($emoticon->pix, $emoticon->component);
1384 return html_writer::empty_tag('img', $attributes);
1385 }
1386
a09aeee4
AD
1387 /**
1388 * Produces the html that represents this rating in the UI
1389 * @param $page the page object on which this rating will appear
1390 */
1391 function render_rating(rating $rating) {
7ac928a7 1392 global $CFG, $USER;
a09aeee4
AD
1393 static $havesetupjavascript = false;
1394
63e87951
AD
1395 if( $rating->settings->aggregationmethod == RATING_AGGREGATE_NONE ){
1396 return null;//ratings are turned off
1397 }
1398
b1721f67
AD
1399 $useajax = !empty($CFG->enableajax);
1400
1401 //include required Javascript
1402 if( !$havesetupjavascript && $useajax ) {
3dcdf440 1403 $this->page->requires->js_init_call('M.core_rating.init');
a09aeee4
AD
1404 $havesetupjavascript = true;
1405 }
1406
63e87951
AD
1407 //check the item we're rating was created in the assessable time window
1408 $inassessablewindow = true;
1409 if ( $rating->settings->assesstimestart && $rating->settings->assesstimefinish ) {
55d95d90 1410 if ($rating->itemtimecreated < $rating->settings->assesstimestart || $rating->itemtimecreated > $rating->settings->assesstimefinish) {
63e87951 1411 $inassessablewindow = false;
a09aeee4 1412 }
63e87951 1413 }
a09aeee4 1414
63e87951
AD
1415 $strrate = get_string("rate", "rating");
1416 $ratinghtml = ''; //the string we'll return
1417
d251b259 1418 //permissions check - can they view the aggregate?
66c34e9c 1419 $canviewaggregate = false;
d251b259 1420
66c34e9c
AD
1421 //if its the current user's item and they have permission to view the aggregate on their own items
1422 if ( $rating->itemuserid==$USER->id && $rating->settings->permissions->view && $rating->settings->pluginpermissions->view) {
1423 $canviewaggregate = true;
1424 }
1425
1426 //if the item doesnt belong to anyone or its another user's items and they can see the aggregate on items they don't own
1427 //Note that viewany doesnt mean you can see the aggregate or ratings of your own items
1428 if ( (empty($rating->itemuserid) or $rating->itemuserid!=$USER->id) && $rating->settings->permissions->viewany && $rating->settings->pluginpermissions->viewany ) {
1429 $canviewaggregate = true;
1430 }
1431
1432 if ($canviewaggregate==true) {
d251b259
AD
1433 $aggregatelabel = '';
1434 switch ($rating->settings->aggregationmethod) {
1435 case RATING_AGGREGATE_AVERAGE :
07f05a04 1436 $aggregatelabel .= get_string("aggregateavg", "rating");
d251b259
AD
1437 break;
1438 case RATING_AGGREGATE_COUNT :
07f05a04 1439 $aggregatelabel .= get_string("aggregatecount", "rating");
d251b259
AD
1440 break;
1441 case RATING_AGGREGATE_MAXIMUM :
07f05a04 1442 $aggregatelabel .= get_string("aggregatemax", "rating");
d251b259
AD
1443 break;
1444 case RATING_AGGREGATE_MINIMUM :
07f05a04 1445 $aggregatelabel .= get_string("aggregatemin", "rating");
d251b259
AD
1446 break;
1447 case RATING_AGGREGATE_SUM :
07f05a04 1448 $aggregatelabel .= get_string("aggregatesum", "rating");
d251b259
AD
1449 break;
1450 }
c6de9cef 1451 $aggregatelabel .= get_string('labelsep', 'langconfig');
a09aeee4 1452
d251b259
AD
1453 //$scalemax = 0;//no longer displaying scale max
1454 $aggregatestr = '';
a09aeee4 1455
50e7d9da
AD
1456 //only display aggregate if aggregation method isn't COUNT
1457 if ($rating->aggregate && $rating->settings->aggregationmethod!= RATING_AGGREGATE_COUNT) {
1458 if ($rating->settings->aggregationmethod!= RATING_AGGREGATE_SUM && is_array($rating->settings->scale->scaleitems)) {
d251b259 1459 $aggregatestr .= $rating->settings->scale->scaleitems[round($rating->aggregate)];//round aggregate as we're using it as an index
63e87951 1460 }
50e7d9da 1461 else { //aggregation is SUM or the scale is numeric
d251b259 1462 $aggregatestr .= round($rating->aggregate,1);
63e87951 1463 }
d251b259 1464 } else {
42c32efd 1465 $aggregatestr = '';
d251b259
AD
1466 }
1467
771b3fbe 1468 $countstr = html_writer::start_tag('span', array('id'=>"ratingcount{$rating->itemid}"));
d251b259 1469 if ($rating->count>0) {
771b3fbe 1470 $countstr .= "({$rating->count})";
d251b259 1471 }
771b3fbe 1472 $countstr .= html_writer::end_tag('span');
63e87951 1473
d251b259
AD
1474 //$aggregatehtml = "{$ratingstr} / $scalemax ({$rating->count}) ";
1475 $aggregatehtml = "<span id='ratingaggregate{$rating->itemid}'>{$aggregatestr}</span> $countstr ";
63e87951 1476
c6de9cef 1477 $ratinghtml .= html_writer::tag('span', $aggregatelabel, array('class'=>'rating-aggregate-label'));
d251b259
AD
1478 if ($rating->settings->permissions->viewall && $rating->settings->pluginpermissions->viewall) {
1479 $url = "/rating/index.php?contextid={$rating->context->id}&itemid={$rating->itemid}&scaleid={$rating->settings->scale->id}";
1480 $nonpopuplink = new moodle_url($url);
1481 $popuplink = new moodle_url("$url&popup=1");
a09aeee4 1482
d251b259 1483 $action = new popup_action('click', $popuplink, 'ratings', array('height' => 400, 'width' => 600));
c6de9cef 1484 $ratinghtml .= $this->action_link($nonpopuplink, $aggregatehtml, $action);
d251b259 1485 } else {
c6de9cef 1486 $ratinghtml .= $aggregatehtml;
a09aeee4 1487 }
d251b259 1488 }
a09aeee4 1489
d251b259 1490 $formstart = null;
71c03ac1 1491 //if the item doesn't belong to the current user, the user has permission to rate
d251b259
AD
1492 //and we're within the assessable period
1493 if ($rating->itemuserid!=$USER->id
1494 && $rating->settings->permissions->rate
1495 && $rating->settings->pluginpermissions->rate
1496 && $inassessablewindow) {
4d2ee4c2 1497
771b3fbe
AD
1498 //start the rating form
1499 $formstart = html_writer::start_tag('form',
1500 array('id'=>"postrating{$rating->itemid}", 'class'=>'postratingform', 'method'=>'post', 'action'=>"{$CFG->wwwroot}/rating/rate.php"));
1501
1502 $formstart .= html_writer::start_tag('div', array('class'=>'ratingform'));
1503
1504 //add the hidden inputs
1505
1506 $attributes = array('type'=>'hidden', 'class'=>'ratinginput', 'name'=>'contextid', 'value'=>$rating->context->id);
1507 $formstart .= html_writer::empty_tag('input', $attributes);
1508
1509 $attributes['name'] = 'itemid';
1510 $attributes['value'] = $rating->itemid;
1511 $formstart .= html_writer::empty_tag('input', $attributes);
1512
1513 $attributes['name'] = 'scaleid';
1514 $attributes['value'] = $rating->settings->scale->id;
1515 $formstart .= html_writer::empty_tag('input', $attributes);
1516
1517 $attributes['name'] = 'returnurl';
1518 $attributes['value'] = $rating->settings->returnurl;
1519 $formstart .= html_writer::empty_tag('input', $attributes);
1520
1521 $attributes['name'] = 'rateduserid';
1522 $attributes['value'] = $rating->itemuserid;
1523 $formstart .= html_writer::empty_tag('input', $attributes);
1524
1525 $attributes['name'] = 'aggregation';
1526 $attributes['value'] = $rating->settings->aggregationmethod;
1527 $formstart .= html_writer::empty_tag('input', $attributes);
1528
3180bc2c
AD
1529 $attributes['name'] = 'sesskey';
1530 $attributes['value'] = sesskey();;
1531 $formstart .= html_writer::empty_tag('input', $attributes);
1532
d251b259
AD
1533 if (empty($ratinghtml)) {
1534 $ratinghtml .= $strrate.': ';
1535 }
63e87951 1536
d251b259 1537 $ratinghtml = $formstart.$ratinghtml;
63e87951 1538
d251b259
AD
1539 //generate an array of values for numeric scales
1540 $scalearray = $rating->settings->scale->scaleitems;
1541 if (!is_array($scalearray)) { //almost certainly a numerical scale
996b1e0c 1542 $intscalearray = intval($scalearray);//just in case they've passed "5" instead of 5
9f60f914
AD
1543 $scalearray = array();
1544 if( is_int($intscalearray) && $intscalearray>0 ) {
d251b259
AD
1545 for($i=0; $i<=$rating->settings->scale->scaleitems; $i++) {
1546 $scalearray[$i] = $i;
6c5fcef7 1547 }
a09aeee4 1548 }
d251b259 1549 }
6c5fcef7 1550
d251b259
AD
1551 $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $scalearray;
1552 $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid));
a09aeee4 1553
d251b259 1554 //output submit button
4d2ee4c2 1555
771b3fbe
AD
1556 $ratinghtml .= html_writer::start_tag('span', array('class'=>"ratingsubmit"));
1557
1558 $attributes = array('type'=>'submit', 'class'=>'postratingmenusubmit', 'id'=>'postratingsubmit'.$rating->itemid, 'value'=>s(get_string('rate', 'rating')));
1559 $ratinghtml .= html_writer::empty_tag('input', $attributes);
a09aeee4 1560
d251b259
AD
1561 if (is_array($rating->settings->scale->scaleitems)) {
1562 $ratinghtml .= $this->help_icon_scale($rating->settings->scale->courseid, $rating->settings->scale);
a09aeee4 1563 }
771b3fbe
AD
1564 $ratinghtml .= html_writer::end_tag('span');
1565 $ratinghtml .= html_writer::end_tag('div');
1566 $ratinghtml .= html_writer::end_tag('form');
a09aeee4
AD
1567 }
1568
63e87951 1569 return $ratinghtml;
a09aeee4
AD
1570 }
1571
d9c8f425 1572 /*
1573 * Centered heading with attached help button (same title text)
1574 * and optional icon attached
4bcc5118 1575 * @param string $text A heading text
53a78cef 1576 * @param string $helpidentifier The keyword that defines a help page
4bcc5118
PS
1577 * @param string $component component name
1578 * @param string|moodle_url $icon
1579 * @param string $iconalt icon alt text
d9c8f425 1580 * @return string HTML fragment
1581 */
53a78cef 1582 public function heading_with_help($text, $helpidentifier, $component='moodle', $icon='', $iconalt='') {
4bcc5118
PS
1583 $image = '';
1584 if ($icon) {
0029a917 1585 $image = $this->pix_icon($icon, $iconalt, $component, array('class'=>'icon'));
d9c8f425 1586 }
4bcc5118 1587
259c165d
PS
1588 $help = '';
1589 if ($helpidentifier) {
1590 $help = $this->help_icon($helpidentifier, $component);
1591 }
4bcc5118
PS
1592
1593 return $this->heading($image.$text.$help, 2, 'main help');
d9c8f425 1594 }
1595
1596 /**
1597 * Print a help icon.
1598 *
cb616be8 1599 * @deprecated since Moodle 2.0
4bcc5118 1600 * @param string $page The keyword that defines a help page
bf11293a 1601 * @param string $title A descriptive text for accessibility only
4bcc5118 1602 * @param string $component component name
bf11293a
PS
1603 * @param string|bool $linktext true means use $title as link text, string means link text value
1604 * @return string HTML fragment
1605 */
596509e4 1606 public function old_help_icon($helpidentifier, $title, $component = 'moodle', $linktext = '') {
cb616be8 1607 debugging('The method old_help_icon() is deprecated, please fix the code and use help_icon() method instead', DEBUG_DEVELOPER);
596509e4 1608 $icon = new old_help_icon($helpidentifier, $title, $component);
bf11293a
PS
1609 if ($linktext === true) {
1610 $icon->linktext = $title;
1611 } else if (!empty($linktext)) {
1612 $icon->linktext = $linktext;
1613 }
1614 return $this->render($icon);
1615 }
4bcc5118 1616
bf11293a
PS
1617 /**
1618 * Implementation of user image rendering.
1619 * @param help_icon $helpicon
1620 * @return string HTML fragment
d9c8f425 1621 */
596509e4 1622 protected function render_old_help_icon(old_help_icon $helpicon) {
bf11293a 1623 global $CFG;
d9c8f425 1624
bf11293a
PS
1625 // first get the help image icon
1626 $src = $this->pix_url('help');
d9c8f425 1627
bf11293a
PS
1628 if (empty($helpicon->linktext)) {
1629 $alt = $helpicon->title;
1630 } else {
1631 $alt = get_string('helpwiththis');
1632 }
1633
1634 $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>'iconhelp');
1635 $output = html_writer::empty_tag('img', $attributes);
1636
1637 // add the link text if given
1638 if (!empty($helpicon->linktext)) {
1639 // the spacing has to be done through CSS
1640 $output .= $helpicon->linktext;
d9c8f425 1641 }
1642
53a78cef
PS
1643 // now create the link around it
1644 $url = new moodle_url('/help.php', array('component' => $helpicon->component, 'identifier' => $helpicon->helpidentifier, 'lang'=>current_language()));
bf11293a
PS
1645
1646 // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip
1647 $title = get_string('helpprefix2', '', trim($helpicon->title, ". \t"));
1648
1649 $attributes = array('href'=>$url, 'title'=>$title);
1650 $id = html_writer::random_id('helpicon');
1651 $attributes['id'] = $id;
26acc814 1652 $output = html_writer::tag('a', $output, $attributes);
8af8be4a
DM
1653
1654 $this->page->requires->js_init_call('M.util.help_icon.add', array(array('id'=>$id, 'url'=>$url->out(false))));
1655
bf11293a 1656 // and finally span
26acc814 1657 return html_writer::tag('span', $output, array('class' => 'helplink'));
d9c8f425 1658 }
1659
259c165d
PS
1660 /**
1661 * Print a help icon.
1662 *
1663 * @param string $identifier The keyword that defines a help page
1664 * @param string $component component name
1665 * @param string|bool $linktext true means use $title as link text, string means link text value
1666 * @return string HTML fragment
1667 */
1668 public function help_icon($identifier, $component = 'moodle', $linktext = '') {
2cf81209 1669 $icon = new help_icon($identifier, $component);
259c165d
PS
1670 $icon->diag_strings();
1671 if ($linktext === true) {
1672 $icon->linktext = get_string($icon->identifier, $icon->component);
1673 } else if (!empty($linktext)) {
1674 $icon->linktext = $linktext;
1675 }
1676 return $this->render($icon);
1677 }
1678
1679 /**
1680 * Implementation of user image rendering.
1681 * @param help_icon $helpicon
1682 * @return string HTML fragment
1683 */
1684 protected function render_help_icon(help_icon $helpicon) {
1685 global $CFG;
1686
1687 // first get the help image icon
1688 $src = $this->pix_url('help');
1689
1690 $title = get_string($helpicon->identifier, $helpicon->component);
1691
1692 if (empty($helpicon->linktext)) {
1693 $alt = $title;
1694 } else {
1695 $alt = get_string('helpwiththis');
1696 }
1697
1698 $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>'iconhelp');
1699 $output = html_writer::empty_tag('img', $attributes);
1700
1701 // add the link text if given
1702 if (!empty($helpicon->linktext)) {
1703 // the spacing has to be done through CSS
1704 $output .= $helpicon->linktext;
1705 }
1706
1707 // now create the link around it
1708 $url = new moodle_url('/help.php', array('component' => $helpicon->component, 'identifier' => $helpicon->identifier, 'lang'=>current_language()));
1709
1710 // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip
1711 $title = get_string('helpprefix2', '', trim($title, ". \t"));
1712
1713 $attributes = array('href'=>$url, 'title'=>$title);
1714 $id = html_writer::random_id('helpicon');
1715 $attributes['id'] = $id;
259c165d
PS
1716 $output = html_writer::tag('a', $output, $attributes);
1717
2cf81209
SH
1718 $this->page->requires->js_init_call('M.util.help_icon.add', array(array('id'=>$id, 'url'=>$url->out(false))));
1719
259c165d
PS
1720 // and finally span
1721 return html_writer::tag('span', $output, array('class' => 'helplink'));
1722 }
1723
d9c8f425 1724 /**
4bcc5118 1725 * Print scale help icon.
d9c8f425 1726 *
4bcc5118
PS
1727 * @param int $courseid
1728 * @param object $scale instance
1729 * @return string HTML fragment
d9c8f425 1730 */
4bcc5118
PS
1731 public function help_icon_scale($courseid, stdClass $scale) {
1732 global $CFG;
02f64f97 1733
4bcc5118 1734 $title = get_string('helpprefix2', '', $scale->name) .' ('.get_string('newwindow').')';
02f64f97 1735
0029a917 1736 $icon = $this->pix_icon('help', get_string('scales'), 'moodle', array('class'=>'iconhelp'));
02f64f97 1737
68bf577b
AD
1738 $scaleid = abs($scale->id);
1739
1740 $link = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scaleid));
230ec401 1741 $action = new popup_action('click', $link, 'ratingscale');
02f64f97 1742
26acc814 1743 return html_writer::tag('span', $this->action_link($link, $icon, $action), array('class' => 'helplink'));
d9c8f425 1744 }
1745
1746 /**
1747 * Creates and returns a spacer image with optional line break.
1748 *
0029a917
PS
1749 * @param array $attributes
1750 * @param boo spacer
d9c8f425 1751 * @return string HTML fragment
1752 */
0029a917
PS
1753 public function spacer(array $attributes = null, $br = false) {
1754 $attributes = (array)$attributes;
1755 if (empty($attributes['width'])) {
1756 $attributes['width'] = 1;
1ba862ec 1757 }
e1a5a9cc 1758 if (empty($attributes['height'])) {
0029a917 1759 $attributes['height'] = 1;
d9c8f425 1760 }
0029a917 1761 $attributes['class'] = 'spacer';
d9c8f425 1762
0029a917 1763 $output = $this->pix_icon('spacer', '', 'moodle', $attributes);
b65bfc3e 1764
0029a917 1765 if (!empty($br)) {
1ba862ec
PS
1766 $output .= '<br />';
1767 }
d9c8f425 1768
1769 return $output;
1770 }
1771
d9c8f425 1772 /**
1773 * Print the specified user's avatar.
1774 *
5d0c95a5 1775 * User avatar may be obtained in two ways:
d9c8f425 1776 * <pre>
812dbaf7
PS
1777 * // Option 1: (shortcut for simple cases, preferred way)
1778 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
1779 * $OUTPUT->user_picture($user, array('popup'=>true));
1780 *
5d0c95a5
PS
1781 * // Option 2:
1782 * $userpic = new user_picture($user);
d9c8f425 1783 * // Set properties of $userpic
812dbaf7 1784 * $userpic->popup = true;
5d0c95a5 1785 * $OUTPUT->render($userpic);
d9c8f425 1786 * </pre>
1787 *
5d0c95a5 1788 * @param object Object with at least fields id, picture, imagealt, firstname, lastname
812dbaf7 1789 * If any of these are missing, the database is queried. Avoid this
d9c8f425 1790 * if at all possible, particularly for reports. It is very bad for performance.
812dbaf7
PS
1791 * @param array $options associative array with user picture options, used only if not a user_picture object,
1792 * options are:
1793 * - courseid=$this->page->course->id (course id of user profile in link)
1794 * - size=35 (size of image)
1795 * - link=true (make image clickable - the link leads to user profile)
1796 * - popup=false (open in popup)
1797 * - alttext=true (add image alt attribute)
5d0c95a5 1798 * - class = image class attribute (default 'userpicture')
d9c8f425 1799 * @return string HTML fragment
1800 */
5d0c95a5
PS
1801 public function user_picture(stdClass $user, array $options = null) {
1802 $userpicture = new user_picture($user);
1803 foreach ((array)$options as $key=>$value) {
1804 if (array_key_exists($key, $userpicture)) {
1805 $userpicture->$key = $value;
1806 }
1807 }
1808 return $this->render($userpicture);
1809 }
1810
1811 /**
1812 * Internal implementation of user image rendering.
1813 * @param user_picture $userpicture
1814 * @return string
1815 */
1816 protected function render_user_picture(user_picture $userpicture) {
1817 global $CFG, $DB;
812dbaf7 1818
5d0c95a5
PS
1819 $user = $userpicture->user;
1820
1821 if ($userpicture->alttext) {
1822 if (!empty($user->imagealt)) {
1823 $alt = $user->imagealt;
1824 } else {
1825 $alt = get_string('pictureof', '', fullname($user));
1826 }
d9c8f425 1827 } else {
97c10099 1828 $alt = '';
5d0c95a5
PS
1829 }
1830
1831 if (empty($userpicture->size)) {
1832 $file = 'f2';
1833 $size = 35;
1834 } else if ($userpicture->size === true or $userpicture->size == 1) {
1835 $file = 'f1';
1836 $size = 100;
1837 } else if ($userpicture->size >= 50) {
1838 $file = 'f1';
1839 $size = $userpicture->size;
1840 } else {
1841 $file = 'f2';
1842 $size = $userpicture->size;
d9c8f425 1843 }
1844
5d0c95a5 1845 $class = $userpicture->class;
d9c8f425 1846
edfd6a5e
PS
1847 if ($user->picture == 1) {
1848 $usercontext = get_context_instance(CONTEXT_USER, $user->id);
1849 $src = moodle_url::make_pluginfile_url($usercontext->id, 'user', 'icon', NULL, '/', $file);
1850
1851 } else if ($user->picture == 2) {
1852 //TODO: gravatar user icon support
1853
5d0c95a5
PS
1854 } else { // Print default user pictures (use theme version if available)
1855 $class .= ' defaultuserpic';
1856 $src = $this->pix_url('u/' . $file);
1857 }
d9c8f425 1858
29cf6631 1859 $attributes = array('src'=>$src, 'alt'=>$alt, 'title'=>$alt, 'class'=>$class, 'width'=>$size, 'height'=>$size);
5d0c95a5
PS
1860
1861 // get the image html output fisrt
1862 $output = html_writer::empty_tag('img', $attributes);;
1863
1864 // then wrap it in link if needed
1865 if (!$userpicture->link) {
1866 return $output;
d9c8f425 1867 }
1868
5d0c95a5
PS
1869 if (empty($userpicture->courseid)) {
1870 $courseid = $this->page->course->id;
1871 } else {
1872 $courseid = $userpicture->courseid;
1873 }
1874
03d9401e
MD
1875 if ($courseid == SITEID) {
1876 $url = new moodle_url('/user/profile.php', array('id' => $user->id));
1877 } else {
1878 $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid));
1879 }
5d0c95a5
PS
1880
1881 $attributes = array('href'=>$url);
1882
1883 if ($userpicture->popup) {
1884 $id = html_writer::random_id('userpicture');
1885 $attributes['id'] = $id;
c80877aa 1886 $this->add_action_handler(new popup_action('click', $url), $id);
5d0c95a5
PS
1887 }
1888
26acc814 1889 return html_writer::tag('a', $output, $attributes);
d9c8f425 1890 }
b80ef420 1891
b80ef420
DC
1892 /**
1893 * Internal implementation of file tree viewer items rendering.
1894 * @param array $dir
1895 * @return string
1896 */
1897 public function htmllize_file_tree($dir) {
1898 if (empty($dir['subdirs']) and empty($dir['files'])) {
1899 return '';
1900 }
1901 $result = '<ul>';
1902 foreach ($dir['subdirs'] as $subdir) {
1903 $result .= '<li>'.s($subdir['dirname']).' '.$this->htmllize_file_tree($subdir).'</li>';
1904 }
1905 foreach ($dir['files'] as $file) {
1906 $filename = $file->get_filename();
1907 $result .= '<li><span>'.html_writer::link($file->fileurl, $filename).'</span></li>';
1908 }
1909 $result .= '</ul>';
1910
1911 return $result;
1912 }
bb496de7
DC
1913 /**
1914 * Print the file picker
1915 *
1916 * <pre>
1917 * $OUTPUT->file_picker($options);
1918 * </pre>
1919 *
1920 * @param array $options associative array with file manager options
1921 * options are:
1922 * maxbytes=>-1,
1923 * itemid=>0,
1924 * client_id=>uniqid(),
1925 * acepted_types=>'*',
1926 * return_types=>FILE_INTERNAL,
1927 * context=>$PAGE->context
1928 * @return string HTML fragment
1929 */
1930 public function file_picker($options) {
1931 $fp = new file_picker($options);
1932 return $this->render($fp);
1933 }
b80ef420
DC
1934 /**
1935 * Internal implementation of file picker rendering.
1936 * @param file_picker $fp
1937 * @return string
1938 */
bb496de7
DC
1939 public function render_file_picker(file_picker $fp) {
1940 global $CFG, $OUTPUT, $USER;
1941 $options = $fp->options;
1942 $client_id = $options->client_id;
1943 $strsaved = get_string('filesaved', 'repository');
1944 $straddfile = get_string('openpicker', 'repository');
1945 $strloading = get_string('loading', 'repository');
1946 $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
1947
1948 $currentfile = $options->currentfile;
1949 if (empty($currentfile)) {
1950 $currentfile = get_string('nofilesattached', 'repository');
1951 }
b817205b
DC
1952 if ($options->maxbytes) {
1953 $size = $options->maxbytes;
1954 } else {
1955 $size = get_max_upload_file_size();
1956 }
513aed3c 1957 if ($size == -1) {
831399c4 1958 $maxsize = '';
513aed3c
DC
1959 } else {
1960 $maxsize = get_string('maxfilesize', 'moodle', display_size($size));
1961 }
bb496de7
DC
1962 $html = <<<EOD
1963<div class="filemanager-loading mdl-align" id='filepicker-loading-{$client_id}'>
1964$icon_progress
1965</div>
1966<div id="filepicker-wrapper-{$client_id}" class="mdl-left" style="display:none">
1967 <div>
26c137b1 1968 <input type="button" id="filepicker-button-{$client_id}" value="{$straddfile}" />
fa7f2a45 1969 <span> $maxsize </span>
bb496de7
DC
1970 </div>
1971EOD;
1972 if ($options->env != 'url') {
1973 $html .= <<<EOD
1974 <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist">$currentfile</div>
1975EOD;
1976 }
1977 $html .= '</div>';
1978 return $html;
1979 }
d9c8f425 1980
1981 /**
1982 * Prints the 'Update this Modulename' button that appears on module pages.
1983 *
1984 * @param string $cmid the course_module id.
1985 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
1986 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
1987 */
1988 public function update_module_button($cmid, $modulename) {
1989 global $CFG;
1990 if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) {
1991 $modulename = get_string('modulename', $modulename);
1992 $string = get_string('updatethis', '', $modulename);
3ba60ee1
PS
1993 $url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
1994 return $this->single_button($url, $string);
d9c8f425 1995 } else {
1996 return '';
1997 }
1998 }
1999
2000 /**
2001 * Prints a "Turn editing on/off" button in a form.
2002 * @param moodle_url $url The URL + params to send through when clicking the button
2003 * @return string HTML the button
2004 */
2005 public function edit_button(moodle_url $url) {
3362dfdc
EL
2006
2007 $url->param('sesskey', sesskey());
2008 if ($this->page->user_is_editing()) {
2009 $url->param('edit', 'off');
2010 $editstring = get_string('turneditingoff');
d9c8f425 2011 } else {
3362dfdc
EL
2012 $url->param('edit', 'on');
2013 $editstring = get_string('turneditingon');
d9c8f425 2014 }
2015
3362dfdc 2016 return $this->single_button($url, $editstring);
d9c8f425 2017 }
2018
d9c8f425 2019 /**
2020 * Prints a simple button to close a window
2021 *
d9c8f425 2022 * @param string $text The lang string for the button's label (already output from get_string())
3ba60ee1 2023 * @return string html fragment
d9c8f425 2024 */
7a5c78e0 2025 public function close_window_button($text='') {
d9c8f425 2026 if (empty($text)) {
2027 $text = get_string('closewindow');
2028 }
a6855934
PS
2029 $button = new single_button(new moodle_url('#'), $text, 'get');
2030 $button->add_action(new component_action('click', 'close_window'));
3ba60ee1
PS
2031
2032 return $this->container($this->render($button), 'closewindow');
d9c8f425 2033 }
2034
d9c8f425 2035 /**
2036 * Output an error message. By default wraps the error message in <span class="error">.
2037 * If the error message is blank, nothing is output.
2038 * @param string $message the error message.
2039 * @return string the HTML to output.
2040 */
2041 public function error_text($message) {
2042 if (empty($message)) {
2043 return '';
2044 }
26acc814 2045 return html_writer::tag('span', $message, array('class' => 'error'));
d9c8f425 2046 }
2047
2048 /**
2049 * Do not call this function directly.
2050 *
2051 * To terminate the current script with a fatal error, call the {@link print_error}
2052 * function, or throw an exception. Doing either of those things will then call this
2053 * function to display the error, before terminating the execution.
2054 *
2055 * @param string $message The message to output
2056 * @param string $moreinfourl URL where more info can be found about the error
2057 * @param string $link Link for the Continue button
2058 * @param array $backtrace The execution backtrace
2059 * @param string $debuginfo Debugging information
d9c8f425 2060 * @return string the HTML to output.
2061 */
83267ec0 2062 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
6bd8d7e7 2063 global $CFG;
d9c8f425 2064
2065 $output = '';
6f8f4d83 2066 $obbuffer = '';
e57c283d 2067
d9c8f425 2068 if ($this->has_started()) {
50764d37
PS
2069 // we can not always recover properly here, we have problems with output buffering,
2070 // html tables, etc.
d9c8f425 2071 $output .= $this->opencontainers->pop_all_but_last();
50764d37 2072
d9c8f425 2073 } else {
50764d37
PS
2074 // It is really bad if library code throws exception when output buffering is on,
2075 // because the buffered text would be printed before our start of page.
2076 // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini
6bd8d7e7 2077 error_reporting(0); // disable notices from gzip compression, etc.
50764d37 2078 while (ob_get_level() > 0) {
2cadd443
PS
2079 $buff = ob_get_clean();
2080 if ($buff === false) {
2081 break;
2082 }
2083 $obbuffer .= $buff;
50764d37 2084 }
6bd8d7e7 2085 error_reporting($CFG->debug);
6f8f4d83 2086
d9c8f425 2087 // Header not yet printed
85309744 2088 if (isset($_SERVER['SERVER_PROTOCOL'])) {
78946b9b
PS
2089 // server protocol should be always present, because this render
2090 // can not be used from command line or when outputting custom XML
85309744
PS
2091 @header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
2092 }
eb5bdb35 2093 $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
7fde1e4b 2094 $this->page->set_url('/'); // no url
191b267b 2095 //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-(
dcfb9b78 2096 $this->page->set_title(get_string('error'));
8093188f 2097 $this->page->set_heading($this->page->course->fullname);
d9c8f425 2098 $output .= $this->header();
2099 }
2100
2101 $message = '<p class="errormessage">' . $message . '</p>'.
2102 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
2103 get_string('moreinformation') . '</a></p>';
2104 $output .= $this->box($message, 'errorbox');
2105
6f8f4d83
PS
2106 if (debugging('', DEBUG_DEVELOPER)) {
2107 if (!empty($debuginfo)) {
c5d18164
PS
2108 $debuginfo = s($debuginfo); // removes all nasty JS
2109 $debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines
2110 $output .= $this->notification('<strong>Debug info:</strong> '.$debuginfo, 'notifytiny');
6f8f4d83
PS
2111 }
2112 if (!empty($backtrace)) {
2113 $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny');
2114 }
2115 if ($obbuffer !== '' ) {
2116 $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny');
2117 }
d9c8f425 2118 }
2119
2120 if (!empty($link)) {
2121 $output .= $this->continue_button($link);
2122 }
2123
2124 $output .= $this->footer();
2125
2126 // Padding to encourage IE to display our error page, rather than its own.
2127 $output .= str_repeat(' ', 512);
2128
2129 return $output;
2130 }
2131
2132 /**
2133 * Output a notification (that is, a status message about something that has
2134 * just happened).
2135 *
2136 * @param string $message the message to print out
2137 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
2138 * @return string the HTML to output.
2139 */
2140 public function notification($message, $classes = 'notifyproblem') {
26acc814 2141 return html_writer::tag('div', clean_text($message), array('class' => renderer_base::prepare_classes($classes)));
d9c8f425 2142 }
2143
2144 /**
2145 * Print a continue button that goes to a particular URL.
2146 *
3ba60ee1 2147 * @param string|moodle_url $url The url the button goes to.
d9c8f425 2148 * @return string the HTML to output.
2149 */
3ba60ee1
PS
2150 public function continue_button($url) {
2151 if (!($url instanceof moodle_url)) {
2152 $url = new moodle_url($url);
d9c8f425 2153 }
3ba60ee1
PS
2154 $button = new single_button($url, get_string('continue'), 'get');
2155 $button->class = 'continuebutton';
d9c8f425 2156
3ba60ee1 2157 return $this->render($button);
d9c8f425 2158 }
2159
2160 /**
2161 * Prints a single paging bar to provide access to other pages (usually in a search)
2162 *
71c03ac1 2163 * @param int $totalcount The total number of entries available to be paged through
929d7a83
PS
2164 * @param int $page The page you are currently viewing
2165 * @param int $perpage The number of entries that should be shown per page
2166 * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
2167 * @param string $pagevar name of page parameter that holds the page number
d9c8f425 2168 * @return string the HTML to output.
2169 */
929d7a83
PS
2170 public function paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
2171 $pb = new paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar);
2172 return $this->render($pb);
2173 }
2174
2175 /**
2176 * Internal implementation of paging bar rendering.
2177 * @param paging_bar $pagingbar
2178 * @return string
2179 */
2180 protected function render_paging_bar(paging_bar $pagingbar) {
d9c8f425 2181 $output = '';
2182 $pagingbar = clone($pagingbar);
34059565 2183 $pagingbar->prepare($this, $this->page, $this->target);
d9c8f425 2184
2185 if ($pagingbar->totalcount > $pagingbar->perpage) {
2186 $output .= get_string('page') . ':';
2187
2188 if (!empty($pagingbar->previouslink)) {
56ddb719 2189 $output .= '&#160;(' . $pagingbar->previouslink . ')&#160;';
d9c8f425 2190 }
2191
2192 if (!empty($pagingbar->firstlink)) {
56ddb719 2193 $output .= '&#160;' . $pagingbar->firstlink . '&#160;...';
d9c8f425 2194 }
2195
2196 foreach ($pagingbar->pagelinks as $link) {
56ddb719 2197 $output .= "&#160;&#160;$link";
d9c8f425 2198 }
2199
2200 if (!empty($pagingbar->lastlink)) {
56ddb719 2201 $output .= '&#160;...' . $pagingbar->lastlink . '&#160;';
d9c8f425 2202 }
2203
2204 if (!empty($pagingbar->nextlink)) {
56ddb719 2205 $output .= '&#160;&#160;(' . $pagingbar->nextlink . ')';
d9c8f425 2206 }
2207 }
2208
26acc814 2209 return html_writer::tag('div', $output, array('class' => 'paging'));
d9c8f425 2210 }
2211
d9c8f425 2212 /**
2213 * Output the place a skip link goes to.
2214 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2215 * @return string the HTML to output.
2216 */
fe213365 2217 public function skip_link_target($id = null) {
26acc814 2218 return html_writer::tag('span', '', array('id' => $id));
d9c8f425 2219 }
2220
2221 /**
2222 * Outputs a heading
2223 * @param string $text The text of the heading
2224 * @param int $level The level of importance of the heading. Defaulting to 2
2225 * @param string $classes A space-separated list of CSS classes
2226 * @param string $id An optional ID
2227 * @return string the HTML to output.
2228 */
fe213365 2229 public function heading($text, $level = 2, $classes = 'main', $id = null) {
d9c8f425 2230 $level = (integer) $level;
2231 if ($level < 1 or $level > 6) {
2232 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2233 }
26acc814 2234 return html_writer::tag('h' . $level, $text, array('id' => $id, 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 2235 }
2236
2237 /**
2238 * Outputs a box.
2239 * @param string $contents The contents of the box
2240 * @param string $classes A space-separated list of CSS classes
2241 * @param string $id An optional ID
2242 * @return string the HTML to output.
2243 */
fe213365 2244 public function box($contents, $classes = 'generalbox', $id = null) {
d9c8f425 2245 return $this->box_start($classes, $id) . $contents . $this->box_end();
2246 }
2247
2248 /**
2249 * Outputs the opening section of a box.
2250 * @param string $classes A space-separated list of CSS classes
2251 * @param string $id An optional ID
2252 * @return string the HTML to output.
2253 */
fe213365 2254 public function box_start($classes = 'generalbox', $id = null) {
5d0c95a5
PS
2255 $this->opencontainers->push('box', html_writer::end_tag('div'));
2256 return html_writer::start_tag('div', array('id' => $id,
78946b9b 2257 'class' => 'box ' . renderer_base::prepare_classes($classes)));
d9c8f425 2258 }
2259
2260 /**
2261 * Outputs the closing section of a box.
2262 * @return string the HTML to output.
2263 */
2264 public function box_end() {
2265 return $this->opencontainers->pop('box');
2266 }
2267
2268 /**
2269 * Outputs a container.
2270 * @param string $contents The contents of the box
2271 * @param string $classes A space-separated list of CSS classes
2272 * @param string $id An optional ID
2273 * @return string the HTML to output.
2274 */
fe213365 2275 public function container($contents, $classes = null, $id = null) {
d9c8f425 2276 return $this->container_start($classes, $id) . $contents . $this->container_end();
2277 }
2278
2279 /**
2280 * Outputs the opening section of a container.
2281 * @param string $classes A space-separated list of CSS classes
2282 * @param string $id An optional ID
2283 * @return string the HTML to output.
2284 */
fe213365 2285 public function container_start($classes = null, $id = null) {
5d0c95a5
PS
2286 $this->opencontainers->push('container', html_writer::end_tag('div'));
2287 return html_writer::start_tag('div', array('id' => $id,
78946b9b 2288 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 2289 }
2290
2291 /**
2292 * Outputs the closing section of a container.
2293 * @return string the HTML to output.
2294 */
2295 public function container_end() {
2296 return $this->opencontainers->pop('container');
2297 }
7d2a0492 2298
3406acde 2299 /**
7d2a0492 2300 * Make nested HTML lists out of the items
2301 *
2302 * The resulting list will look something like this:
2303 *
2304 * <pre>
2305 * <<ul>>
2306 * <<li>><div class='tree_item parent'>(item contents)</div>
2307 * <<ul>
2308 * <<li>><div class='tree_item'>(item contents)</div><</li>>
2309 * <</ul>>
2310 * <</li>>
2311 * <</ul>>
2312 * </pre>
2313 *
2314 * @param array[]tree_item $items
2315 * @param array[string]string $attrs html attributes passed to the top of
2316 * the list
2317 * @return string HTML
2318 */
2319 function tree_block_contents($items, $attrs=array()) {
2320 // exit if empty, we don't want an empty ul element
2321 if (empty($items)) {
2322 return '';
2323 }
2324 // array of nested li elements
2325 $lis = array();
2326 foreach ($items as $item) {
2327 // this applies to the li item which contains all child lists too
2328 $content = $item->content($this);
2329 $liclasses = array($item->get_css_type());
3406acde 2330 if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0 && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
7d2a0492 2331 $liclasses[] = 'collapsed';
2332 }
2333 if ($item->isactive === true) {
2334 $liclasses[] = 'current_branch';
2335 }
2336 $liattr = array('class'=>join(' ',$liclasses));
2337 // class attribute on the div item which only contains the item content
2338 $divclasses = array('tree_item');
3406acde 2339 if ($item->children->count()>0 || $item->nodetype==navigation_node::NODETYPE_BRANCH) {
7d2a0492 2340 $divclasses[] = 'branch';
2341 } else {
2342 $divclasses[] = 'leaf';
2343 }
2344 if (!empty($item->classes) && count($item->classes)>0) {
2345 $divclasses[] = join(' ', $item->classes);
2346 }
2347 $divattr = array('class'=>join(' ', $divclasses));
2348 if (!empty($item->id)) {
2349 $divattr['id'] = $item->id;
2350 }
26acc814 2351 $content = html_writer::tag('p', $content, $divattr) . $this->tree_block_contents($item->children);
7d2a0492 2352 if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
26acc814 2353 $content = html_writer::empty_tag('hr') . $content;
7d2a0492 2354 }
26acc814 2355 $content = html_writer::tag('li', $content, $liattr);
7d2a0492 2356 $lis[] = $content;
2357 }
26acc814 2358 return html_writer::tag('ul', implode("\n", $lis), $attrs);
7d2a0492 2359 }
2360
2361 /**
2362 * Return the navbar content so that it can be echoed out by the layout
2363 * @return string XHTML navbar
2364 */
2365 public function navbar() {
3406acde
SH
2366 $items = $this->page->navbar->get_items();
2367
3406acde
SH
2368 $htmlblocks = array();
2369 // Iterate the navarray and display each node
ffca6f4b
SH
2370 $itemcount = count($items);
2371 $separator = get_separator();
2372 for ($i=0;$i < $itemcount;$i++) {
2373 $item = $items[$i];
493a48f3 2374 $item->hideicon = true;
ffca6f4b
SH
2375 if ($i===0) {
2376 $content = html_writer::tag('li', $this->render($item));
2377 } else {
2378 $content = html_writer::tag('li', $separator.$this->render($item));
2379 }
2380 $htmlblocks[] = $content;
3406acde
SH
2381 }
2382
dcfb9b78
RW
2383 //accessibility: heading for navbar list (MDL-20446)
2384 $navbarcontent = html_writer::tag('span', get_string('pagepath'), array('class'=>'accesshide'));
2385 $navbarcontent .= html_writer::tag('ul', join('', $htmlblocks));
3406acde 2386 // XHTML
dcfb9b78 2387 return $navbarcontent;
3406acde
SH
2388 }
2389
2390 protected function render_navigation_node(navigation_node $item) {
2391 $content = $item->get_content();
2392 $title = $item->get_title();
493a48f3 2393 if ($item->icon instanceof renderable && !$item->hideicon) {
3406acde 2394 $icon = $this->render($item->icon);
48fa9484 2395 $content = $icon.$content; // use CSS for spacing of icons
3406acde
SH
2396 }
2397 if ($item->helpbutton !== null) {
2398 $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton'));
2399 }
2400 if ($content === '') {
b4c458a3 2401 return '';
3406acde
SH
2402 }
2403 if ($item->action instanceof action_link) {
2404 //TODO: to be replaced with something else
2405 $link = $item->action;
2406 if ($item->hidden) {
2407 $link->add_class('dimmed');
2408 }
2409 $content = $this->output->render($link);
2410 } else if ($item->action instanceof moodle_url) {
2411 $attributes = array();
2412 if ($title !== '') {
2413 $attributes['title'] = $title;
2414 }
2415 if ($item->hidden) {
2416 $attributes['class'] = 'dimmed_text';
2417 }
2418 $content = html_writer::link($item->action, $content, $attributes);
2419
2420 } else if (is_string($item->action) || empty($item->action)) {
2421 $attributes = array();
2422 if ($title !== '') {
2423 $attributes['title'] = $title;
2424 }
2425 if ($item->hidden) {
2426 $attributes['class'] = 'dimmed_text';
2427 }
2428 $content = html_writer::tag('span', $content, $attributes);
2429 }
2430 return $content;
7d2a0492 2431 }
92e01ab7
PS
2432
2433 /**
2434 * Accessibility: Right arrow-like character is
2435 * used in the breadcrumb trail, course navigation menu
2436 * (previous/next activity), calendar, and search forum block.
2437 * If the theme does not set characters, appropriate defaults
2438 * are set automatically. Please DO NOT
2439 * use &lt; &gt; &raquo; - these are confusing for blind users.
2440 * @return string
2441 */
2442 public function rarrow() {
2443 return $this->page->theme->rarrow;
2444 }
2445
2446 /**
2447 * Accessibility: Right arrow-like character is
2448 * used in the breadcrumb trail, course navigation menu
2449 * (previous/next activity), calendar, and search forum block.
2450 * If the theme does not set characters, appropriate defaults
2451 * are set automatically. Please DO NOT
2452 * use &lt; &gt; &raquo; - these are confusing for blind users.
2453 * @return string
2454 */
2455 public function larrow() {
2456 return $this->page->theme->larrow;
2457 }
088ccb43
PS
2458
2459 /**
2460 * Returns the colours of the small MP3 player
2461 * @return string
2462 */
2463 public function filter_mediaplugin_colors() {
2464 return $this->page->theme->filter_mediaplugin_colors;
2465 }
2466
2467 /**
2468 * Returns the colours of the big MP3 player
2469 * @return string
2470 */
2471 public function resource_mp3player_colors() {
2472 return $this->page->theme->resource_mp3player_colors;
2473 }
d2dbd0c0
SH
2474
2475 /**
2476 * Returns the custom menu if one has been set
2477 *
71c03ac1 2478 * A custom menu can be configured by browsing to
d2dbd0c0
SH
2479 * Settings: Administration > Appearance > Themes > Theme settings
2480 * and then configuring the custommenu config setting as described.
4d2ee4c2 2481 *
d2dbd0c0
SH
2482 * @return string
2483 */
2484 public function custom_menu() {
12cc75ae
SH
2485 global $CFG;
2486 if (empty($CFG->custommenuitems)) {
2487 return '';
2488 }
d2dbd0c0
SH
2489 $custommenu = new custom_menu();
2490 return $this->render_custom_menu($custommenu);
2491 }
2492
2493 /**
2494 * Renders a custom menu object (located in outputcomponents.php)
2495 *
2496 * The custom menu this method produces makes use of the YUI3 menunav widget
2497 * and requires very specific html elements and classes.
2498 *
2499 * @staticvar int $menucount
2500 * @param custom_menu $menu
2501 * @return string
2502 */
2503 protected function render_custom_menu(custom_menu $menu) {
2504 static $menucount = 0;
2505 // If the menu has no children return an empty string
2506 if (!$menu->has_children()) {
2507 return '';
2508 }
2509 // Increment the menu count. This is used for ID's that get worked with
2510 // in JavaScript as is essential
2511 $menucount++;
d2dbd0c0 2512 // Initialise this custom menu
d7bd9acd 2513 $this->page->requires->js_init_call('M.core_custom_menu.init', array('custom_menu_'.$menucount));
d2dbd0c0
SH
2514 // Build the root nodes as required by YUI
2515 $content = html_writer::start_tag('div', array('id'=>'custom_menu_'.$menucount, 'class'=>'yui3-menu yui3-menu-horizontal javascript-disabled'));
2516 $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
2517 $content .= html_writer::start_tag('ul');
2518 // Render each child
2519 foreach ($menu->get_children() as $item) {
2520 $content .= $this->render_custom_menu_item($item);
2521 }
2522 // Close the open tags
2523 $content .= html_writer::end_tag('ul');
2524 $content .= html_writer::end_tag('div');
2525 $content .= html_writer::end_tag('div');
2526 // Return the custom menu
2527 return $content;
2528 }
2529
2530 /**
2531 * Renders a custom menu node as part of a submenu
2532 *
2533 * The custom menu this method produces makes use of the YUI3 menunav widget
2534 * and requires very specific html elements and classes.
2535 *
2536 * @see render_custom_menu()
2537 *
2538 * @staticvar int $submenucount
2539 * @param custom_menu_item $menunode
2540 * @return string
2541 */
2542 protected function render_custom_menu_item(custom_menu_item $menunode) {
2543 // Required to ensure we get unique trackable id's
2544 static $submenucount = 0;
2545 if ($menunode->has_children()) {
2546 // If the child has menus render it as a sub menu
2547 $submenucount++;
2548 $content = html_writer::start_tag('li');
2549 if ($menunode->get_url() !== null) {
2550 $url = $menunode->get_url();
2551 } else {
2552 $url = '#cm_submenu_'.$submenucount;
2553 }
2554 $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menu-label', 'title'=>$menunode->get_title()));
2555 $content .= html_writer::start_tag('div', array('id'=>'cm_submenu_'.$submenucount, 'class'=>'yui3-menu custom_menu_submenu'));
2556 $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
2557 $content .= html_writer::start_tag('ul');
2558 foreach ($menunode->get_children() as $menunode) {
2559 $content .= $this->render_custom_menu_item($menunode);
2560 }
2561 $content .= html_writer::end_tag('ul');
2562 $content .= html_writer::end_tag('div');
2563 $content .= html_writer::end_tag('div');
2564 $content .= html_writer::end_tag('li');
2565 } else {
2566 // The node doesn't have children so produce a final menuitem
2567 $content = html_writer::start_tag('li', array('class'=>'yui3-menuitem'));
2568 if ($menunode->get_url() !== null) {
2569 $url = $menunode->get_url();
2570 } else {
2571 $url = '#';
2572 }
2573 $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menuitem-content', 'title'=>$menunode->get_title()));
2574 $content .= html_writer::end_tag('li');
2575 }
2576 // Return the sub menu
2577 return $content;
2578 }
78946b9b 2579}
d9c8f425 2580
2581
2582/// RENDERERS
2583
2584/**
2585 * A renderer that generates output for command-line scripts.
2586 *
2587 * The implementation of this renderer is probably incomplete.
2588 *
2589 * @copyright 2009 Tim Hunt
2590 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2591 * @since Moodle 2.0
2592 */
56cbc53b 2593class core_renderer_cli extends core_renderer {
d9c8f425 2594 /**
2595 * Returns the page header.
2596 * @return string HTML fragment
2597 */
2598 public function header() {
d9c8f425 2599 return $this->page->heading . "\n";
2600 }
2601
2602 /**
2603 * Returns a template fragment representing a Heading.
2604 * @param string $text The text of the heading
2605 * @param int $level The level of importance of the heading
2606 * @param string $classes A space-separated list of CSS classes
2607 * @param string $id An optional ID
2608 * @return string A template fragment for a heading
2609 */
0fddc031 2610 public function heading($text, $level = 2, $classes = 'main', $id = null) {
d9c8f425 2611 $text .= "\n";
2612 switch ($level) {
2613 case 1:
2614 return '=>' . $text;
2615 case 2:
2616 return '-->' . $text;
2617 default:
2618 return $text;
2619 }
2620 }
2621
2622 /**
2623 * Returns a template fragment representing a fatal error.
2624 * @param string $message The message to output
2625 * @param string $moreinfourl URL where more info can be found about the error
2626 * @param string $link Link for the Continue button
2627 * @param array $backtrace The execution backtrace
2628 * @param string $debuginfo Debugging information
d9c8f425 2629 * @return string A template fragment for a fatal error
2630 */
83267ec0 2631 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
d9c8f425 2632 $output = "!!! $message !!!\n";
2633
2634 if (debugging('', DEBUG_DEVELOPER)) {
2635 if (!empty($debuginfo)) {
2b27ae72 2636 $output .= $this->notification($debuginfo, 'notifytiny');
d9c8f425 2637 }
2638 if (!empty($backtrace)) {
2b27ae72 2639 $output .= $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
d9c8f425 2640 }
2641 }
2b27ae72
PS
2642
2643 return $output;
d9c8f425 2644 }
2645
2646 /**
2647 * Returns a template fragment representing a notification.
2648 * @param string $message The message to include
2649 * @param string $classes A space-separated list of CSS classes
2650 * @return string A template fragment for a notification
2651 */
2652 public function notification($message, $classes = 'notifyproblem') {
2653 $message = clean_text($message);
2654 if ($classes === 'notifysuccess') {
2655 return "++ $message ++\n";
2656 }
2657 return "!! $message !!\n";
2658 }
2659}
2660
1adaa404
PS
2661
2662/**
2663 * A renderer that generates output for ajax scripts.
2664 *
2665 * This renderer prevents accidental sends back only json
2666 * encoded error messages, all other output is ignored.
2667 *
2668 * @copyright 2010 Petr Skoda
2669 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2670 * @since Moodle 2.0
2671 */
2672class core_renderer_ajax extends core_renderer {
2673 /**
2674 * Returns a template fragment representing a fatal error.
2675 * @param string $message The message to output
2676 * @param string $moreinfourl URL where more info can be found about the error
2677 * @param string $link Link for the Continue button
2678 * @param array $backtrace The execution backtrace
2679 * @param string $debuginfo Debugging information
2680 * @return string A template fragment for a fatal error
2681 */
2682 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
79beb849 2683 global $CFG;
eb5bdb35
PS
2684
2685 $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
2686
1adaa404
PS
2687 $e = new stdClass();
2688 $e->error = $message;
2689 $e->stacktrace = NULL;
2690 $e->debuginfo = NULL;
6db3eee0 2691 $e->reproductionlink = NULL;
1adaa404 2692 if (!empty($CFG->debug) and $CFG->debug >= DEBUG_DEVELOPER) {
6db3eee0 2693 $e->reproductionlink = $link;
1adaa404
PS
2694 if (!empty($debuginfo)) {
2695 $e->debuginfo = $debuginfo;
2696 }
2697 if (!empty($backtrace)) {
2698 $e->stacktrace = format_backtrace($backtrace, true);
2699 }
2700 }
bce08d9a 2701 $this->header();
1adaa404
PS
2702 return json_encode($e);
2703 }
2704
2705 public function notification($message, $classes = 'notifyproblem') {
2706 }
bce08d9a 2707
1adaa404
PS
2708 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
2709 }
bce08d9a 2710
1adaa404 2711 public function header() {
bce08d9a
PS
2712 // unfortunately YUI iframe upload does not support application/json
2713 if (!empty($_FILES)) {
2714 @header('Content-type: text/plain');
2715 } else {
2716 @header('Content-type: application/json');
2717 }
2718
2719 /// Headers to make it not cacheable and json
2720 @header('Cache-Control: no-store, no-cache, must-revalidate');
2721 @header('Cache-Control: post-check=0, pre-check=0', false);
2722 @header('Pragma: no-cache');
2723 @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
2724 @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
2725 @header('Accept-Ranges: none');
1adaa404 2726 }
bce08d9a 2727
1adaa404
PS
2728 public function footer() {
2729 }
bce08d9a 2730
0fddc031 2731 public function heading($text, $level = 2, $classes = 'main', $id = null) {
1adaa404
PS
2732 }
2733}
2734