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