PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/outputrenderers.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 4036 lines | 2040 code | 409 blank | 1587 comment | 444 complexity | 4862e94af8a96ce6044417edac40dbff MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Classes for rendering HTML output for Moodle.
  18. *
  19. * Please see {@link http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML}
  20. * for an overview.
  21. *
  22. * Included in this file are the primary renderer classes:
  23. * - renderer_base: The renderer outline class that all renderers
  24. * should inherit from.
  25. * - core_renderer: The standard HTML renderer.
  26. * - core_renderer_cli: An adaption of the standard renderer for CLI scripts.
  27. * - core_renderer_ajax: An adaption of the standard renderer for AJAX scripts.
  28. * - plugin_renderer_base: A renderer class that should be extended by all
  29. * plugin renderers.
  30. *
  31. * @package core
  32. * @category output
  33. * @copyright 2009 Tim Hunt
  34. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35. */
  36. defined('MOODLE_INTERNAL') || die();
  37. /**
  38. * Simple base class for Moodle renderers.
  39. *
  40. * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
  41. *
  42. * Also has methods to facilitate generating HTML output.
  43. *
  44. * @copyright 2009 Tim Hunt
  45. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  46. * @since Moodle 2.0
  47. * @package core
  48. * @category output
  49. */
  50. class renderer_base {
  51. /**
  52. * @var xhtml_container_stack The xhtml_container_stack to use.
  53. */
  54. protected $opencontainers;
  55. /**
  56. * @var moodle_page The Moodle page the renderer has been created to assist with.
  57. */
  58. protected $page;
  59. /**
  60. * @var string The requested rendering target.
  61. */
  62. protected $target;
  63. /**
  64. * Constructor
  65. *
  66. * The constructor takes two arguments. The first is the page that the renderer
  67. * has been created to assist with, and the second is the target.
  68. * The target is an additional identifier that can be used to load different
  69. * renderers for different options.
  70. *
  71. * @param moodle_page $page the page we are doing output for.
  72. * @param string $target one of rendering target constants
  73. */
  74. public function __construct(moodle_page $page, $target) {
  75. $this->opencontainers = $page->opencontainers;
  76. $this->page = $page;
  77. $this->target = $target;
  78. }
  79. /**
  80. * Returns rendered widget.
  81. *
  82. * The provided widget needs to be an object that extends the renderable
  83. * interface.
  84. * If will then be rendered by a method based upon the classname for the widget.
  85. * For instance a widget of class `crazywidget` will be rendered by a protected
  86. * render_crazywidget method of this renderer.
  87. *
  88. * @param renderable $widget instance with renderable interface
  89. * @return string
  90. */
  91. public function render(renderable $widget) {
  92. $rendermethod = 'render_'.get_class($widget);
  93. if (method_exists($this, $rendermethod)) {
  94. return $this->$rendermethod($widget);
  95. }
  96. throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.');
  97. }
  98. /**
  99. * Adds a JS action for the element with the provided id.
  100. *
  101. * This method adds a JS event for the provided component action to the page
  102. * and then returns the id that the event has been attached to.
  103. * If no id has been provided then a new ID is generated by {@link html_writer::random_id()}
  104. *
  105. * @param component_action $action
  106. * @param string $id
  107. * @return string id of element, either original submitted or random new if not supplied
  108. */
  109. public function add_action_handler(component_action $action, $id = null) {
  110. if (!$id) {
  111. $id = html_writer::random_id($action->event);
  112. }
  113. $this->page->requires->event_handler("#$id", $action->event, $action->jsfunction, $action->jsfunctionargs);
  114. return $id;
  115. }
  116. /**
  117. * Returns true is output has already started, and false if not.
  118. *
  119. * @return boolean true if the header has been printed.
  120. */
  121. public function has_started() {
  122. return $this->page->state >= moodle_page::STATE_IN_BODY;
  123. }
  124. /**
  125. * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
  126. *
  127. * @param mixed $classes Space-separated string or array of classes
  128. * @return string HTML class attribute value
  129. */
  130. public static function prepare_classes($classes) {
  131. if (is_array($classes)) {
  132. return implode(' ', array_unique($classes));
  133. }
  134. return $classes;
  135. }
  136. /**
  137. * Return the moodle_url for an image.
  138. *
  139. * The exact image location and extension is determined
  140. * automatically by searching for gif|png|jpg|jpeg, please
  141. * note there can not be diferent images with the different
  142. * extension. The imagename is for historical reasons
  143. * a relative path name, it may be changed later for core
  144. * images. It is recommended to not use subdirectories
  145. * in plugin and theme pix directories.
  146. *
  147. * There are three types of images:
  148. * 1/ theme images - stored in theme/mytheme/pix/,
  149. * use component 'theme'
  150. * 2/ core images - stored in /pix/,
  151. * overridden via theme/mytheme/pix_core/
  152. * 3/ plugin images - stored in mod/mymodule/pix,
  153. * overridden via theme/mytheme/pix_plugins/mod/mymodule/,
  154. * example: pix_url('comment', 'mod_glossary')
  155. *
  156. * @param string $imagename the pathname of the image
  157. * @param string $component full plugin name (aka component) or 'theme'
  158. * @return moodle_url
  159. */
  160. public function pix_url($imagename, $component = 'moodle') {
  161. return $this->page->theme->pix_url($imagename, $component);
  162. }
  163. }
  164. /**
  165. * Basis for all plugin renderers.
  166. *
  167. * @copyright Petr Skoda (skodak)
  168. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  169. * @since Moodle 2.0
  170. * @package core
  171. * @category output
  172. */
  173. class plugin_renderer_base extends renderer_base {
  174. /**
  175. * @var renderer_base|core_renderer A reference to the current renderer.
  176. * The renderer provided here will be determined by the page but will in 90%
  177. * of cases by the {@link core_renderer}
  178. */
  179. protected $output;
  180. /**
  181. * Constructor method, calls the parent constructor
  182. *
  183. * @param moodle_page $page
  184. * @param string $target one of rendering target constants
  185. */
  186. public function __construct(moodle_page $page, $target) {
  187. if (empty($target) && $page->pagelayout === 'maintenance') {
  188. // If the page is using the maintenance layout then we're going to force the target to maintenance.
  189. // This way we'll get a special maintenance renderer that is designed to block access to API's that are likely
  190. // unavailable for this page layout.
  191. $target = RENDERER_TARGET_MAINTENANCE;
  192. }
  193. $this->output = $page->get_renderer('core', null, $target);
  194. parent::__construct($page, $target);
  195. }
  196. /**
  197. * Renders the provided widget and returns the HTML to display it.
  198. *
  199. * @param renderable $widget instance with renderable interface
  200. * @return string
  201. */
  202. public function render(renderable $widget) {
  203. $rendermethod = 'render_'.get_class($widget);
  204. if (method_exists($this, $rendermethod)) {
  205. return $this->$rendermethod($widget);
  206. }
  207. // pass to core renderer if method not found here
  208. return $this->output->render($widget);
  209. }
  210. /**
  211. * Magic method used to pass calls otherwise meant for the standard renderer
  212. * to it to ensure we don't go causing unnecessary grief.
  213. *
  214. * @param string $method
  215. * @param array $arguments
  216. * @return mixed
  217. */
  218. public function __call($method, $arguments) {
  219. if (method_exists('renderer_base', $method)) {
  220. throw new coding_exception('Protected method called against '.get_class($this).' :: '.$method);
  221. }
  222. if (method_exists($this->output, $method)) {
  223. return call_user_func_array(array($this->output, $method), $arguments);
  224. } else {
  225. throw new coding_exception('Unknown method called against '.get_class($this).' :: '.$method);
  226. }
  227. }
  228. }
  229. /**
  230. * The standard implementation of the core_renderer interface.
  231. *
  232. * @copyright 2009 Tim Hunt
  233. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  234. * @since Moodle 2.0
  235. * @package core
  236. * @category output
  237. */
  238. class core_renderer extends renderer_base {
  239. /**
  240. * Do NOT use, please use <?php echo $OUTPUT->main_content() ?>
  241. * in layout files instead.
  242. * @deprecated
  243. * @var string used in {@link core_renderer::header()}.
  244. */
  245. const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
  246. /**
  247. * @var string Used to pass information from {@link core_renderer::doctype()} to
  248. * {@link core_renderer::standard_head_html()}.
  249. */
  250. protected $contenttype;
  251. /**
  252. * @var string Used by {@link core_renderer::redirect_message()} method to communicate
  253. * with {@link core_renderer::header()}.
  254. */
  255. protected $metarefreshtag = '';
  256. /**
  257. * @var string Unique token for the closing HTML
  258. */
  259. protected $unique_end_html_token;
  260. /**
  261. * @var string Unique token for performance information
  262. */
  263. protected $unique_performance_info_token;
  264. /**
  265. * @var string Unique token for the main content.
  266. */
  267. protected $unique_main_content_token;
  268. /**
  269. * Constructor
  270. *
  271. * @param moodle_page $page the page we are doing output for.
  272. * @param string $target one of rendering target constants
  273. */
  274. public function __construct(moodle_page $page, $target) {
  275. $this->opencontainers = $page->opencontainers;
  276. $this->page = $page;
  277. $this->target = $target;
  278. $this->unique_end_html_token = '%%ENDHTML-'.sesskey().'%%';
  279. $this->unique_performance_info_token = '%%PERFORMANCEINFO-'.sesskey().'%%';
  280. $this->unique_main_content_token = '[MAIN CONTENT GOES HERE - '.sesskey().']';
  281. }
  282. /**
  283. * Get the DOCTYPE declaration that should be used with this page. Designed to
  284. * be called in theme layout.php files.
  285. *
  286. * @return string the DOCTYPE declaration that should be used.
  287. */
  288. public function doctype() {
  289. if ($this->page->theme->doctype === 'html5') {
  290. $this->contenttype = 'text/html; charset=utf-8';
  291. return "<!DOCTYPE html>\n";
  292. } else if ($this->page->theme->doctype === 'xhtml5') {
  293. $this->contenttype = 'application/xhtml+xml; charset=utf-8';
  294. return "<!DOCTYPE html>\n";
  295. } else {
  296. // legacy xhtml 1.0
  297. $this->contenttype = 'text/html; charset=utf-8';
  298. return ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n");
  299. }
  300. }
  301. /**
  302. * The attributes that should be added to the <html> tag. Designed to
  303. * be called in theme layout.php files.
  304. *
  305. * @return string HTML fragment.
  306. */
  307. public function htmlattributes() {
  308. $return = get_html_lang(true);
  309. if ($this->page->theme->doctype !== 'html5') {
  310. $return .= ' xmlns="http://www.w3.org/1999/xhtml"';
  311. }
  312. return $return;
  313. }
  314. /**
  315. * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
  316. * that should be included in the <head> tag. Designed to be called in theme
  317. * layout.php files.
  318. *
  319. * @return string HTML fragment.
  320. */
  321. public function standard_head_html() {
  322. global $CFG, $SESSION;
  323. // Before we output any content, we need to ensure that certain
  324. // page components are set up.
  325. // Blocks must be set up early as they may require javascript which
  326. // has to be included in the page header before output is created.
  327. foreach ($this->page->blocks->get_regions() as $region) {
  328. $this->page->blocks->ensure_content_created($region, $this);
  329. }
  330. $output = '';
  331. $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
  332. $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
  333. if (!$this->page->cacheable) {
  334. $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
  335. $output .= '<meta http-equiv="expires" content="0" />' . "\n";
  336. }
  337. // This is only set by the {@link redirect()} method
  338. $output .= $this->metarefreshtag;
  339. // Check if a periodic refresh delay has been set and make sure we arn't
  340. // already meta refreshing
  341. if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
  342. $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
  343. }
  344. // flow player embedding support
  345. $this->page->requires->js_function_call('M.util.load_flowplayer');
  346. // Set up help link popups for all links with the helptooltip class
  347. $this->page->requires->js_init_call('M.util.help_popups.setup');
  348. // Setup help icon overlays.
  349. $this->page->requires->yui_module('moodle-core-popuphelp', 'M.core.init_popuphelp');
  350. $this->page->requires->strings_for_js(array(
  351. 'morehelp',
  352. 'loadinghelp',
  353. ), 'moodle');
  354. $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
  355. $focus = $this->page->focuscontrol;
  356. if (!empty($focus)) {
  357. if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
  358. // This is a horrifically bad way to handle focus but it is passed in
  359. // through messy formslib::moodleform
  360. $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
  361. } else if (strpos($focus, '.')!==false) {
  362. // Old style of focus, bad way to do it
  363. 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);
  364. $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
  365. } else {
  366. // Focus element with given id
  367. $this->page->requires->js_function_call('focuscontrol', array($focus));
  368. }
  369. }
  370. // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins;
  371. // any other custom CSS can not be overridden via themes and is highly discouraged
  372. $urls = $this->page->theme->css_urls($this->page);
  373. foreach ($urls as $url) {
  374. $this->page->requires->css_theme($url);
  375. }
  376. // Get the theme javascript head and footer
  377. if ($jsurl = $this->page->theme->javascript_url(true)) {
  378. $this->page->requires->js($jsurl, true);
  379. }
  380. if ($jsurl = $this->page->theme->javascript_url(false)) {
  381. $this->page->requires->js($jsurl);
  382. }
  383. // Get any HTML from the page_requirements_manager.
  384. $output .= $this->page->requires->get_head_code($this->page, $this);
  385. // List alternate versions.
  386. foreach ($this->page->alternateversions as $type => $alt) {
  387. $output .= html_writer::empty_tag('link', array('rel' => 'alternate',
  388. 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
  389. }
  390. if (!empty($CFG->additionalhtmlhead)) {
  391. $output .= "\n".$CFG->additionalhtmlhead;
  392. }
  393. return $output;
  394. }
  395. /**
  396. * The standard tags (typically skip links) that should be output just inside
  397. * the start of the <body> tag. Designed to be called in theme layout.php files.
  398. *
  399. * @return string HTML fragment.
  400. */
  401. public function standard_top_of_body_html() {
  402. global $CFG;
  403. $output = $this->page->requires->get_top_of_body_code();
  404. if (!empty($CFG->additionalhtmltopofbody)) {
  405. $output .= "\n".$CFG->additionalhtmltopofbody;
  406. }
  407. $output .= $this->maintenance_warning();
  408. return $output;
  409. }
  410. /**
  411. * Scheduled maintenance warning message.
  412. *
  413. * Note: This is a nasty hack to display maintenance notice, this should be moved
  414. * to some general notification area once we have it.
  415. *
  416. * @return string
  417. */
  418. public function maintenance_warning() {
  419. global $CFG;
  420. $output = '';
  421. if (isset($CFG->maintenance_later) and $CFG->maintenance_later > time()) {
  422. $output .= $this->box_start('errorbox maintenancewarning');
  423. $output .= get_string('maintenancemodeisscheduled', 'admin', (int)(($CFG->maintenance_later-time())/60));
  424. $output .= $this->box_end();
  425. }
  426. return $output;
  427. }
  428. /**
  429. * The standard tags (typically performance information and validation links,
  430. * if we are in developer debug mode) that should be output in the footer area
  431. * of the page. Designed to be called in theme layout.php files.
  432. *
  433. * @return string HTML fragment.
  434. */
  435. public function standard_footer_html() {
  436. global $CFG, $SCRIPT;
  437. if (during_initial_install()) {
  438. // Debugging info can not work before install is finished,
  439. // in any case we do not want any links during installation!
  440. return '';
  441. }
  442. // This function is normally called from a layout.php file in {@link core_renderer::header()}
  443. // but some of the content won't be known until later, so we return a placeholder
  444. // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
  445. $output = $this->unique_performance_info_token;
  446. if ($this->page->devicetypeinuse == 'legacy') {
  447. // The legacy theme is in use print the notification
  448. $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse'));
  449. }
  450. // Get links to switch device types (only shown for users not on a default device)
  451. $output .= $this->theme_switch_links();
  452. if (!empty($CFG->debugpageinfo)) {
  453. $output .= '<div class="performanceinfo pageinfo">This page is: ' . $this->page->debug_summary() . '</div>';
  454. }
  455. if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', context_system::instance())) { // Only in developer mode
  456. // Add link to profiling report if necessary
  457. if (function_exists('profiling_is_running') && profiling_is_running()) {
  458. $txt = get_string('profiledscript', 'admin');
  459. $title = get_string('profiledscriptview', 'admin');
  460. $url = $CFG->wwwroot . '/admin/tool/profiling/index.php?script=' . urlencode($SCRIPT);
  461. $link= '<a title="' . $title . '" href="' . $url . '">' . $txt . '</a>';
  462. $output .= '<div class="profilingfooter">' . $link . '</div>';
  463. }
  464. $purgeurl = new moodle_url('/admin/purgecaches.php', array('confirm' => 1,
  465. 'sesskey' => sesskey(), 'returnurl' => $this->page->url->out_as_local_url(false)));
  466. $output .= '<div class="purgecaches">' .
  467. html_writer::link($purgeurl, get_string('purgecaches', 'admin')) . '</div>';
  468. }
  469. if (!empty($CFG->debugvalidators)) {
  470. // NOTE: this is not a nice hack, $PAGE->url is not always accurate and $FULLME neither, it is not a bug if it fails. --skodak
  471. $output .= '<div class="validators"><ul>
  472. <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
  473. <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
  474. <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>
  475. </ul></div>';
  476. }
  477. return $output;
  478. }
  479. /**
  480. * Returns standard main content placeholder.
  481. * Designed to be called in theme layout.php files.
  482. *
  483. * @return string HTML fragment.
  484. */
  485. public function main_content() {
  486. // This is here because it is the only place we can inject the "main" role over the entire main content area
  487. // without requiring all theme's to manually do it, and without creating yet another thing people need to
  488. // remember in the theme.
  489. // This is an unfortunate hack. DO NO EVER add anything more here.
  490. // DO NOT add classes.
  491. // DO NOT add an id.
  492. return '<div role="main">'.$this->unique_main_content_token.'</div>';
  493. }
  494. /**
  495. * The standard tags (typically script tags that are not needed earlier) that
  496. * should be output after everything else. Designed to be called in theme layout.php files.
  497. *
  498. * @return string HTML fragment.
  499. */
  500. public function standard_end_of_body_html() {
  501. global $CFG;
  502. // This function is normally called from a layout.php file in {@link core_renderer::header()}
  503. // but some of the content won't be known until later, so we return a placeholder
  504. // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
  505. $output = '';
  506. if (!empty($CFG->additionalhtmlfooter)) {
  507. $output .= "\n".$CFG->additionalhtmlfooter;
  508. }
  509. $output .= $this->unique_end_html_token;
  510. return $output;
  511. }
  512. /**
  513. * Return the standard string that says whether you are logged in (and switched
  514. * roles/logged in as another user).
  515. * @param bool $withlinks if false, then don't include any links in the HTML produced.
  516. * If not set, the default is the nologinlinks option from the theme config.php file,
  517. * and if that is not set, then links are included.
  518. * @return string HTML fragment.
  519. */
  520. public function login_info($withlinks = null) {
  521. global $USER, $CFG, $DB, $SESSION;
  522. if (during_initial_install()) {
  523. return '';
  524. }
  525. if (is_null($withlinks)) {
  526. $withlinks = empty($this->page->layout_options['nologinlinks']);
  527. }
  528. $loginpage = ((string)$this->page->url === get_login_url());
  529. $course = $this->page->course;
  530. if (\core\session\manager::is_loggedinas()) {
  531. $realuser = \core\session\manager::get_realuser();
  532. $fullname = fullname($realuser, true);
  533. if ($withlinks) {
  534. $loginastitle = get_string('loginas');
  535. $realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&amp;sesskey=".sesskey()."\"";
  536. $realuserinfo .= "title =\"".$loginastitle."\">$fullname</a>] ";
  537. } else {
  538. $realuserinfo = " [$fullname] ";
  539. }
  540. } else {
  541. $realuserinfo = '';
  542. }
  543. $loginurl = get_login_url();
  544. if (empty($course->id)) {
  545. // $course->id is not defined during installation
  546. return '';
  547. } else if (isloggedin()) {
  548. $context = context_course::instance($course->id);
  549. $fullname = fullname($USER, true);
  550. // Since Moodle 2.0 this link always goes to the public profile page (not the course profile page)
  551. if ($withlinks) {
  552. $linktitle = get_string('viewprofile');
  553. $username = "<a href=\"$CFG->wwwroot/user/profile.php?id=$USER->id\" title=\"$linktitle\">$fullname</a>";
  554. } else {
  555. $username = $fullname;
  556. }
  557. if (is_mnet_remote_user($USER) and $idprovider = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid))) {
  558. if ($withlinks) {
  559. $username .= " from <a href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>";
  560. } else {
  561. $username .= " from {$idprovider->name}";
  562. }
  563. }
  564. if (isguestuser()) {
  565. $loggedinas = $realuserinfo.get_string('loggedinasguest');
  566. if (!$loginpage && $withlinks) {
  567. $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
  568. }
  569. } else if (is_role_switched($course->id)) { // Has switched roles
  570. $rolename = '';
  571. if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
  572. $rolename = ': '.role_get_name($role, $context);
  573. }
  574. $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename;
  575. if ($withlinks) {
  576. $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>0, 'returnurl'=>$this->page->url->out_as_local_url(false)));
  577. $loggedinas .= ' ('.html_writer::tag('a', get_string('switchrolereturn'), array('href' => $url)).')';
  578. }
  579. } else {
  580. $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username);
  581. if ($withlinks) {
  582. $loggedinas .= " (<a href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)';
  583. }
  584. }
  585. } else {
  586. $loggedinas = get_string('loggedinnot', 'moodle');
  587. if (!$loginpage && $withlinks) {
  588. $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
  589. }
  590. }
  591. $loggedinas = '<div class="logininfo">'.$loggedinas.'</div>';
  592. if (isset($SESSION->justloggedin)) {
  593. unset($SESSION->justloggedin);
  594. if (!empty($CFG->displayloginfailures)) {
  595. if (!isguestuser()) {
  596. if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) {
  597. $loggedinas .= '&nbsp;<div class="loginfailures">';
  598. if (empty($count->accounts)) {
  599. $loggedinas .= get_string('failedloginattempts', '', $count);
  600. } else {
  601. $loggedinas .= get_string('failedloginattemptsall', '', $count);
  602. }
  603. if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
  604. $loggedinas .= ' (<a href="'.$CFG->wwwroot.'/report/log/index.php'.
  605. '?chooselog=1&amp;id=1&amp;modid=site_errors">'.get_string('logs').'</a>)';
  606. }
  607. $loggedinas .= '</div>';
  608. }
  609. }
  610. }
  611. }
  612. return $loggedinas;
  613. }
  614. /**
  615. * Return the 'back' link that normally appears in the footer.
  616. *
  617. * @return string HTML fragment.
  618. */
  619. public function home_link() {
  620. global $CFG, $SITE;
  621. if ($this->page->pagetype == 'site-index') {
  622. // Special case for site home page - please do not remove
  623. return '<div class="sitelink">' .
  624. '<a title="Moodle" href="http://moodle.org/">' .
  625. '<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
  626. } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
  627. // Special case for during install/upgrade.
  628. return '<div class="sitelink">'.
  629. '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
  630. '<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
  631. } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
  632. return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
  633. get_string('home') . '</a></div>';
  634. } else {
  635. return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
  636. format_string($this->page->course->shortname, true, array('context' => $this->page->context)) . '</a></div>';
  637. }
  638. }
  639. /**
  640. * Redirects the user by any means possible given the current state
  641. *
  642. * This function should not be called directly, it should always be called using
  643. * the redirect function in lib/weblib.php
  644. *
  645. * The redirect function should really only be called before page output has started
  646. * however it will allow itself to be called during the state STATE_IN_BODY
  647. *
  648. * @param string $encodedurl The URL to send to encoded if required
  649. * @param string $message The message to display to the user if any
  650. * @param int $delay The delay before redirecting a user, if $message has been
  651. * set this is a requirement and defaults to 3, set to 0 no delay
  652. * @param boolean $debugdisableredirect this redirect has been disabled for
  653. * debugging purposes. Display a message that explains, and don't
  654. * trigger the redirect.
  655. * @return string The HTML to display to the user before dying, may contain
  656. * meta refresh, javascript refresh, and may have set header redirects
  657. */
  658. public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
  659. global $CFG;
  660. $url = str_replace('&amp;', '&', $encodedurl);
  661. switch ($this->page->state) {
  662. case moodle_page::STATE_BEFORE_HEADER :
  663. // No output yet it is safe to delivery the full arsenal of redirect methods
  664. if (!$debugdisableredirect) {
  665. // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
  666. $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
  667. $this->page->requires->js_function_call('document.location.replace', array($url), false, ($delay + 3));
  668. }
  669. $output = $this->header();
  670. break;
  671. case moodle_page::STATE_PRINTING_HEADER :
  672. // We should hopefully never get here
  673. throw new coding_exception('You cannot redirect while printing the page header');
  674. break;
  675. case moodle_page::STATE_IN_BODY :
  676. // We really shouldn't be here but we can deal with this
  677. debugging("You should really redirect before you start page output");
  678. if (!$debugdisableredirect) {
  679. $this->page->requires->js_function_call('document.location.replace', array($url), false, $delay);
  680. }
  681. $output = $this->opencontainers->pop_all_but_last();
  682. break;
  683. case moodle_page::STATE_DONE :
  684. // Too late to be calling redirect now
  685. throw new coding_exception('You cannot redirect after the entire page has been generated');
  686. break;
  687. }
  688. $output .= $this->notification($message, 'redirectmessage');
  689. $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>';
  690. if ($debugdisableredirect) {
  691. $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
  692. }
  693. $output .= $this->footer();
  694. return $output;
  695. }
  696. /**
  697. * Start output by sending the HTTP headers, and printing the HTML <head>
  698. * and the start of the <body>.
  699. *
  700. * To control what is printed, you should set properties on $PAGE. If you
  701. * are familiar with the old {@link print_header()} function from Moodle 1.9
  702. * you will find that there are properties on $PAGE that correspond to most
  703. * of the old parameters to could be passed to print_header.
  704. *
  705. * Not that, in due course, the remaining $navigation, $menu parameters here
  706. * will be replaced by more properties of $PAGE, but that is still to do.
  707. *
  708. * @return string HTML that you must output this, preferably immediately.
  709. */
  710. public function header() {
  711. global $USER, $CFG;
  712. if (\core\session\manager::is_loggedinas()) {
  713. $this->page->add_body_class('userloggedinas');
  714. }
  715. // Give themes a chance to init/alter the page object.
  716. $this->page->theme->init_page($this->page);
  717. $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
  718. // Find the appropriate page layout file, based on $this->page->pagelayout.
  719. $layoutfile = $this->page->theme->layout_file($this->page->pagelayout);
  720. // Render the layout using the layout file.
  721. $rendered = $this->render_page_layout($layoutfile);
  722. // Slice the rendered output into header and footer.
  723. $cutpos = strpos($rendered, $this->unique_main_content_token);
  724. if ($cutpos === false) {
  725. $cutpos = strpos($rendered, self::MAIN_CONTENT_TOKEN);
  726. $token = self::MAIN_CONTENT_TOKEN;
  727. } else {
  728. $token = $this->unique_main_content_token;
  729. }
  730. if ($cutpos === false) {
  731. throw new coding_exception('page layout file ' . $layoutfile . ' does not contain the main content placeholder, please include "<?php echo $OUTPUT->main_content() ?>" in theme layout file.');
  732. }
  733. $header = substr($rendered, 0, $cutpos);
  734. $footer = substr($rendered, $cutpos + strlen($token));
  735. if (empty($this->contenttype)) {
  736. debugging('The page layout file did not call $OUTPUT->doctype()');
  737. $header = $this->doctype() . $header;
  738. }
  739. // If this theme version is below 2.4 release and this is a course view page
  740. if ((!isset($this->page->theme->settings->version) || $this->page->theme->settings->version < 2012101500) &&
  741. $this->page->pagelayout === 'course' && $this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
  742. // check if course content header/footer have not been output during render of theme layout
  743. $coursecontentheader = $this->course_content_header(true);
  744. $coursecontentfooter = $this->course_content_footer(true);
  745. if (!empty($coursecontentheader)) {
  746. // display debug message and add header and footer right above and below main content
  747. // Please note that course header and footer (to be displayed above and below the whole page)
  748. // are not displayed in this case at all.
  749. // Besides the content header and footer are not displayed on any other course page
  750. debugging('The current theme is not optimised for 2.4, the course-specific header and footer defined in course format will not be output', DEBUG_DEVELOPER);
  751. $header .= $coursecontentheader;
  752. $footer = $coursecontentfooter. $footer;
  753. }
  754. }
  755. send_headers($this->contenttype, $this->page->cacheable);
  756. $this->opencontainers->push('header/footer', $footer);
  757. $this->page->set_state(moodle_page::STATE_IN_BODY);
  758. return $header . $this->skip_link_target('maincontent');
  759. }
  760. /**
  761. * Renders and outputs the page layout file.
  762. *
  763. * This is done by preparing the normal globals available to a script, and
  764. * then including the layout file provided by the current theme for the
  765. * requested layout.
  766. *
  767. * @param string $layoutfile The name of the layout file
  768. * @return string HTML code
  769. */
  770. protected function render_page_layout($layoutfile) {
  771. global $CFG, $SITE, $USER;
  772. // The next lines are a bit tricky. The point is, here we are in a method
  773. // of a renderer class, and this object may, or may not, be the same as
  774. // the global $OUTPUT object. When rendering the page layout file, we want to use
  775. // this object. However, people writing Moodle code expect the current
  776. // renderer to be called $OUTPUT, not $this, so define a variable called
  777. // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
  778. $OUTPUT = $this;
  779. $PAGE = $this->page;
  780. $COURSE = $this->page->course;
  781. ob_start();
  782. include($layoutfile);
  783. $rendered = ob_get_contents();
  784. ob_end_clean();
  785. return $rendered;
  786. }
  787. /**
  788. * Outputs the page's footer
  789. *
  790. * @return string HTML fragment
  791. */
  792. public function footer() {
  793. global $CFG, $DB;
  794. $output = $this->container_end_all(true);
  795. $footer = $this->opencontainers->pop('header/footer');
  796. if (debugging() and $DB and $DB->is_transaction_started()) {
  797. // TODO: MDL-20625 print warning - transaction will be rolled back
  798. }
  799. // Provide some performance info if required
  800. $performanceinfo = '';
  801. if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
  802. $perf = get_performance_info();
  803. if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
  804. $performanceinfo = $perf['html'];
  805. }
  806. }
  807. // We always want performance data when running a performance test, even if the user is redirected to another page.
  808. if (MDL_PERF_TEST && strpos($footer, $this->unique_performance_info_token) === false) {
  809. $footer = $this->unique_performance_info_token . $footer;
  810. }
  811. $footer = str_replace($this->unique_performance_info_token, $performanceinfo, $footer);
  812. $footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer);
  813. $this->page->set_state(moodle_page::STATE_DONE);
  814. return $output . $footer;
  815. }
  816. /**
  817. * Close all but the last open container. This is useful in places like error
  818. * handling, where you want to close all the open containers (apart from <body>)
  819. * before outputting the error message.
  820. *
  821. * @param bool $shouldbenone assert that the stack should be empty now - causes a
  822. * developer debug warning if it isn't.
  823. * @return string the HTML required to close any open containers inside <body>.
  824. */
  825. public function container_end_all($shouldbenone = false) {
  826. return $this->opencontainers->pop_all_but_last($shouldbenone);
  827. }
  828. /**
  829. * Returns course-specific information to be output immediately above content on any course page
  830. * (for the current course)
  831. *
  832. * @param bool $onlyifnotcalledbefore output content only if it has not been output before
  833. * @return string
  834. */
  835. public function course_content_header($onlyifnotcalledbefore = false) {
  836. global $CFG;
  837. if ($this->page->course->id == SITEID) {
  838. // return immediately and do not include /course/lib.php if not necessary
  839. return '';
  840. }
  841. static $functioncalled = false;
  842. if ($functioncalled && $onlyifnotcalledbefore) {
  843. // we have already output the content header
  844. return '';
  845. }
  846. require_once($CFG->dirroot.'/course/lib.php');
  847. $functioncalled = true;
  848. $courseformat = course_get_format($this->page->course);
  849. if (($obj = $courseformat->course_content_header()) !== null) {
  850. return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
  851. }
  852. return '';
  853. }
  854. /**
  855. * Returns course-specific information to be output immediately below content on any course page
  856. * (for the current course)
  857. *
  858. * @param bool $onlyifnotcalledbefore output content only if it has not been output before
  859. * @return string
  860. */
  861. public function course_content_footer($onlyifnotcalledbefore = false) {
  862. global $CFG;
  863. if ($this->page->course->id == SITEID) {
  864. // return immediately and do not include /course/lib.php if not necessary
  865. return '';
  866. }
  867. static $functioncalled = false;
  868. if ($functioncalled && $onlyifnotcalledbefore) {
  869. // we have already output the content footer
  870. return '';
  871. }
  872. $functioncalled = true;
  873. require_once($CFG->dirroot.'/course/lib.php');
  874. $courseformat = course_get_format($this->page->course);
  875. if (($obj = $courseformat->course_content_footer()) !== null) {
  876. return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-footer');
  877. }
  878. return '';
  879. }
  880. /**
  881. * Returns course-specific information to be output on any course page in the header area
  882. * (for the current course)
  883. *
  884. * @return string
  885. */
  886. public function course_header() {
  887. global $CFG;
  888. if ($this->page->course->id == SITEID) {
  889. // return immediately and do not include /course/lib.php if not necessary
  890. return '';
  891. }
  892. require_once($CFG->dirroot.'/course/lib.php');
  893. $courseformat = course_get_format($this->page->course);
  894. if (($obj = $courseformat->course_header()) !== null) {
  895. return $courseformat->get_renderer($this->page)->render($obj);
  896. }
  897. return '';
  898. }
  899. /**
  900. * Returns course-specific information to be output on any course page in the footer area
  901. * (for the current course)
  902. *
  903. * @return string
  904. */
  905. public function course_footer() {
  906. global $CFG;
  907. if ($this->page->course->id == SITEID) {
  908. // return immediately and do not include /course/lib.php if not necessary
  909. return '';
  910. }
  911. require_once($CFG->dirroot.'/course/lib.php');
  912. $courseformat = course_get_format($this->page->course);
  913. if (($obj = $courseformat->course_footer()) !== null) {
  914. return $courseformat->get_renderer($this->page)->render($obj);
  915. }
  916. return '';
  917. }
  918. /**
  919. * Returns lang menu or '', this method also checks forcing of languages in courses.
  920. *
  921. * This function calls {@link core_renderer::render_single_select()} to actually display the language menu.
  922. *
  923. * @return string The lang menu HTML or empty string
  924. */
  925. public function lang_menu() {
  926. global $CFG;
  927. if (empty($CFG->langmenu)) {
  928. return '';
  929. }
  930. if ($this->page->course != SITEID and !empty($this->page->course->lang)) {
  931. // do not show lang menu if language forced
  932. return '';
  933. }
  934. $currlang = current_language();
  935. $langs = get_string_manager()->get_list_of_translations();
  936. if (count($langs) < 2) {
  937. return '';
  938. }
  939. $s = new single_select($this->page->url, 'lang', $langs, $currlang, null);
  940. $s->label = get_accesshide(get_string('language'));
  941. $s->class = 'langmenu';
  942. return $this->render($s);
  943. }
  944. /**
  945. * Output the row of editing icons for a block, as defined by the controls array.
  946. *
  947. * @param array $controls an array like {@link block_contents::$controls}.
  948. * @param string $blockid The ID given to the block.
  949. * @return string HTML fragment.
  950. */
  951. public function block_controls($actions, $blockid = null) {
  952. global $CFG;
  953. if (empty($actions)) {
  954. return '';
  955. }
  956. $menu = new action_menu($actions);
  957. if ($blockid !== null) {
  958. $menu->set_owner_selector('#'.$blockid);
  959. }
  960. $menu->set_constraint('.block-region');
  961. $menu->attributes['class'] .= ' block-control-actions commands';
  962. if (isset($CFG->blockeditingmenu) && !$CFG->blockeditingmenu) {
  963. $menu->do_not_enhance();
  964. }
  965. return $this->render($menu);
  966. }
  967. /**
  968. * Renders an action menu component.
  969. *
  970. * ARIA references:
  971. * - http://www.w3.org/WAI/GL/wiki/Using_ARIA_menus
  972. * - http://stackoverflow.com/questions/12279113/recommended-wai-aria-implementation-for-navigation-bar-menu
  973. *
  974. * @param action_menu $menu
  975. * @return string HTML
  976. */
  977. public function render_action_menu(action_menu $menu) {
  978. $menu->initialise_js($this->page);
  979. $output = html_writer::start_tag('div', $menu->attributes);
  980. $output .= html_writer::start_tag('ul', $menu->attributesprimary);
  981. foreach ($menu->get_primary_actions($this) as $action) {
  982. if ($action instanceof renderable) {
  983. $content = $this->render($action);
  984. } else {
  985. $content = $action;
  986. }
  987. $output .= html_writer::tag('li', $content, array('role' => 'presentation'));
  988. }
  989. $output .= html_writer::end_tag('ul');
  990. $output .= html_writer::start_tag('ul', $menu->attributessecondary);
  991. foreach ($menu->get_secondary_actions() as $action) {
  992. if ($action instanceof renderable) {
  993. $content = $this->render($action);
  994. } else {
  995. $content = $action;
  996. }
  997. $output .= html_writer::tag('li', $content, array('role' => 'presentation'));
  998. }
  999. $output .= html_writer::end_tag('ul');
  1000. $output .= html_writer::end_tag('div');
  1001. return $output;
  1002. }
  1003. /**
  1004. * Renders an action_menu_link item.
  1005. *
  1006. * @param action_menu_link $action
  1007. * @return string HTML fragment
  1008. */
  1009. protected function render_action_menu_link(action_menu_link $action) {
  1010. static $actioncount = 0;
  1011. $actioncount++;
  1012. $comparetoalt = '';
  1013. $text = '';
  1014. if (!$action->icon || $action->primary === false) {
  1015. $text .= html_writer::start_tag('span', array('class'=>'menu-action-text', 'id' => 'actionmenuaction-'.$actioncount));
  1016. if ($action->text instanceof renderable) {
  1017. $text .= $this->render($action->text);
  1018. } else {
  1019. $text .= $action->text;
  1020. $comparetoalt = (string)$action->text;
  1021. }
  1022. $text .= html_writer::end_tag('span');
  1023. }
  1024. $icon = '';
  1025. if ($action->icon) {
  1026. $icon = $action->icon;
  1027. if ($action->primary || !$action->actionmenu->will_be_enhanced()) {
  1028. $action->attributes['title'] = $action->text;
  1029. }
  1030. if (!$action->primary && $action->actionmenu->will_be_enhanced()) {
  1031. if ((string)$icon->attributes['alt'] === $comparetoalt) {
  1032. $icon->attributes['alt'] = '';
  1033. }
  1034. if (isset($icon->attributes['title']) && (string)$icon->attributes['title'] === $comparetoalt) {
  1035. unset($icon->attributes['title']);
  1036. }
  1037. }
  1038. $icon = $this->render($icon);
  1039. }
  1040. // A disabled link is rendered as formatted text.
  1041. if (!empty($action->attributes['disabled'])) {
  1042. // Do not use div here due to nesting restriction in xhtml strict.
  1043. return html_writer::tag('span', $icon.$text, array('class'=>'currentlink', 'role' => 'menuitem'));
  1044. }
  1045. $attributes = $action->attributes;
  1046. unset($action->attributes['disabled']);
  1047. $attributes['href'] = $action->url;
  1048. if ($text !== '') {
  1049. $attributes['aria-labelledby'] = 'actionmenuaction-'.$actioncount;
  1050. }
  1051. return html_writer::tag('a', $icon.$text, $attributes);
  1052. }
  1053. /**
  1054. * Renders a primary action_menu_filler item.
  1055. *
  1056. * @param action_menu_link_filler $action
  1057. * @return string HTML fragment
  1058. */
  1059. protected function render_action_menu_filler(action_menu_filler $action) {
  1060. return html_writer::span('&nbsp;', 'filler');
  1061. }
  1062. /**
  1063. * Renders a primary action_menu_link item.
  1064. *
  1065. * @param action_menu_link_primary $action
  1066. * @return string HTML fragment
  1067. */
  1068. protected function render_action_menu_link_primary(action_menu_link_primary $action) {
  1069. return $this->render_action_menu_link($action);
  1070. }
  1071. /**
  1072. * Renders a secondary action_menu_link item.
  1073. *
  1074. * @param action_menu_link_secondary $action
  1075. * @return string HTML fragment
  1076. */
  1077. protected function render_action_menu_link_secondary(action_menu_link_secondary $action) {
  1078. return $this->render_action_menu_link($action);
  1079. }
  1080. /**
  1081. * Prints a nice side block with an optional header.
  1082. *
  1083. * The content is described
  1084. * by a {@link cor…

Large files files are truncated, but you can click here to view the full file