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