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