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