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