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