MDL-39814 frontend: improvements to course/block editing
[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}.
7a3c215b 1032 * @return string HTML fragment.
d9c8f425 1033 */
e282c679
SH
1034 public function block_controls($actions) {
1035 $this->page->requires->yui_module('moodle-core-actionmenu', 'M.core.actionmenu.init');
1036 $output = html_writer::start_tag('span', array('class' => 'actionmenu'));
1037 $output .= html_writer::start_tag('span', array('class' => 'statuses'));
1038 foreach ($actions as $action) {
1039 if ($action->has_class('status')) {
1040 $output .= $this->render($action);
1041 }
d9c8f425 1042 }
e282c679
SH
1043 $output .= html_writer::end_tag('span');
1044 $output .= html_writer::start_tag('span', array('class' => 'actions'));
1045 foreach ($actions as $action) {
1046 if (!$action->has_class('status')) {
1047 $output .= $this->render($action);
1048 }
d9c8f425 1049 }
e282c679
SH
1050 $output .= html_writer::end_tag('span');
1051 $output .= html_writer::end_tag('span');
1052 return $output;
d9c8f425 1053 }
1054
1055 /**
1056 * Prints a nice side block with an optional header.
1057 *
1058 * The content is described
f8129210 1059 * by a {@link core_renderer::block_contents} object.
d9c8f425 1060 *
cbb54cce
SH
1061 * <div id="inst{$instanceid}" class="block_{$blockname} block">
1062 * <div class="header"></div>
1063 * <div class="content">
1064 * ...CONTENT...
1065 * <div class="footer">
1066 * </div>
1067 * </div>
1068 * <div class="annotation">
1069 * </div>
1070 * </div>
1071 *
d9c8f425 1072 * @param block_contents $bc HTML for the content
1073 * @param string $region the region the block is appearing in.
1074 * @return string the HTML to be output.
1075 */
7a3c215b 1076 public function block(block_contents $bc, $region) {
d9c8f425 1077 $bc = clone($bc); // Avoid messing up the object passed in.
dd72b308
PS
1078 if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
1079 $bc->collapsible = block_contents::NOT_HIDEABLE;
1080 }
84192d78
SH
1081 if (!empty($bc->blockinstanceid)) {
1082 $bc->attributes['data-instanceid'] = $bc->blockinstanceid;
1083 }
91d941c3
SH
1084 $skiptitle = strip_tags($bc->title);
1085 if ($bc->blockinstanceid && !empty($skiptitle)) {
1086 $bc->attributes['aria-labelledby'] = 'instance-'.$bc->blockinstanceid.'-header';
1087 } else if (!empty($bc->arialabel)) {
1088 $bc->attributes['aria-label'] = $bc->arialabel;
1089 }
84192d78
SH
1090 if ($bc->dockable) {
1091 $bc->attributes['data-dockable'] = 1;
1092 }
dd72b308
PS
1093 if ($bc->collapsible == block_contents::HIDDEN) {
1094 $bc->add_class('hidden');
1095 }
1096 if (!empty($bc->controls)) {
1097 $bc->add_class('block_with_controls');
1098 }
d9c8f425 1099
91d941c3 1100
d9c8f425 1101 if (empty($skiptitle)) {
1102 $output = '';
1103 $skipdest = '';
1104 } else {
26acc814
PS
1105 $output = html_writer::tag('a', get_string('skipa', 'access', $skiptitle), array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'));
1106 $skipdest = html_writer::tag('span', '', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'));
d9c8f425 1107 }
4d2ee4c2 1108
5d0c95a5 1109 $output .= html_writer::start_tag('div', $bc->attributes);
d9c8f425 1110
9f5c39b5
SH
1111 $output .= $this->block_header($bc);
1112 $output .= $this->block_content($bc);
1113
1114 $output .= html_writer::end_tag('div');
1115
1116 $output .= $this->block_annotation($bc);
1117
1118 $output .= $skipdest;
1119
1120 $this->init_block_hider_js($bc);
1121 return $output;
1122 }
1123
1124 /**
1125 * Produces a header for a block
fa7f2a45 1126 *
9f5c39b5
SH
1127 * @param block_contents $bc
1128 * @return string
1129 */
1130 protected function block_header(block_contents $bc) {
d9c8f425 1131
1132 $title = '';
1133 if ($bc->title) {
91d941c3
SH
1134 $attributes = array();
1135 if ($bc->blockinstanceid) {
1136 $attributes['id'] = 'instance-'.$bc->blockinstanceid.'-header';
1137 }
1138 $title = html_writer::tag('h2', $bc->title, $attributes);
d9c8f425 1139 }
1140
9f5c39b5
SH
1141 $controlshtml = $this->block_controls($bc->controls);
1142
1143 $output = '';
d9c8f425 1144 if ($title || $controlshtml) {
46de77b6 1145 $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 1146 }
9f5c39b5
SH
1147 return $output;
1148 }
d9c8f425 1149
9f5c39b5
SH
1150 /**
1151 * Produces the content area for a block
1152 *
1153 * @param block_contents $bc
1154 * @return string
1155 */
1156 protected function block_content(block_contents $bc) {
1157 $output = html_writer::start_tag('div', array('class' => 'content'));
1158 if (!$bc->title && !$this->block_controls($bc->controls)) {
46de77b6
SH
1159 $output .= html_writer::tag('div', '', array('class'=>'block_action notitle'));
1160 }
d9c8f425 1161 $output .= $bc->content;
9f5c39b5
SH
1162 $output .= $this->block_footer($bc);
1163 $output .= html_writer::end_tag('div');
fa7f2a45 1164
9f5c39b5
SH
1165 return $output;
1166 }
d9c8f425 1167
9f5c39b5
SH
1168 /**
1169 * Produces the footer for a block
1170 *
1171 * @param block_contents $bc
1172 * @return string
1173 */
1174 protected function block_footer(block_contents $bc) {
1175 $output = '';
d9c8f425 1176 if ($bc->footer) {
26acc814 1177 $output .= html_writer::tag('div', $bc->footer, array('class' => 'footer'));
d9c8f425 1178 }
9f5c39b5
SH
1179 return $output;
1180 }
d9c8f425 1181
9f5c39b5
SH
1182 /**
1183 * Produces the annotation for a block
1184 *
1185 * @param block_contents $bc
1186 * @return string
1187 */
1188 protected function block_annotation(block_contents $bc) {
1189 $output = '';
d9c8f425 1190 if ($bc->annotation) {
26acc814 1191 $output .= html_writer::tag('div', $bc->annotation, array('class' => 'blockannotation'));
d9c8f425 1192 }
d9c8f425 1193 return $output;
1194 }
1195
1196 /**
1197 * Calls the JS require function to hide a block.
7a3c215b 1198 *
d9c8f425 1199 * @param block_contents $bc A block_contents object
d9c8f425 1200 */
dd72b308
PS
1201 protected function init_block_hider_js(block_contents $bc) {
1202 if (!empty($bc->attributes['id']) and $bc->collapsible != block_contents::NOT_HIDEABLE) {
cbb54cce
SH
1203 $config = new stdClass;
1204 $config->id = $bc->attributes['id'];
1205 $config->title = strip_tags($bc->title);
1206 $config->preference = 'block' . $bc->blockinstanceid . 'hidden';
1207 $config->tooltipVisible = get_string('hideblocka', 'access', $config->title);
1208 $config->tooltipHidden = get_string('showblocka', 'access', $config->title);
1209
1210 $this->page->requires->js_init_call('M.util.init_block_hider', array($config));
1211 user_preference_allow_ajax_update($config->preference, PARAM_BOOL);
d9c8f425 1212 }
1213 }
1214
1215 /**
1216 * Render the contents of a block_list.
7a3c215b 1217 *
d9c8f425 1218 * @param array $icons the icon for each item.
1219 * @param array $items the content of each item.
1220 * @return string HTML
1221 */
1222 public function list_block_contents($icons, $items) {
1223 $row = 0;
1224 $lis = array();
1225 foreach ($items as $key => $string) {
5d0c95a5 1226 $item = html_writer::start_tag('li', array('class' => 'r' . $row));
2c5ec833 1227 if (!empty($icons[$key])) { //test if the content has an assigned icon
26acc814 1228 $item .= html_writer::tag('div', $icons[$key], array('class' => 'icon column c0'));
d9c8f425 1229 }
26acc814 1230 $item .= html_writer::tag('div', $string, array('class' => 'column c1'));
5d0c95a5 1231 $item .= html_writer::end_tag('li');
d9c8f425 1232 $lis[] = $item;
1233 $row = 1 - $row; // Flip even/odd.
1234 }
f2a04fc1 1235 return html_writer::tag('ul', implode("\n", $lis), array('class' => 'unlist'));
d9c8f425 1236 }
1237
1238 /**
1239 * Output all the blocks in a particular region.
7a3c215b 1240 *
d9c8f425 1241 * @param string $region the name of a region on this page.
1242 * @return string the HTML to be output.
1243 */
1244 public function blocks_for_region($region) {
36b77e3a 1245 $region = $this->page->apply_theme_region_manipulations($region);
d9c8f425 1246 $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
6671fa73
JF
1247 $blocks = $this->page->blocks->get_blocks_for_region($region);
1248 $lastblock = null;
1249 $zones = array();
1250 foreach ($blocks as $block) {
1251 $zones[] = $block->title;
1252 }
d9c8f425 1253 $output = '';
6671fa73 1254
d9c8f425 1255 foreach ($blockcontents as $bc) {
1256 if ($bc instanceof block_contents) {
1257 $output .= $this->block($bc, $region);
6671fa73 1258 $lastblock = $bc->title;
d9c8f425 1259 } else if ($bc instanceof block_move_target) {
6671fa73 1260 $output .= $this->block_move_target($bc, $zones, $lastblock);
d9c8f425 1261 } else {
1262 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
1263 }
1264 }
1265 return $output;
1266 }
1267
1268 /**
1269 * Output a place where the block that is currently being moved can be dropped.
7a3c215b 1270 *
d9c8f425 1271 * @param block_move_target $target with the necessary details.
6671fa73
JF
1272 * @param array $zones array of areas where the block can be moved to
1273 * @param string $previous the block located before the area currently being rendered.
d9c8f425 1274 * @return string the HTML to be output.
1275 */
6671fa73 1276 public function block_move_target($target, $zones, $previous) {
0e2ca62e 1277 if ($previous == null) {
6671fa73
JF
1278 $position = get_string('moveblockbefore', 'block', $zones[0]);
1279 } else {
1280 $position = get_string('moveblockafter', 'block', $previous);
1281 }
1282 return html_writer::tag('a', html_writer::tag('span', $position, array('class' => 'accesshide')), array('href' => $target->url, 'class' => 'blockmovetarget'));
d9c8f425 1283 }
1284
574fbea4 1285 /**
996b1e0c 1286 * Renders a special html link with attached action
574fbea4 1287 *
2fada290
MG
1288 * Theme developers: DO NOT OVERRIDE! Please override function
1289 * {@link core_renderer::render_action_link()} instead.
1290 *
574fbea4
PS
1291 * @param string|moodle_url $url
1292 * @param string $text HTML fragment
1293 * @param component_action $action
11820bac 1294 * @param array $attributes associative array of html link attributes + disabled
e282c679 1295 * @param pix_icon optional pix icon to render with the link
7a3c215b 1296 * @return string HTML fragment
574fbea4 1297 */
e282c679 1298 public function action_link($url, $text, component_action $action = null, array $attributes = null, $icon = null) {
574fbea4
PS
1299 if (!($url instanceof moodle_url)) {
1300 $url = new moodle_url($url);
1301 }
e282c679 1302 $link = new action_link($url, $text, $action, $attributes, $icon);
574fbea4 1303
f14b641b 1304 return $this->render($link);
574fbea4
PS
1305 }
1306
1307 /**
7a3c215b
SH
1308 * Renders an action_link object.
1309 *
1310 * The provided link is renderer and the HTML returned. At the same time the
f8129210 1311 * associated actions are setup in JS by {@link core_renderer::add_action_handler()}
7a3c215b 1312 *
574fbea4
PS
1313 * @param action_link $link
1314 * @return string HTML fragment
1315 */
1316 protected function render_action_link(action_link $link) {
1317 global $CFG;
1318
e282c679
SH
1319 $text = '';
1320 if ($link->icon) {
1321 $text .= $this->render($link->icon);
1322 }
1323
1324 $text .= html_writer::start_tag('span', array('class'=>'actionlinktext'));
7749e187 1325 if ($link->text instanceof renderable) {
e282c679 1326 $text .= $this->render($link->text);
7749e187 1327 } else {
e282c679 1328 $text .= $link->text;
7749e187 1329 }
e282c679 1330 $text .= html_writer::end_tag('span');
7749e187 1331
574fbea4
PS
1332 // A disabled link is rendered as formatted text
1333 if (!empty($link->attributes['disabled'])) {
1334 // do not use div here due to nesting restriction in xhtml strict
7749e187 1335 return html_writer::tag('span', $text, array('class'=>'currentlink'));
574fbea4 1336 }
11820bac 1337
574fbea4
PS
1338 $attributes = $link->attributes;
1339 unset($link->attributes['disabled']);
1340 $attributes['href'] = $link->url;
1341
1342 if ($link->actions) {
f14b641b 1343 if (empty($attributes['id'])) {
574fbea4
PS
1344 $id = html_writer::random_id('action_link');
1345 $attributes['id'] = $id;
1346 } else {
1347 $id = $attributes['id'];
1348 }
1349 foreach ($link->actions as $action) {
c80877aa 1350 $this->add_action_handler($action, $id);
574fbea4
PS
1351 }
1352 }
1353
7749e187 1354 return html_writer::tag('a', $text, $attributes);
574fbea4
PS
1355 }
1356
c63923bd
PS
1357
1358 /**
7a3c215b
SH
1359 * Renders an action_icon.
1360 *
f8129210 1361 * This function uses the {@link core_renderer::action_link()} method for the
7a3c215b
SH
1362 * most part. What it does different is prepare the icon as HTML and use it
1363 * as the link text.
c63923bd 1364 *
2fada290
MG
1365 * Theme developers: If you want to change how action links and/or icons are rendered,
1366 * consider overriding function {@link core_renderer::render_action_link()} and
1367 * {@link core_renderer::render_pix_icon()}.
1368 *
c63923bd
PS
1369 * @param string|moodle_url $url A string URL or moodel_url
1370 * @param pix_icon $pixicon
1371 * @param component_action $action
1372 * @param array $attributes associative array of html link attributes + disabled
1373 * @param bool $linktext show title next to image in link
1374 * @return string HTML fragment
1375 */
1376 public function action_icon($url, pix_icon $pixicon, component_action $action = null, array $attributes = null, $linktext=false) {
1377 if (!($url instanceof moodle_url)) {
1378 $url = new moodle_url($url);
1379 }
1380 $attributes = (array)$attributes;
1381
524645e7 1382 if (empty($attributes['class'])) {
c63923bd
PS
1383 // let ppl override the class via $options
1384 $attributes['class'] = 'action-icon';
1385 }
1386
1387 $icon = $this->render($pixicon);
1388
1389 if ($linktext) {
1390 $text = $pixicon->attributes['alt'];
1391 } else {
1392 $text = '';
1393 }
1394
1395 return $this->action_link($url, $text.$icon, $action, $attributes);
1396 }
1397
d9c8f425 1398 /**
0b634d75 1399 * Print a message along with button choices for Continue/Cancel
1400 *
4ed85790 1401 * If a string or moodle_url is given instead of a single_button, method defaults to post.
0b634d75 1402 *
d9c8f425 1403 * @param string $message The question to ask the user
3ba60ee1
PS
1404 * @param single_button|moodle_url|string $continue The single_button component representing the Continue answer. Can also be a moodle_url or string URL
1405 * @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 1406 * @return string HTML fragment
1407 */
1408 public function confirm($message, $continue, $cancel) {
4871a238 1409 if ($continue instanceof single_button) {
11820bac 1410 // ok
4871a238
PS
1411 } else if (is_string($continue)) {
1412 $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post');
1413 } else if ($continue instanceof moodle_url) {
26eab8d4 1414 $continue = new single_button($continue, get_string('continue'), 'post');
d9c8f425 1415 } else {
4ed85790 1416 throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
d9c8f425 1417 }
1418
4871a238 1419 if ($cancel instanceof single_button) {
11820bac 1420 // ok
4871a238
PS
1421 } else if (is_string($cancel)) {
1422 $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get');
1423 } else if ($cancel instanceof moodle_url) {
26eab8d4 1424 $cancel = new single_button($cancel, get_string('cancel'), 'get');
d9c8f425 1425 } else {
4ed85790 1426 throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
d9c8f425 1427 }
1428
d9c8f425 1429 $output = $this->box_start('generalbox', 'notice');
26acc814
PS
1430 $output .= html_writer::tag('p', $message);
1431 $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons'));
d9c8f425 1432 $output .= $this->box_end();
1433 return $output;
1434 }
1435
3cd5305f 1436 /**
3ba60ee1 1437 * Returns a form with a single button.
3cd5305f 1438 *
2fada290
MG
1439 * Theme developers: DO NOT OVERRIDE! Please override function
1440 * {@link core_renderer::render_single_button()} instead.
1441 *
3ba60ee1 1442 * @param string|moodle_url $url
3cd5305f
PS
1443 * @param string $label button text
1444 * @param string $method get or post submit method
3ba60ee1 1445 * @param array $options associative array {disabled, title, etc.}
3cd5305f
PS
1446 * @return string HTML fragment
1447 */
3ba60ee1 1448 public function single_button($url, $label, $method='post', array $options=null) {
574fbea4
PS
1449 if (!($url instanceof moodle_url)) {
1450 $url = new moodle_url($url);
3ba60ee1 1451 }
574fbea4
PS
1452 $button = new single_button($url, $label, $method);
1453
3ba60ee1
PS
1454 foreach ((array)$options as $key=>$value) {
1455 if (array_key_exists($key, $button)) {
1456 $button->$key = $value;
1457 }
3cd5305f
PS
1458 }
1459
3ba60ee1 1460 return $this->render($button);
3cd5305f
PS
1461 }
1462
d9c8f425 1463 /**
7a3c215b
SH
1464 * Renders a single button widget.
1465 *
1466 * This will return HTML to display a form containing a single button.
1467 *
3ba60ee1 1468 * @param single_button $button
d9c8f425 1469 * @return string HTML fragment
1470 */
3ba60ee1
PS
1471 protected function render_single_button(single_button $button) {
1472 $attributes = array('type' => 'submit',
1473 'value' => $button->label,
db09524d 1474 'disabled' => $button->disabled ? 'disabled' : null,
3ba60ee1
PS
1475 'title' => $button->tooltip);
1476
1477 if ($button->actions) {
1478 $id = html_writer::random_id('single_button');
1479 $attributes['id'] = $id;
1480 foreach ($button->actions as $action) {
c80877aa 1481 $this->add_action_handler($action, $id);
3ba60ee1 1482 }
d9c8f425 1483 }
d9c8f425 1484
3ba60ee1
PS
1485 // first the input element
1486 $output = html_writer::empty_tag('input', $attributes);
d9c8f425 1487
3ba60ee1
PS
1488 // then hidden fields
1489 $params = $button->url->params();
1490 if ($button->method === 'post') {
1491 $params['sesskey'] = sesskey();
1492 }
1493 foreach ($params as $var => $val) {
1494 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1495 }
d9c8f425 1496
3ba60ee1 1497 // then div wrapper for xhtml strictness
26acc814 1498 $output = html_writer::tag('div', $output);
d9c8f425 1499
3ba60ee1 1500 // now the form itself around it
a12cd69c
DM
1501 if ($button->method === 'get') {
1502 $url = $button->url->out_omit_querystring(true); // url without params, the anchor part allowed
1503 } else {
1504 $url = $button->url->out_omit_querystring(); // url without params, the anchor part not allowed
1505 }
a6855934
PS
1506 if ($url === '') {
1507 $url = '#'; // there has to be always some action
1508 }
3ba60ee1 1509 $attributes = array('method' => $button->method,
a6855934 1510 'action' => $url,
3ba60ee1 1511 'id' => $button->formid);
26acc814 1512 $output = html_writer::tag('form', $output, $attributes);
d9c8f425 1513
3ba60ee1 1514 // and finally one more wrapper with class
26acc814 1515 return html_writer::tag('div', $output, array('class' => $button->class));
d9c8f425 1516 }
1517
a9967cf5 1518 /**
ab08be98 1519 * Returns a form with a single select widget.
7a3c215b 1520 *
2fada290
MG
1521 * Theme developers: DO NOT OVERRIDE! Please override function
1522 * {@link core_renderer::render_single_select()} instead.
1523 *
a9967cf5
PS
1524 * @param moodle_url $url form action target, includes hidden fields
1525 * @param string $name name of selection field - the changing parameter in url
1526 * @param array $options list of options
1527 * @param string $selected selected element
1528 * @param array $nothing
f8dab966 1529 * @param string $formid
a9967cf5
PS
1530 * @return string HTML fragment
1531 */
7a3c215b 1532 public function single_select($url, $name, array $options, $selected = '', $nothing = array('' => 'choosedots'), $formid = null) {
a9967cf5
PS
1533 if (!($url instanceof moodle_url)) {
1534 $url = new moodle_url($url);
1535 }
f8dab966 1536 $select = new single_select($url, $name, $options, $selected, $nothing, $formid);
a9967cf5
PS
1537
1538 return $this->render($select);
1539 }
1540
1541 /**
1542 * Internal implementation of single_select rendering
7a3c215b 1543 *
a9967cf5
PS
1544 * @param single_select $select
1545 * @return string HTML fragment
1546 */
1547 protected function render_single_select(single_select $select) {
1548 $select = clone($select);
1549 if (empty($select->formid)) {
1550 $select->formid = html_writer::random_id('single_select_f');
1551 }
1552
1553 $output = '';
1554 $params = $select->url->params();
1555 if ($select->method === 'post') {
1556 $params['sesskey'] = sesskey();
1557 }
1558 foreach ($params as $name=>$value) {
1559 $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$value));
1560 }
1561
1562 if (empty($select->attributes['id'])) {
1563 $select->attributes['id'] = html_writer::random_id('single_select');
1564 }
1565
0b2cb132
PS
1566 if ($select->disabled) {
1567 $select->attributes['disabled'] = 'disabled';
1568 }
4d10e579 1569
a9967cf5
PS
1570 if ($select->tooltip) {
1571 $select->attributes['title'] = $select->tooltip;
1572 }
1573
7266bd3e
ARN
1574 $select->attributes['class'] = 'autosubmit';
1575 if ($select->class) {
1576 $select->attributes['class'] .= ' ' . $select->class;
1577 }
1578
a9967cf5 1579 if ($select->label) {
ecc5cc31 1580 $output .= html_writer::label($select->label, $select->attributes['id'], false, $select->labelattributes);
a9967cf5
PS
1581 }
1582
1583 if ($select->helpicon instanceof help_icon) {
1584 $output .= $this->render($select->helpicon);
1585 }
a9967cf5
PS
1586 $output .= html_writer::select($select->options, $select->name, $select->selected, $select->nothing, $select->attributes);
1587
1588 $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go')));
ebc583e4 1589 $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('class' => 'inline'));
a9967cf5
PS
1590
1591 $nothing = empty($select->nothing) ? false : key($select->nothing);
7266bd3e
ARN
1592 $this->page->requires->yui_module('moodle-core-formautosubmit',
1593 'M.core.init_formautosubmit',
1594 array(array('selectid' => $select->attributes['id'], 'nothing' => $nothing))
1595 );
a9967cf5
PS
1596
1597 // then div wrapper for xhtml strictness
26acc814 1598 $output = html_writer::tag('div', $output);
a9967cf5
PS
1599
1600 // now the form itself around it
a12cd69c
DM
1601 if ($select->method === 'get') {
1602 $url = $select->url->out_omit_querystring(true); // url without params, the anchor part allowed
1603 } else {
1604 $url = $select->url->out_omit_querystring(); // url without params, the anchor part not allowed
1605 }
a9967cf5 1606 $formattributes = array('method' => $select->method,
a12cd69c 1607 'action' => $url,
a9967cf5 1608 'id' => $select->formid);
26acc814 1609 $output = html_writer::tag('form', $output, $formattributes);
4d10e579
PS
1610
1611 // and finally one more wrapper with class
26acc814 1612 return html_writer::tag('div', $output, array('class' => $select->class));
4d10e579
PS
1613 }
1614
1615 /**
ab08be98 1616 * Returns a form with a url select widget.
7a3c215b 1617 *
2fada290
MG
1618 * Theme developers: DO NOT OVERRIDE! Please override function
1619 * {@link core_renderer::render_url_select()} instead.
1620 *
4d10e579
PS
1621 * @param array $urls list of urls - array('/course/view.php?id=1'=>'Frontpage', ....)
1622 * @param string $selected selected element
1623 * @param array $nothing
1624 * @param string $formid
1625 * @return string HTML fragment
1626 */
7a3c215b 1627 public function url_select(array $urls, $selected, $nothing = array('' => 'choosedots'), $formid = null) {
4d10e579
PS
1628 $select = new url_select($urls, $selected, $nothing, $formid);
1629 return $this->render($select);
1630 }
1631
1632 /**
ab08be98 1633 * Internal implementation of url_select rendering
7a3c215b
SH
1634 *
1635 * @param url_select $select
4d10e579
PS
1636 * @return string HTML fragment
1637 */
1638 protected function render_url_select(url_select $select) {
c422efcf
PS
1639 global $CFG;
1640
4d10e579
PS
1641 $select = clone($select);
1642 if (empty($select->formid)) {
1643 $select->formid = html_writer::random_id('url_select_f');
1644 }
1645
1646 if (empty($select->attributes['id'])) {
1647 $select->attributes['id'] = html_writer::random_id('url_select');
1648 }
1649
1650 if ($select->disabled) {
1651 $select->attributes['disabled'] = 'disabled';
1652 }
1653
1654 if ($select->tooltip) {
1655 $select->attributes['title'] = $select->tooltip;
1656 }
1657
1658 $output = '';
1659
1660 if ($select->label) {
ecc5cc31 1661 $output .= html_writer::label($select->label, $select->attributes['id'], false, $select->labelattributes);
4d10e579
PS
1662 }
1663
50d6ad84
ARN
1664 $classes = array();
1665 if (!$select->showbutton) {
1666 $classes[] = 'autosubmit';
1667 }
7266bd3e 1668 if ($select->class) {
50d6ad84
ARN
1669 $classes[] = $select->class;
1670 }
1671 if (count($classes)) {
1672 $select->attributes['class'] = implode(' ', $classes);
7266bd3e
ARN
1673 }
1674
4d10e579
PS
1675 if ($select->helpicon instanceof help_icon) {
1676 $output .= $this->render($select->helpicon);
1677 }
1678
d4dcfc6b
DM
1679 // For security reasons, the script course/jumpto.php requires URL starting with '/'. To keep
1680 // backward compatibility, we are removing heading $CFG->wwwroot from URLs here.
c422efcf
PS
1681 $urls = array();
1682 foreach ($select->urls as $k=>$v) {
d4dcfc6b
DM
1683 if (is_array($v)) {
1684 // optgroup structure
1685 foreach ($v as $optgrouptitle => $optgroupoptions) {
1686 foreach ($optgroupoptions as $optionurl => $optiontitle) {
1687 if (empty($optionurl)) {
1688 $safeoptionurl = '';
1689 } else if (strpos($optionurl, $CFG->wwwroot.'/') === 0) {
1690 // debugging('URLs passed to url_select should be in local relative form - please fix the code.', DEBUG_DEVELOPER);
1691 $safeoptionurl = str_replace($CFG->wwwroot, '', $optionurl);
1692 } else if (strpos($optionurl, '/') !== 0) {
1693 debugging("Invalid url_select urls parameter inside optgroup: url '$optionurl' is not local relative url!");
1694 continue;
1695 } else {
1696 $safeoptionurl = $optionurl;
1697 }
1698 $urls[$k][$optgrouptitle][$safeoptionurl] = $optiontitle;
1699 }
1700 }
1701 } else {
1702 // plain list structure
1703 if (empty($k)) {
1704 // nothing selected option
1705 } else if (strpos($k, $CFG->wwwroot.'/') === 0) {
1706 $k = str_replace($CFG->wwwroot, '', $k);
1707 } else if (strpos($k, '/') !== 0) {
1708 debugging("Invalid url_select urls parameter: url '$k' is not local relative url!");
1709 continue;
1710 }
1711 $urls[$k] = $v;
1712 }
1713 }
1714 $selected = $select->selected;
1715 if (!empty($selected)) {
1716 if (strpos($select->selected, $CFG->wwwroot.'/') === 0) {
1717 $selected = str_replace($CFG->wwwroot, '', $selected);
1718 } else if (strpos($selected, '/') !== 0) {
1719 debugging("Invalid value of parameter 'selected': url '$selected' is not local relative url!");
c422efcf 1720 }
c422efcf
PS
1721 }
1722
4d10e579 1723 $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()));
d4dcfc6b 1724 $output .= html_writer::select($urls, 'jump', $selected, $select->nothing, $select->attributes);
4d10e579 1725
15e48a1a
SM
1726 if (!$select->showbutton) {
1727 $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go')));
ebc583e4 1728 $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('class' => 'inline'));
15e48a1a 1729 $nothing = empty($select->nothing) ? false : key($select->nothing);
7266bd3e
ARN
1730 $this->page->requires->yui_module('moodle-core-formautosubmit',
1731 'M.core.init_formautosubmit',
1732 array(array('selectid' => $select->attributes['id'], 'nothing' => $nothing))
1733 );
15e48a1a
SM
1734 } else {
1735 $output .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>$select->showbutton));
1736 }
4d10e579
PS
1737
1738 // then div wrapper for xhtml strictness
26acc814 1739 $output = html_writer::tag('div', $output);
4d10e579
PS
1740
1741 // now the form itself around it
1742 $formattributes = array('method' => 'post',
1743 'action' => new moodle_url('/course/jumpto.php'),
1744 'id' => $select->formid);
26acc814 1745 $output = html_writer::tag('form', $output, $formattributes);
a9967cf5
PS
1746
1747 // and finally one more wrapper with class
26acc814 1748 return html_writer::tag('div', $output, array('class' => $select->class));
a9967cf5
PS
1749 }
1750
d9c8f425 1751 /**
1752 * Returns a string containing a link to the user documentation.
1753 * Also contains an icon by default. Shown to teachers and admin only.
7a3c215b 1754 *
d9c8f425 1755 * @param string $path The page link after doc root and language, no leading slash.
1756 * @param string $text The text to be displayed for the link
afe3566c 1757 * @param boolean $forcepopup Whether to force a popup regardless of the value of $CFG->doctonewwindow
996b1e0c 1758 * @return string
d9c8f425 1759 */
afe3566c 1760 public function doc_link($path, $text = '', $forcepopup = false) {
8ae8bf8a
PS
1761 global $CFG;
1762
c22fbd38 1763 $icon = $this->pix_icon('docs', $text, 'moodle', array('class'=>'iconhelp icon-pre'));
8ae8bf8a 1764
000c278c 1765 $url = new moodle_url(get_docs_url($path));
8ae8bf8a 1766
c80877aa 1767 $attributes = array('href'=>$url);
afe3566c
ARN
1768 if (!empty($CFG->doctonewwindow) || $forcepopup) {
1769 $attributes['class'] = 'helplinkpopup';
d9c8f425 1770 }
1adaa404 1771
26acc814 1772 return html_writer::tag('a', $icon.$text, $attributes);
d9c8f425 1773 }
1774
000c278c 1775 /**
7a3c215b
SH
1776 * Return HTML for a pix_icon.
1777 *
2fada290
MG
1778 * Theme developers: DO NOT OVERRIDE! Please override function
1779 * {@link core_renderer::render_pix_icon()} instead.
1780 *
000c278c
PS
1781 * @param string $pix short pix name
1782 * @param string $alt mandatory alt attribute
eb557002 1783 * @param string $component standard compoennt name like 'moodle', 'mod_forum', etc.
000c278c
PS
1784 * @param array $attributes htm lattributes
1785 * @return string HTML fragment
1786 */
1787 public function pix_icon($pix, $alt, $component='moodle', array $attributes = null) {
1788 $icon = new pix_icon($pix, $alt, $component, $attributes);
1789 return $this->render($icon);
1790 }
1791
1792 /**
7a3c215b
SH
1793 * Renders a pix_icon widget and returns the HTML to display it.
1794 *
000c278c
PS
1795 * @param pix_icon $icon
1796 * @return string HTML fragment
1797 */
ce0110bf 1798 protected function render_pix_icon(pix_icon $icon) {
000c278c
PS
1799 $attributes = $icon->attributes;
1800 $attributes['src'] = $this->pix_url($icon->pix, $icon->component);
c80877aa 1801 return html_writer::empty_tag('img', $attributes);
000c278c
PS
1802 }
1803
d63c5073 1804 /**
7a3c215b
SH
1805 * Return HTML to display an emoticon icon.
1806 *
d63c5073
DM
1807 * @param pix_emoticon $emoticon
1808 * @return string HTML fragment
1809 */
1810 protected function render_pix_emoticon(pix_emoticon $emoticon) {
1811 $attributes = $emoticon->attributes;
1812 $attributes['src'] = $this->pix_url($emoticon->pix, $emoticon->component);
1813 return html_writer::empty_tag('img', $attributes);
1814 }
1815
a09aeee4 1816 /**
7a3c215b
SH
1817 * Produces the html that represents this rating in the UI
1818 *
1819 * @param rating $rating the page object on which this rating will appear
1820 * @return string
1821 */
a09aeee4 1822 function render_rating(rating $rating) {
7ac928a7 1823 global $CFG, $USER;
a09aeee4 1824
2b04c41c 1825 if ($rating->settings->aggregationmethod == RATING_AGGREGATE_NONE) {
63e87951
AD
1826 return null;//ratings are turned off
1827 }
1828
2b04c41c
SH
1829 $ratingmanager = new rating_manager();
1830 // Initialise the JavaScript so ratings can be done by AJAX.
1831 $ratingmanager->initialise_rating_javascript($this->page);
a09aeee4 1832
63e87951
AD
1833 $strrate = get_string("rate", "rating");
1834 $ratinghtml = ''; //the string we'll return
1835
2b04c41c
SH
1836 // permissions check - can they view the aggregate?
1837 if ($rating->user_can_view_aggregate()) {
a09aeee4 1838
2b04c41c
SH
1839 $aggregatelabel = $ratingmanager->get_aggregate_label($rating->settings->aggregationmethod);
1840 $aggregatestr = $rating->get_aggregate_string();
a09aeee4 1841
6278ce45 1842 $aggregatehtml = html_writer::tag('span', $aggregatestr, array('id' => 'ratingaggregate'.$rating->itemid, 'class' => 'ratingaggregate')).' ';
2b04c41c 1843 if ($rating->count > 0) {
6278ce45 1844 $countstr = "({$rating->count})";
d251b259 1845 } else {
6278ce45 1846 $countstr = '-';
d251b259 1847 }
6278ce45 1848 $aggregatehtml .= html_writer::tag('span', $countstr, array('id'=>"ratingcount{$rating->itemid}", 'class' => 'ratingcount')).' ';
63e87951 1849
c6de9cef 1850 $ratinghtml .= html_writer::tag('span', $aggregatelabel, array('class'=>'rating-aggregate-label'));
d251b259 1851 if ($rating->settings->permissions->viewall && $rating->settings->pluginpermissions->viewall) {
2b04c41c
SH
1852
1853 $nonpopuplink = $rating->get_view_ratings_url();
1854 $popuplink = $rating->get_view_ratings_url(true);
a09aeee4 1855
d251b259 1856 $action = new popup_action('click', $popuplink, 'ratings', array('height' => 400, 'width' => 600));
c6de9cef 1857 $ratinghtml .= $this->action_link($nonpopuplink, $aggregatehtml, $action);
d251b259 1858 } else {
c6de9cef 1859 $ratinghtml .= $aggregatehtml;
a09aeee4 1860 }
d251b259 1861 }
a09aeee4 1862
d251b259 1863 $formstart = null;
2b04c41c
SH
1864 // if the item doesn't belong to the current user, the user has permission to rate
1865 // and we're within the assessable period
1866 if ($rating->user_can_rate()) {
771b3fbe 1867
2b04c41c
SH
1868 $rateurl = $rating->get_rate_url();
1869 $inputs = $rateurl->params();
771b3fbe 1870
2b04c41c
SH
1871 //start the rating form
1872 $formattrs = array(
1873 'id' => "postrating{$rating->itemid}",
1874 'class' => 'postratingform',
1875 'method' => 'post',
1876 'action' => $rateurl->out_omit_querystring()
1877 );
1878 $formstart = html_writer::start_tag('form', $formattrs);
1879 $formstart .= html_writer::start_tag('div', array('class' => 'ratingform'));
1880
1881 // add the hidden inputs
1882 foreach ($inputs as $name => $value) {
1883 $attributes = array('type' => 'hidden', 'class' => 'ratinginput', 'name' => $name, 'value' => $value);
1884 $formstart .= html_writer::empty_tag('input', $attributes);
1885 }
3180bc2c 1886
d251b259
AD
1887 if (empty($ratinghtml)) {
1888 $ratinghtml .= $strrate.': ';
1889 }
d251b259 1890 $ratinghtml = $formstart.$ratinghtml;
63e87951 1891
2b04c41c
SH
1892 $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $rating->settings->scale->scaleitems;
1893 $scaleattrs = array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid);
ecc5cc31 1894 $ratinghtml .= html_writer::label($rating->rating, 'menurating'.$rating->itemid, false, array('class' => 'accesshide'));
2b04c41c 1895 $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, $scaleattrs);
a09aeee4 1896
d251b259 1897 //output submit button
771b3fbe
AD
1898 $ratinghtml .= html_writer::start_tag('span', array('class'=>"ratingsubmit"));
1899
2b04c41c 1900 $attributes = array('type' => 'submit', 'class' => 'postratingmenusubmit', 'id' => 'postratingsubmit'.$rating->itemid, 'value' => s(get_string('rate', 'rating')));
771b3fbe 1901 $ratinghtml .= html_writer::empty_tag('input', $attributes);
a09aeee4 1902
2b04c41c 1903 if (!$rating->settings->scale->isnumeric) {
eaf52ff0
MN
1904 // If a global scale, try to find current course ID from the context
1905 if (empty($rating->settings->scale->courseid) and $coursecontext = $rating->context->get_course_context(false)) {
1906 $courseid = $coursecontext->instanceid;
1907 } else {
1908 $courseid = $rating->settings->scale->courseid;
1909 }
1910 $ratinghtml .= $this->help_icon_scale($courseid, $rating->settings->scale);
a09aeee4 1911 }
771b3fbe
AD
1912 $ratinghtml .= html_writer::end_tag('span');
1913 $ratinghtml .= html_writer::end_tag('div');
1914 $ratinghtml .= html_writer::end_tag('form');
a09aeee4
AD
1915 }
1916
63e87951 1917 return $ratinghtml;
a09aeee4
AD
1918 }
1919
7a3c215b 1920 /**
d9c8f425 1921 * Centered heading with attached help button (same title text)
7a3c215b
SH
1922 * and optional icon attached.
1923 *
4bcc5118 1924 * @param string $text A heading text
53a78cef 1925 * @param string $helpidentifier The keyword that defines a help page
4bcc5118
PS
1926 * @param string $component component name
1927 * @param string|moodle_url $icon
1928 * @param string $iconalt icon alt text
d9c8f425 1929 * @return string HTML fragment
1930 */
7a3c215b 1931 public function heading_with_help($text, $helpidentifier, $component = 'moodle', $icon = '', $iconalt = '') {
4bcc5118
PS
1932 $image = '';
1933 if ($icon) {
0029a917 1934 $image = $this->pix_icon($icon, $iconalt, $component, array('class'=>'icon'));
d9c8f425 1935 }
4bcc5118 1936
259c165d
PS
1937 $help = '';
1938 if ($helpidentifier) {
1939 $help = $this->help_icon($helpidentifier, $component);
1940 }
4bcc5118
PS
1941
1942 return $this->heading($image.$text.$help, 2, 'main help');
d9c8f425 1943 }
1944
1945 /**
7a3c215b 1946 * Returns HTML to display a help icon.
d9c8f425 1947 *
cb616be8 1948 * @deprecated since Moodle 2.0
bf11293a 1949 */
596509e4 1950 public function old_help_icon($helpidentifier, $title, $component = 'moodle', $linktext = '') {
a6d81a73 1951 throw new coding_exception('old_help_icon() can not be used any more, please see help_icon().');
d9c8f425 1952 }
1953
259c165d 1954 /**
7a3c215b 1955 * Returns HTML to display a help icon.
259c165d 1956 *
2fada290
MG
1957 * Theme developers: DO NOT OVERRIDE! Please override function
1958 * {@link core_renderer::render_help_icon()} instead.
1959 *
259c165d
PS
1960 * @param string $identifier The keyword that defines a help page
1961 * @param string $component component name
1962 * @param string|bool $linktext true means use $title as link text, string means link text value
1963 * @return string HTML fragment
1964 */
1965 public function help_icon($identifier, $component = 'moodle', $linktext = '') {
2cf81209 1966 $icon = new help_icon($identifier, $component);
259c165d
PS
1967 $icon->diag_strings();
1968 if ($linktext === true) {
1969 $icon->linktext = get_string($icon->identifier, $icon->component);
1970 } else if (!empty($linktext)) {
1971 $icon->linktext = $linktext;
1972 }
1973 return $this->render($icon);
1974 }
1975
1976 /**
1977 * Implementation of user image rendering.
7a3c215b 1978 *
3d3fae72 1979 * @param help_icon $helpicon A help icon instance
259c165d
PS
1980 * @return string HTML fragment
1981 */
1982 protected function render_help_icon(help_icon $helpicon) {
1983 global $CFG;
1984
1985 // first get the help image icon
1986 $src = $this->pix_url('help');
1987
1988 $title = get_string($helpicon->identifier, $helpicon->component);
1989
1990 if (empty($helpicon->linktext)) {
cab2c7ea 1991 $alt = get_string('helpprefix2', '', trim($title, ". \t"));
259c165d
PS
1992 } else {
1993 $alt = get_string('helpwiththis');
1994 }
1995
1996 $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>'iconhelp');
1997 $output = html_writer::empty_tag('img', $attributes);
1998
1999 // add the link text if given
2000 if (!empty($helpicon->linktext)) {
2001 // the spacing has to be done through CSS
2002 $output .= $helpicon->linktext;
2003 }
2004
69542fb3
PS
2005 // now create the link around it - we need https on loginhttps pages
2006 $url = new moodle_url($CFG->httpswwwroot.'/help.php', array('component' => $helpicon->component, 'identifier' => $helpicon->identifier, 'lang'=>current_language()));
259c165d
PS
2007
2008 // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip
2009 $title = get_string('helpprefix2', '', trim($title, ". \t"));
2010
e88419a2 2011 $attributes = array('href' => $url, 'title' => $title, 'aria-haspopup' => 'true', 'target'=>'_blank');
259c165d
PS
2012 $output = html_writer::tag('a', $output, $attributes);
2013
2014 // and finally span
238b8bc9 2015 return html_writer::tag('span', $output, array('class' => 'helptooltip'));
259c165d
PS
2016 }
2017
d9c8f425 2018 /**
7a3c215b 2019 * Returns HTML to display a scale help icon.
d9c8f425 2020 *
4bcc5118 2021 * @param int $courseid
7a3c215b
SH
2022 * @param stdClass $scale instance
2023 * @return string HTML fragment
d9c8f425 2024 */
4bcc5118
PS
2025 public function help_icon_scale($courseid, stdClass $scale) {
2026 global $CFG;
02f64f97 2027
4bcc5118 2028 $title = get_string('helpprefix2', '', $scale->name) .' ('.get_string('newwindow').')';
02f64f97 2029
0029a917 2030 $icon = $this->pix_icon('help', get_string('scales'), 'moodle', array('class'=>'iconhelp'));
02f64f97 2031
68bf577b
AD
2032 $scaleid = abs($scale->id);
2033
2034 $link = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scaleid));
230ec401 2035 $action = new popup_action('click', $link, 'ratingscale');
02f64f97 2036
26acc814 2037 return html_writer::tag('span', $this->action_link($link, $icon, $action), array('class' => 'helplink'));
d9c8f425 2038 }
2039
2040 /**
2041 * Creates and returns a spacer image with optional line break.
2042 *
3d3fae72
SH
2043 * @param array $attributes Any HTML attributes to add to the spaced.
2044 * @param bool $br Include a BR after the spacer.... DON'T USE THIS. Don't be
2045 * laxy do it with CSS which is a much better solution.
d9c8f425 2046 * @return string HTML fragment
2047 */
0029a917
PS
2048 public function spacer(array $attributes = null, $br = false) {
2049 $attributes = (array)$attributes;
2050 if (empty($attributes['width'])) {
2051 $attributes['width'] = 1;
1ba862ec 2052 }
e1a5a9cc 2053 if (empty($attributes['height'])) {
0029a917 2054 $attributes['height'] = 1;
d9c8f425 2055 }
0029a917 2056 $attributes['class'] = 'spacer';
d9c8f425 2057
0029a917 2058 $output = $this->pix_icon('spacer', '', 'moodle', $attributes);
b65bfc3e 2059
0029a917 2060 if (!empty($br)) {
1ba862ec
PS
2061 $output .= '<br />';
2062 }
d9c8f425 2063
2064 return $output;
2065 }
2066
d9c8f425 2067 /**
7a3c215b 2068 * Returns HTML to display the specified user's avatar.
d9c8f425 2069 *
5d0c95a5 2070 * User avatar may be obtained in two ways:
d9c8f425 2071 * <pre>
812dbaf7
PS
2072 * // Option 1: (shortcut for simple cases, preferred way)
2073 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
2074 * $OUTPUT->user_picture($user, array('popup'=>true));
2075 *
5d0c95a5
PS
2076 * // Option 2:
2077 * $userpic = new user_picture($user);
d9c8f425 2078 * // Set properties of $userpic
812dbaf7 2079 * $userpic->popup = true;
5d0c95a5 2080 * $OUTPUT->render($userpic);
d9c8f425 2081 * </pre>
2082 *
2fada290
MG
2083 * Theme developers: DO NOT OVERRIDE! Please override function
2084 * {@link core_renderer::render_user_picture()} instead.
2085 *
7a3c215b 2086 * @param stdClass $user Object with at least fields id, picture, imagealt, firstname, lastname
812dbaf7 2087 * If any of these are missing, the database is queried. Avoid this
d9c8f425 2088 * if at all possible, particularly for reports. It is very bad for performance.
812dbaf7
PS
2089 * @param array $options associative array with user picture options, used only if not a user_picture object,
2090 * options are:
2091 * - courseid=$this->page->course->id (course id of user profile in link)
2092 * - size=35 (size of image)
2093 * - link=true (make image clickable - the link leads to user profile)
2094 * - popup=false (open in popup)
2095 * - alttext=true (add image alt attribute)
5d0c95a5 2096 * - class = image class attribute (default 'userpicture')
d9c8f425 2097 * @return string HTML fragment
2098 */
5d0c95a5
PS
2099 public function user_picture(stdClass $user, array $options = null) {
2100 $userpicture = new user_picture($user);
2101 foreach ((array)$options as $key=>$value) {
2102 if (array_key_exists($key, $userpicture)) {
2103 $userpicture->$key = $value;
2104 }
2105 }
2106 return $this->render($userpicture);
2107 }
2108
2109 /**
2110 * Internal implementation of user image rendering.
7a3c215b 2111 *
5d0c95a5
PS
2112 * @param user_picture $userpicture
2113 * @return string
2114 */
2115 protected function render_user_picture(user_picture $userpicture) {
2116 global $CFG, $DB;
812dbaf7 2117
5d0c95a5
PS
2118 $user = $userpicture->user;
2119
2120 if ($userpicture->alttext) {
2121 if (!empty($user->imagealt)) {
2122 $alt = $user->imagealt;
2123 } else {
2124 $alt = get_string('pictureof', '', fullname($user));
2125 }
d9c8f425 2126 } else {
97c10099 2127 $alt = '';
5d0c95a5
PS
2128 }
2129
2130 if (empty($userpicture->size)) {
5d0c95a5
PS
2131 $size = 35;
2132 } else if ($userpicture->size === true or $userpicture->size == 1) {
5d0c95a5 2133 $size = 100;
5d0c95a5 2134 } else {
5d0c95a5 2135 $size = $userpicture->size;
d9c8f425 2136 }
2137
5d0c95a5 2138 $class = $userpicture->class;
d9c8f425 2139
4d254790 2140 if ($user->picture == 0) {
5d0c95a5 2141 $class .= ' defaultuserpic';
5d0c95a5 2142 }
d9c8f425 2143
871a3ec5
SH
2144 $src = $userpicture->get_url($this->page, $this);
2145
29cf6631 2146 $attributes = array('src'=>$src, 'alt'=>$alt, 'title'=>$alt, 'class'=>$class, 'width'=>$size, 'height'=>$size);
5d0c95a5
PS
2147
2148 // get the image html output fisrt
0e35ba6f 2149 $output = html_writer::empty_tag('img', $attributes);
5d0c95a5
PS
2150
2151 // then wrap it in link if needed
2152 if (!$userpicture->link) {
2153 return $output;
d9c8f425 2154 }
2155
5d0c95a5
PS
2156 if (empty($userpicture->courseid)) {
2157 $courseid = $this->page->course->id;
2158 } else {
2159 $courseid = $userpicture->courseid;
2160 }
2161
03d9401e
MD
2162 if ($courseid == SITEID) {
2163 $url = new moodle_url('/user/profile.php', array('id' => $user->id));
2164 } else {
2165 $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid));
2166 }
5d0c95a5
PS
2167
2168 $attributes = array('href'=>$url);
2169
2170 if ($userpicture->popup) {
2171 $id = html_writer::random_id('userpicture');
2172 $attributes['id'] = $id;
c80877aa 2173 $this->add_action_handler(new popup_action('click', $url), $id);
5d0c95a5
PS
2174 }
2175
26acc814 2176 return html_writer::tag('a', $output, $attributes);
d9c8f425 2177 }
b80ef420 2178
b80ef420
DC
2179 /**
2180 * Internal implementation of file tree viewer items rendering.
7a3c215b 2181 *
b80ef420
DC
2182 * @param array $dir
2183 * @return string
2184 */
2185 public function htmllize_file_tree($dir) {
2186 if (empty($dir['subdirs']) and empty($dir['files'])) {
2187 return '';
2188 }
2189 $result = '<ul>';
2190 foreach ($dir['subdirs'] as $subdir) {
2191 $result .= '<li>'.s($subdir['dirname']).' '.$this->htmllize_file_tree($subdir).'</li>';
2192 }
2193 foreach ($dir['files'] as $file) {
2194 $filename = $file->get_filename();
2195 $result .= '<li><span>'.html_writer::link($file->fileurl, $filename).'</span></li>';
2196 }
2197 $result .= '</ul>';
2198
2199 return $result;
2200 }
7a3c215b 2201
bb496de7 2202 /**
7a3c215b 2203 * Returns HTML to display the file picker
bb496de7
DC
2204 *
2205 * <pre>
2206 * $OUTPUT->file_picker($options);
2207 * </pre>
2208 *
2fada290
MG
2209 * Theme developers: DO NOT OVERRIDE! Please override function
2210 * {@link core_renderer::render_file_picker()} instead.
2211 *
bb496de7
DC
2212 * @param array $options associative array with file manager options
2213 * options are:
2214 * maxbytes=>-1,
2215 * itemid=>0,
2216 * client_id=>uniqid(),
2217 * acepted_types=>'*',
2218 * return_types=>FILE_INTERNAL,
2219 * context=>$PAGE->context
2220 * @return string HTML fragment
2221 */
2222 public function file_picker($options) {
2223 $fp = new file_picker($options);
2224 return $this->render($fp);
2225 }
7a3c215b 2226
b80ef420
DC
2227 /**
2228 * Internal implementation of file picker rendering.
7a3c215b 2229 *
b80ef420
DC
2230 * @param file_picker $fp
2231 * @return string
2232 */
bb496de7
DC
2233 public function render_file_picker(file_picker $fp) {
2234 global $CFG, $OUTPUT, $USER;
2235 $options = $fp->options;
2236 $client_id = $options->client_id;
2237 $strsaved = get_string('filesaved', 'repository');
2238 $straddfile = get_string('openpicker', 'repository');
2239 $strloading = get_string('loading', 'repository');
adce0230 2240 $strdndenabled = get_string('dndenabled_inbox', 'moodle');
906e7d89 2241 $strdroptoupload = get_string('droptoupload', 'moodle');
bb496de7
DC
2242 $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
2243
2244 $currentfile = $options->currentfile;
2245 if (empty($currentfile)) {
322945e9
FM
2246 $currentfile = '';
2247 } else {
2248 $currentfile .= ' - ';
bb496de7 2249 }
b817205b
DC
2250 if ($options->maxbytes) {
2251 $size = $options->maxbytes;
2252 } else {
2253 $size = get_max_upload_file_size();
2254 }
513aed3c 2255 if ($size == -1) {
831399c4 2256 $maxsize = '';
513aed3c
DC
2257 } else {
2258 $maxsize = get_string('maxfilesize', 'moodle', display_size($size));
2259 }
f50a61fb 2260 if ($options->buttonname) {
4b72f9eb
AW
2261 $buttonname = ' name="' . $options->buttonname . '"';
2262 } else {
2263 $buttonname = '';
2264 }
bb496de7
DC
2265 $html = <<<EOD
2266<div class="filemanager-loading mdl-align" id='filepicker-loading-{$client_id}'>
2267$icon_progress
2268</div>
2269<div id="filepicker-wrapper-{$client_id}" class="mdl-left" style="display:none">
2270 <div>
c81f3328 2271 <input type="button" class="fp-btn-choose" id="filepicker-button-{$client_id}" value="{$straddfile}"{$buttonname}/>
fa7f2a45 2272 <span> $maxsize </span>
bb496de7
DC
2273 </div>
2274EOD;
2275 if ($options->env != 'url') {
2276 $html .= <<<EOD
50597880 2277 <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist" style="position: relative">
a9352f1f 2278 <div class="filepicker-filename">
08a6a19d 2279 <div class="filepicker-container">$currentfile<div class="dndupload-message">$strdndenabled <br/><div class="dndupload-arrow"></div></div></div>
0f94289c 2280 <div class="dndupload-progressbars"></div>
a9352f1f 2281 </div>
08a6a19d 2282 <div><div class="dndupload-target">{$strdroptoupload}<br/><div class="dndupload-arrow"></div></div></div>
f08fac7c 2283 </div>
bb496de7
DC
2284EOD;
2285 }
2286 $html .= '</div>';
2287 return $html;
2288 }
d9c8f425 2289
2290 /**
7a3c215b 2291 * Returns HTML to display the 'Update this Modulename' button that appears on module pages.
d9c8f425 2292 *
2293 * @param string $cmid the course_module id.
2294 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
2295 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
2296 */
2297 public function update_module_button($cmid, $modulename) {
2298 global $CFG;
b0c6dc1c 2299 if (has_capability('moodle/course:manageactivities', context_module::instance($cmid))) {
d9c8f425 2300 $modulename = get_string('modulename', $modulename);
2301 $string = get_string('updatethis', '', $modulename);
3ba60ee1
PS
2302 $url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
2303 return $this->single_button($url, $string);
d9c8f425 2304 } else {
2305 return '';
2306 }
2307 }
2308
2309 /**
7a3c215b
SH
2310 * Returns HTML to display a "Turn editing on/off" button in a form.
2311 *
d9c8f425 2312 * @param moodle_url $url The URL + params to send through when clicking the button
2313 * @return string HTML the button
2314 */
2315 public function edit_button(moodle_url $url) {
3362dfdc
EL
2316
2317 $url->param('sesskey', sesskey());
2318 if ($this->page->user_is_editing()) {
2319 $url->param('edit', 'off');
2320 $editstring = get_string('turneditingoff');
d9c8f425 2321 } else {
3362dfdc
EL
2322 $url->param('edit', 'on');
2323 $editstring = get_string('turneditingon');
d9c8f425 2324 }
2325
3362dfdc 2326 return $this->single_button($url, $editstring);
d9c8f425 2327 }
2328
d9c8f425 2329 /**
7a3c215b 2330 * Returns HTML to display a simple button to close a window
d9c8f425 2331 *
d9c8f425 2332 * @param string $text The lang string for the button's label (already output from get_string())
3ba60ee1 2333 * @return string html fragment
d9c8f425 2334 */
7a5c78e0 2335 public function close_window_button($text='') {
d9c8f425 2336 if (empty($text)) {
2337 $text = get_string('closewindow');
2338 }
a6855934
PS
2339 $button = new single_button(new moodle_url('#'), $text, 'get');
2340 $button->add_action(new component_action('click', 'close_window'));
3ba60ee1
PS
2341
2342 return $this->container($this->render($button), 'closewindow');
d9c8f425 2343 }
2344
d9c8f425 2345 /**
2346 * Output an error message. By default wraps the error message in <span class="error">.
2347 * If the error message is blank, nothing is output.
7a3c215b 2348 *
d9c8f425 2349 * @param string $message the error message.
2350 * @return string the HTML to output.
2351 */
2352 public function error_text($message) {
2353 if (empty($message)) {
2354 return '';
2355 }
3246648b 2356 $message = $this->pix_icon('i/warning', get_string('error'), '', array('class' => 'icon icon-pre', 'title'=>'')) . $message;
26acc814 2357 return html_writer::tag('span', $message, array('class' => 'error'));
d9c8f425 2358 }
2359
2360 /**
2361 * Do not call this function directly.
2362 *
f8129210 2363 * To terminate the current script with a fatal error, call the {@link print_error}
d9c8f425 2364 * function, or throw an exception. Doing either of those things will then call this
2365 * function to display the error, before terminating the execution.
2366 *
2367 * @param string $message The message to output
2368 * @param string $moreinfourl URL where more info can be found about the error
2369 * @param string $link Link for the Continue button
2370 * @param array $backtrace The execution backtrace
2371 * @param string $debuginfo Debugging information
d9c8f425 2372 * @return string the HTML to output.
2373 */
83267ec0 2374 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
6bd8d7e7 2375 global $CFG;
d9c8f425 2376
2377 $output = '';
6f8f4d83 2378 $obbuffer = '';
e57c283d 2379
d9c8f425 2380 if ($this->has_started()) {
50764d37
PS
2381 // we can not always recover properly here, we have problems with output buffering,
2382 // html tables, etc.
d9c8f425 2383 $output .= $this->opencontainers->pop_all_but_last();
50764d37 2384
d9c8f425 2385 } else {
50764d37
PS
2386 // It is really bad if library code throws exception when output buffering is on,
2387 // because the buffered text would be printed before our start of page.
2388 // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini
6bd8d7e7 2389 error_reporting(0); // disable notices from gzip compression, etc.
50764d37 2390 while (ob_get_level() > 0) {
2cadd443
PS
2391 $buff = ob_get_clean();
2392 if ($buff === false) {
2393 break;
2394 }
2395 $obbuffer .= $buff;
50764d37 2396 }
6bd8d7e7 2397 error_reporting($CFG->debug);
6f8f4d83 2398
f22f1caf
PS
2399 // Output not yet started.
2400 $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
2401 if (empty($_SERVER['HTTP_RANGE'])) {
2402 @header($protocol . ' 404 Not Found');
2403 } else {
2404 // Must stop byteserving attempts somehow,
2405 // this is weird but Chrome PDF viewer can be stopped only with 407!
2406 @header($protocol . ' 407 Proxy Authentication Required');
85309744 2407 }
f22f1caf 2408
eb5bdb35 2409 $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
7fde1e4b 2410 $this->page->set_url('/'); // no url
191b267b 2411 //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-(
dcfb9b78 2412 $this->page->set_title(get_string('error'));
8093188f 2413 $this->page->set_heading($this->page->course->fullname);
d9c8f425 2414 $output .= $this->header();
2415 }
2416
2417 $message = '<p class="errormessage">' . $message . '</p>'.
2418 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
2419 get_string('moreinformation') . '</a></p>';
1ad8143a
PS
2420 if (empty($CFG->rolesactive)) {
2421 $message .= '<p class="errormessage">' . get_string('installproblem', 'error') . '</p>';
2422 //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.
2423 }
3e76c7fa 2424 $output .= $this->box($message, 'errorbox', array('data-rel' => 'fatalerror'));
d9c8f425 2425
6f8f4d83
PS
2426 if (debugging('', DEBUG_DEVELOPER)) {
2427 if (!empty($debuginfo)) {
c5d18164
PS
2428 $debuginfo = s($debuginfo); // removes all nasty JS
2429 $debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines
2430 $output .= $this->notification('<strong>Debug info:</strong> '.$debuginfo, 'notifytiny');
6f8f4d83
PS
2431 }
2432 if (!empty($backtrace)) {
2433 $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny');
2434 }
2435 if ($obbuffer !== '' ) {
2436 $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny');
2437 }
d9c8f425 2438 }
2439
3efe6bbb
PS
2440 if (empty($CFG->rolesactive)) {
2441 // continue does not make much sense if moodle is not installed yet because error is most probably not recoverable
2442 } else if (!empty($link)) {
d9c8f425 2443 $output .= $this->continue_button($link);
2444 }
2445
2446 $output .= $this->footer();
2447
2448 // Padding to encourage IE to display our error page, rather than its own.
2449 $output .= str_repeat(' ', 512);
2450
2451 return $output;
2452 }
2453
2454 /**
2455 * Output a notification (that is, a status message about something that has
2456 * just happened).
2457 *
2458 * @param string $message the message to print out
2459 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
2460 * @return string the HTML to output.
2461 */
2462 public function notification($message, $classes = 'notifyproblem') {
26acc814 2463 return html_writer::tag('div', clean_text($message), array('class' => renderer_base::prepare_classes($classes)));
d9c8f425 2464 }
2465
2466 /**
7a3c215b 2467 * Returns HTML to display a continue button that goes to a particular URL.
d9c8f425 2468 *
3ba60ee1 2469 * @param string|moodle_url $url The url the button goes to.
d9c8f425 2470 * @return string the HTML to output.
2471 */
3ba60ee1
PS
2472 public function continue_button($url) {
2473 if (!($url instanceof moodle_url)) {
2474 $url = new moodle_url($url);
d9c8f425 2475 }
3ba60ee1
PS
2476 $button = new single_button($url, get_string('continue'), 'get');
2477 $button->class = 'continuebutton';
d9c8f425 2478
3ba60ee1 2479 return $this->render($button);
d9c8f425 2480 }
2481
2482 /**
7a3c215b 2483 * Returns HTML to display a single paging bar to provide access to other pages (usually in a search)
d9c8f425 2484 *
2fada290
MG
2485 * Theme developers: DO NOT OVERRIDE! Please override function
2486 * {@link core_renderer::render_paging_bar()} instead.
2487 *
71c03ac1 2488 * @param int $totalcount The total number of entries available to be paged through
929d7a83
PS
2489 * @param int $page The page you are currently viewing
2490 * @param int $perpage The number of entries that should be shown per page
2491 * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
2492 * @param string $pagevar name of page parameter that holds the page number
d9c8f425 2493 * @return string the HTML to output.
2494 */
929d7a83
PS
2495 public function paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
2496 $pb = new paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar);
2497 return $this->render($pb);
2498 }
2499
2500 /**
2501 * Internal implementation of paging bar rendering.
7a3c215b 2502 *
929d7a83
PS
2503 * @param paging_bar $pagingbar
2504 * @return string
2505 */
2506 protected function render_paging_bar(paging_bar $pagingbar) {
d9c8f425 2507 $output = '';
2508 $pagingbar = clone($pagingbar);
34059565 2509 $pagingbar->prepare($this, $this->page, $this->target);
d9c8f425 2510
2511 if ($pagingbar->totalcount > $pagingbar->perpage) {
2512 $output .= get_string('page') . ':';
2513
2514 if (!empty($pagingbar->previouslink)) {
56ddb719 2515 $output .= '&#160;(' . $pagingbar->previouslink . ')&#160;';
d9c8f425 2516 }
2517
2518 if (!empty($pagingbar->firstlink)) {
56ddb719 2519 $output .= '&#160;' . $pagingbar->firstlink . '&#160;...';
d9c8f425 2520 }
2521
2522 foreach ($pagingbar->pagelinks as $link) {
56ddb719 2523 $output .= "&#160;&#160;$link";
d9c8f425 2524 }
2525
2526 if (!empty($pagingbar->lastlink)) {
56ddb719 2527 $output .= '&#160;...' . $pagingbar->lastlink . '&#160;';
d9c8f425 2528 }
2529
2530 if (!empty($pagingbar->nextlink)) {
56ddb719 2531 $output .= '&#160;&#160;(' . $pagingbar->nextlink . ')';
d9c8f425 2532 }
2533 }
2534
26acc814 2535 return html_writer::tag('div', $output, array('class' => 'paging'));
d9c8f425 2536 }
2537
d9c8f425 2538 /**
2539 * Output the place a skip link goes to.
7a3c215b 2540 *
d9c8f425 2541 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2542 * @return string the HTML to output.
2543 */
fe213365 2544 public function skip_link_target($id = null) {
26acc814 2545 return html_writer::tag('span', '', array('id' => $id));
d9c8f425 2546 }
2547
2548 /**
2549 * Outputs a heading
7a3c215b 2550 *
d9c8f425 2551 * @param string $text The text of the heading
2552 * @param int $level The level of importance of the heading. Defaulting to 2
2553 * @param string $classes A space-separated list of CSS classes
2554 * @param string $id An optional ID
2555 * @return string the HTML to output.
2556 */
fe213365 2557 public function heading($text, $level = 2, $classes = 'main', $id = null) {
d9c8f425 2558 $level = (integer) $level;
2559 if ($level < 1 or $level > 6) {
2560 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2561 }
26acc814 2562 return html_writer::tag('h' . $level, $text, array('id' => $id, 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 2563 }
2564
2565 /**
2566 * Outputs a box.
7a3c215b 2567 *
d9c8f425 2568 * @param string $contents The contents of the box
2569 * @param string $classes A space-separated list of CSS classes
2570 * @param string $id An optional ID
3e76c7fa 2571 * @param array $attributes An array of other attributes to give the box.
d9c8f425 2572 * @return string the HTML to output.
2573 */
3e76c7fa
SH
2574 public function box($contents, $classes = 'generalbox', $id = null, $attributes = array()) {
2575 return $this->box_start($classes, $id, $attributes) . $contents . $this->box_end();
d9c8f425 2576 }
2577
2578 /**
3d3fae72 2579 * Outputs the opening section of a box.
7a3c215b 2580 *
d9c8f425 2581 * @param string $classes A space-separated list of CSS classes
2582 * @param string $id An optional ID
3e76c7fa 2583 * @param array $attributes An array of other attributes to give the box.
d9c8f425 2584 * @return string the HTML to output.
2585 */
3e76c7fa 2586 public function box_start($classes = 'generalbox', $id = null, $attributes = array()) {
5d0c95a5 2587 $this->opencontainers->push('box', html_writer::end_tag('div'));
3e76c7fa
SH
2588 $attributes['id'] = $id;
2589 $attributes['class'] = 'box ' . renderer_base::prepare_classes($classes);
2590 return html_writer::start_tag('div', $attributes);
d9c8f425 2591 }
2592
2593 /**
2594 * Outputs the closing section of a box.
7a3c215b 2595 *
d9c8f425 2596 * @return string the HTML to output.
2597 */
2598 public function box_end() {
2599 return $this->opencontainers->pop('box');
2600 }
2601
2602 /**
2603 * Outputs a container.
7a3c215b 2604 *
d9c8f425 2605 * @param string $contents The contents of the box
2606 * @param string $classes A space-separated list of CSS classes
2607 * @param string $id An optional ID
2608 * @return string the HTML to output.
2609 */
fe213365 2610 public function container($contents, $classes = null, $id = null) {
d9c8f425 2611 return $this->container_start($classes, $id) . $contents . $this->container_end();
2612 }
2613
2614 /**
2615 * Outputs the opening section of a container.
7a3c215b 2616 *
d9c8f425 2617 * @param string $classes A space-separated list of CSS classes
2618 * @param string $id An optional ID
2619 * @return string the HTML to output.
2620 */
fe213365 2621 public function container_start($classes = null, $id = null) {
5d0c95a5
PS
2622 $this->opencontainers->push('container', html_writer::end_tag('div'));
2623 return html_writer::start_tag('div', array('id' => $id,
78946b9b 2624 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 2625 }
2626
2627 /**
2628 * Outputs the closing section of a container.
7a3c215b 2629 *
d9c8f425 2630 * @return string the HTML to output.
2631 */
2632 public function container_end() {
2633 return $this->opencontainers->pop('container');
2634 }
7d2a0492 2635
3406acde 2636 /**
7d2a0492 2637 * Make nested HTML lists out of the items
2638 *
2639 * The resulting list will look something like this:
2640 *
2641 * <pre>
2642 * <<ul>>
2643 * <<li>><div class='tree_item parent'>(item contents)</div>
2644 * <<ul>
2645 * <<li>><div class='tree_item'>(item contents)</div><</li>>
2646 * <</ul>>
2647 * <</li>>
2648 * <</ul>>
2649 * </pre>
2650 *
7a3c215b
SH
2651 * @param array $items
2652 * @param array $attrs html attributes passed to the top ofs the list
7d2a0492 2653 * @return string HTML
2654 */
7a3c215b 2655 public function tree_block_contents($items, $attrs = array()) {
7d2a0492 2656 // exit if empty, we don't want an empty ul element
2657 if (empty($items)) {
2658 return '';
2659 }
2660 // array of nested li elements
2661 $lis = array();
2662 foreach ($items as $item) {
2663 // this applies to the li item which contains all child lists too
2664 $content = $item->content($this);
2665 $liclasses = array($item->get_css_type());
3406acde 2666 if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0 && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
7d2a0492 2667 $liclasses[] = 'collapsed';
2668 }
2669 if ($item->isactive === true) {
2670 $liclasses[] = 'current_branch';
2671 }
2672 $liattr = array('class'=>join(' ',$liclasses));
2673 // class attribute on the div item which only contains the item content
2674 $divclasses = array('tree_item');
3406acde 2675 if ($item->children->count()>0 || $item->nodetype==navigation_node::NODETYPE_BRANCH) {
7d2a0492 2676 $divclasses[] = 'branch';
2677 } else {
2678 $divclasses[] = 'leaf';
2679 }
2680 if (!empty($item->classes) && count($item->classes)>0) {
2681 $divclasses[] = join(' ', $item->classes);
2682 }
2683 $divattr = array('class'=>join(' ', $divclasses));
2684 if (!empty($item->id)) {
2685 $divattr['id'] = $item->id;
2686 }
26acc814 2687 $content = html_writer::tag('p', $content, $divattr) . $this->tree_block_contents($item->children);
7d2a0492 2688 if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
26acc814 2689 $content = html_writer::empty_tag('hr') . $content;
7d2a0492 2690 }
26acc814 2691 $content = html_writer::tag('li', $content, $liattr);
7d2a0492 2692 $lis[] = $content;
2693 }
26acc814 2694 return html_writer::tag('ul', implode("\n", $lis), $attrs);
7d2a0492 2695 }
2696
2697 /**
2698 * Return the navbar content so that it can be echoed out by the layout
7a3c215b 2699 *
7d2a0492 2700 * @return string XHTML navbar
2701 */
2702 public function navbar() {
3406acde 2703 $items = $this->page->navbar->get_items();
06a72e01
SH
2704 $itemcount = count($items);
2705 if ($itemcount === 0) {
2706 return '';
2707 }
3406acde 2708
3406acde
SH
2709 $htmlblocks = array();
2710 // Iterate the navarray and display each node
ffca6f4b
SH
2711 $separator = get_separator();
2712 for ($i=0;$i < $itemcount;$i++) {
2713 $item = $items[$i];
493a48f3 2714 $item->hideicon = true;
ffca6f4b
SH
2715 if ($i===0) {
2716 $content = html_writer::tag('li', $this->render($item));
2717 } else {
2718 $content = html_writer::tag('li', $separator.$this->render($item));
2719 }
2720 $htmlblocks[] = $content;
3406acde
SH
2721 }
2722
dcfb9b78
RW
2723 //accessibility: heading for navbar list (MDL-20446)
2724 $navbarcontent = html_writer::tag('span', get_string('pagepath'), array('class'=>'accesshide'));
27e30578 2725 $navbarcontent .= html_writer::tag('ul', join('', $htmlblocks), array('role'=>'navigation'));
3406acde 2726 // XHTML
dcfb9b78 2727 return $navbarcontent;
3406acde
SH
2728 }
2729
7a3c215b
SH
2730 /**
2731 * Renders a navigation node object.
2732 *
2733 * @param navigation_node $item The navigation node to render.
2734 * @return string HTML fragment
2735 */
3406acde
SH
2736 protected function render_navigation_node(navigation_node $item) {
2737 $content = $item->get_content();
2738 $title = $item->get_title();
493a48f3 2739 if ($item->icon instanceof renderable && !$item->hideicon) {
3406acde 2740 $icon = $this->render($item->icon);
48fa9484 2741 $content = $icon.$content; // use CSS for spacing of icons
3406acde
SH
2742 }
2743 if ($item->helpbutton !== null) {
32561caf 2744 $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton', 'tabindex'=>'0'));
3406acde
SH
2745 }
2746 if ($content === '') {
b4c458a3 2747 return '';
3406acde
SH
2748 }
2749 if ($item->action instanceof action_link) {
3406acde
SH
2750 $link = $item->action;
2751 if ($item->hidden) {
2752 $link->add_class('dimmed');
2753 }
0064f3cf
SH
2754 if (!empty($content)) {
2755 // Providing there is content we will use that for the link content.
2756 $link->text = $content;
2757 }
62594358 2758 $content = $this->render($link);
3406acde
SH
2759 } else if ($item->action instanceof moodle_url) {
2760 $attributes = array();
2761 if ($title !== '') {
2762 $attributes['title'] = $title;
2763 }
2764 if ($item->hidden) {
2765 $attributes['class'] = 'dimmed_text';
2766 }
2767 $content = html_writer::link($item->action, $content, $attributes);
2768
2769 } else if (is_string($item->action) || empty($item->action)) {
32561caf 2770 $attributes = array('tabindex'=>'0'); //add tab support to span but still maintain character stream sequence.
3406acde
SH
2771 if ($title !== '') {
2772 $attributes['title'] = $title;
2773 }
2774 if ($item->hidden) {
2775 $attributes['class'] = 'dimmed_text';
2776 }
2777 $content = html_writer::tag('span', $content, $attributes);
2778 }
2779 return $content;
7d2a0492 2780 }
92e01ab7
PS
2781
2782 /**
2783 * Accessibility: Right arrow-like character is
2784 * used in the breadcrumb trail, course navigation menu
2785 * (previous/next activity), calendar, and search forum block.
2786 * If the theme does not set characters, appropriate defaults
2787 * are set automatically. Please DO NOT
2788 * use &lt; &gt; &raquo; - these are confusing for blind users.
7a3c215b 2789 *
92e01ab7
PS
2790 * @return string
2791 */
2792 public function rarrow() {
2793 return $this->page->theme->rarrow;
2794 }
2795
2796 /**
2797 * Accessibility: Right arrow-like character is
2798 * used in the breadcrumb trail, course navigation menu
2799 * (previous/next activity), calendar, and search forum block.
2800 * If the theme does not set characters, appropriate defaults
2801 * are set automatically. Please DO NOT
2802 * use &lt; &gt; &raquo; - these are confusing for blind users.
7a3c215b 2803 *
92e01ab7
PS
2804 * @return string
2805 */
2806 public function larrow() {
2807 return $this->page->theme->larrow;
2808 }
088ccb43 2809
d2dbd0c0
SH
2810 /**
2811 * Returns the custom menu if one has been set
2812 *
71c03ac1 2813 * A custom menu can be configured by browsing to
d2dbd0c0
SH
2814 * Settings: Administration > Appearance > Themes > Theme settings
2815 * and then configuring the custommenu config setting as described.
4d2ee4c2 2816 *
2fada290
MG
2817 * Theme developers: DO NOT OVERRIDE! Please override function
2818 * {@link core_renderer::render_custom_menu()} instead.
2819 *
0f6d9349 2820 * @param string $custommenuitems - custom menuitems set by theme instead of global theme settings
d2dbd0c0
SH
2821 * @return string
2822 */
0f6d9349 2823 public function custom_menu($custommenuitems = '') {
12cc75ae 2824 global $CFG;
0f6d9349
DM
2825 if (empty($custommenuitems) && !empty($CFG->custommenuitems)) {
2826 $custommenuitems = $CFG->custommenuitems;
2827 }
2828 if (empty($custommenuitems)) {
12cc75ae
SH
2829 return '';
2830 }
0f6d9349 2831 $custommenu = new custom_menu($custommenuitems, current_language());
2fada290 2832 return $this->render($custommenu);
d2dbd0c0
SH
2833 }
2834
2835 /**
2836 * Renders a custom menu object (located in outputcomponents.php)
2837 *
2838 * The custom menu this method produces makes use of the YUI3 menunav widget
2839 * and requires very specific html elements and classes.
2840 *
2841 * @staticvar int $menucount
2842 * @param custom_menu $menu
2843 * @return string
2844 */
2845 protected function render_custom_menu(custom_menu $menu) {
2846 static $menucount = 0;
2847 // If the menu has no children return an empty string
2848 if (!$menu->has_children()) {
2849 return '';
2850 }
2851 // Increment the menu count. This is used for ID's that get worked with
2852 // in JavaScript as is essential
2853 $menucount++;
6c95e46a
SH
2854 // Initialise this custom menu (the custom menu object is contained in javascript-static
2855 $jscode = js_writer::function_call_with_Y('M.core_custom_menu.init', array('custom_menu_'.$menucount));
2856 $jscode = "(function(){{$jscode}})";
2857 $this->page->requires->yui_module('node-menunav', $jscode);
d2dbd0c0 2858 // Build the root nodes as required by YUI
06a72e01 2859 $content = html_writer::start_tag('div', array('id'=>'custom_menu_'.$menucount, 'class'=>'yui3-menu yui3-menu-horizontal javascript-disabled custom-menu'));
d2dbd0c0
SH
2860 $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
2861 $content .= html_writer::start_tag('ul');
2862 // Render each child
2863 foreach ($menu->get_children() as $item) {
2864 $content .= $this->render_custom_menu_item($item);
2865 }
2866 // Close the open tags
2867 $content .= html_writer::end_tag('ul');
2868 $content .= html_writer::end_tag('div');
2869 $content .= html_writer::end_tag('div');
2870 // Return the custom menu
2871 return $content;
2872 }
2873
2874 /**
2875 * Renders a custom menu node as part of a submenu
2876 *
2877 * The custom menu this method produces makes use of the YUI3 menunav widget
2878 * and requires very specific html elements and classes.
2879 *
7a3c215b 2880 * @see core:renderer::render_custom_menu()
d2dbd0c0
SH
2881 *
2882 * @staticvar int $submenucount
2883 * @param custom_menu_item $menunode
2884 * @return string
2885 */
2886 protected function render_custom_menu_item(custom_menu_item $menunode) {
2887 // Required to ensure we get unique trackable id's
2888 static $submenucount = 0;
2889 if ($menunode->has_children()) {
2890 // If the child has menus render it as a sub menu
2891 $submenucount++;
2892 $content = html_writer::start_tag('li');
2893 if ($menunode->get_url() !== null) {
2894 $url = $menunode->get_url();
2895 } else {
2896 $url = '#cm_submenu_'.$submenucount;
2897 }
2898 $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menu-label', 'title'=>$menunode->get_title()));
2899 $content .= html_writer::start_tag('div', array('id'=>'cm_submenu_'.$submenucount, 'class'=>'yui3-menu custom_menu_submenu'));
2900 $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
2901 $content .= html_writer::start_tag('ul');
2902 foreach ($menunode->get_children() as $menunode) {
2903 $content .= $this->render_custom_menu_item($menunode);
2904 }
2905 $content .= html_writer::end_tag('ul');
2906 $content .= html_writer::end_tag('div');
2907 $content .= html_writer::end_tag('div');
2908 $content .= html_writer::end_tag('li');
2909 } else {
2910 // The node doesn't have children so produce a final menuitem
2911 $content = html_writer::start_tag('li', array('class'=>'yui3-menuitem'));
2912 if ($menunode->get_url() !== null) {
2913 $url = $menunode->get_url();
2914 } else {
2915 $url = '#';
2916 }
2917 $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menuitem-content', 'title'=>$menunode->get_title()));
2918 $content .= html_writer::end_tag('li');
2919 }
2920 // Return the sub menu
2921 return $content;
2922 }
37959dd4 2923
e5824bb9 2924 /**
37959dd4 2925 * Renders theme links for switching between default and other themes.
e5824bb9
SH
2926 *
2927 * @return string
37959dd4
AF
2928 */
2929 protected function theme_switch_links() {
37959dd4 2930
e5824bb9
SH
2931 $actualdevice = get_device_type();
2932 $currentdevice = $this->page->devicetypeinuse;
2933 $switched = ($actualdevice != $currentdevice);
37959dd4 2934
e5824bb9
SH
2935 if (!$switched && $currentdevice == 'default' && $actualdevice == 'default') {
2936 // The user is using the a default device and hasn't switched so don't shown the switch
2937 // device links.
37959dd4
AF
2938 return '';
2939 }
2940
37959dd4
AF
2941 if ($switched) {
2942 $linktext = get_string('switchdevicerecommended');
e5824bb9 2943 $devicetype = $actualdevice;
37959dd4
AF
2944 } else {
2945 $linktext = get_string('switchdevicedefault');
e5824bb9 2946 $devicetype = 'default';
37959dd4 2947 }
e5824bb9 2948 $linkurl = new moodle_url('/theme/switchdevice.php', array('url' => $this->page->url, 'device' => $devicetype, 'sesskey' => sesskey()));
37959dd4 2949
e5824bb9 2950 $content = html_writer::start_tag('div', array('id' => 'theme_switch_link'));
37959dd4
AF
2951 $content .= html_writer::link($linkurl, $linktext);
2952 $content .= html_writer::end_tag('div');
2953
2954 return $content;
2955 }
c269b9d1
MG
2956
2957 /**
2958 * Renders tabs
2959 *
2960 * This function replaces print_tabs() used before Moodle 2.5 but with slightly different arguments
2961 *
2fada290
MG
2962 * Theme developers: In order to change how tabs are displayed please override functions
2963 * {@link core_renderer::render_tabtree()} and/or {@link core_renderer::render_tabobject()}
2964 *
c269b9d1
MG
2965 * @param array $tabs array of tabs, each of them may have it's own ->subtree
2966 * @param string|null $selected which tab to mark as selected, all parent tabs will
2967 * automatically be marked as activated
2968 * @param array|string|null $inactive list of ids of inactive tabs, regardless of
2969 * their level. Note that you can as weel specify tabobject::$inactive for separate instances
2970 * @return string
2971 */
2fada290 2972 public final function tabtree($tabs, $selected = null, $inactive = null) {
c269b9d1
MG
2973 return $this->render(new tabtree($tabs, $selected, $inactive));
2974 }
2975
2976 /**
2977 * Renders tabtree
2978 *
2979 * @param tabtree $tabtree
2980 * @return string
2981 */
2982 protected function render_tabtree(tabtree $tabtree) {
2983 if (empty($tabtree->subtree)) {
2984 return '';
2985 }
2986 $str = '';
2987 $str .= html_writer::start_tag('div', array('class' => 'tabtree'));
2988 $str .= $this->render_tabobject($tabtree);
2989 $str .= html_writer::end_tag('div').
2990 html_writer::tag('div', ' ', array('class' => 'clearer'));
3d7414b3 2991 return $str;
c269b9d1
MG
2992 }
2993
2994 /**
2995 * Renders tabobject (part of tabtree)
2996 *
2997 * This function is called from {@link core_renderer::render_tabtree()}
2998 * and also it calls itself when printing the $tabobject subtree recursively.
2999 *
3000 * Property $tabobject->level indicates the number of row of tabs.
3001 *
3002 * @param tabobject $tabobject
3003 * @return string HTML fragment
3004 */
3005 protected function render_tabobject(tabobject $tabobject) {
3006 $str = '';
3007
3008 // Print name of the current tab.
3009 if ($tabobject instanceof tabtree) {
3010 // No name for tabtree root.
3011 } else if ($tabobject->inactive || $tabobject->activated || ($tabobject->selected && !$tabobject->linkedwhenselected)) {
3012 // Tab name without a link. The <a> tag is used for styling.
3013 $str .= html_writer::tag('a', html_writer::span($tabobject->text), array('class' => 'nolink'));
3014 } else {
3015 // Tab name with a link.
3016 if (!($tabobject->link instanceof moodle_url)) {
3017 // backward compartibility when link was passed as quoted string
3018 $str .= "<a href=\"$tabobject->link\" title=\"$tabobject->title\"><span>$tabobject->text</span></a>";
3019 } else {
3020 $str .= html_writer::link($tabobject->link, html_writer::span($tabobject->text), array('title' => $tabobject->title));
3021 }
3022 }
3023
3024 if (empty($tabobject->subtree)) {
3025 if ($tabobject->selected) {
3026 $str .= html_writer::tag('div', '&nbsp;', array('class' => 'tabrow'. ($tabobject->level + 1). ' empty'));
3027 }
3028 return $str;
3029 }
3030
3031 // Print subtree
3032 $str .= html_writer::start_tag('ul', array('class' => 'tabrow'. $tabobject->level));
3033 $cnt = 0;
3034 foreach ($tabobject->subtree as $tab) {
3035 $liclass = '';
3036 if (!$cnt) {
3037 $liclass .= ' first';
3038 }
3039 if ($cnt == count($tabobject->subtree) - 1) {
3040 $liclass .= ' last';
3041 }
3042 if ((empty($tab->subtree)) && (!empty($tab->selected))) {
3043 $liclass .= ' onerow';
3044 }
3045
3046 if ($tab->selected) {
3047 $liclass .= ' here selected';
3048 } else if ($tab->activated) {
3049 $liclass .= ' here active';
3050 }
3051
3052 // This will recursively call function render_tabobject() for each item in subtree
3053 $str .= html_writer::tag('li', $this->render($tab), array('class' => trim($liclass)));
3054 $cnt++;
3055 }
3056 $str .= html_writer::end_tag('ul');
3057
3058 return $str;
3059 }
8cf2b9ab
SH
3060
3061 /**
3062 * Get the HTML for blocks in the given region.
3063 *
3064 * @since 2.5.1 2.6
3065 * @param string $region The region to get HTML for.
3066 * @return string HTML.
3067 */
3068 public function blocks($region, $classes = array(), $tag = 'aside') {
a2f8bccf 3069 $displayregion = $this->page->apply_theme_region_manipulations($region);
8cf2b9ab
SH
3070 $classes = (array)$classes;
3071 $classes[] = 'block-region';
3072 $attributes = array(
a2f8bccf 3073 'id' => 'block-region-'.preg_replace('#[^a-zA-Z0-9_\-]+#', '-', $displayregion),
8cf2b9ab 3074 'class' => join(' ', $classes),
a2f8bccf 3075 'data-blockregion' => $displayregion,
8cf2b9ab
SH
3076 'data-droptarget' => '1'
3077 );
3078 return html_writer::tag($tag, $this->blocks_for_region($region), $attributes);
3079 }
3707972a
SH
3080
3081 /**
3082 * Returns the CSS classes to apply to the body tag.
3083 *
3084 * @since 2.5.1 2.6
3085 * @param array $additionalclasses Any additional classes to apply.
3086 * @return string
3087 */
3088 public function body_css_classes(array $additionalclasses = array()) {
3089 // Add a class for each block region on the page.
3090 // We use the block manager here because the theme object makes get_string calls.
3091 foreach ($this->page->blocks->get_regions() as $region) {
3092 $additionalclasses[] = 'has-region-'.$region;
3093 if ($this->page->blocks->region_has_content($region, $this)) {
3094 $additionalclasses[] = 'used-region-'.$region;
3095 } else {
3096 $additionalclasses[] = 'empty-region-'.$region;
3097 }
3098 }
3099 foreach ($this->page->layout_options as $option => $value) {
3100 if ($value) {
3101 $additionalclasses[] = 'layout-option-'.$option;