PageRenderTime 27ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/outputrenderers.php

http://github.com/moodle/moodle
PHP | 5233 lines | 2800 code | 543 blank | 1890 comment | 574 complexity | c161263a7273691547007b21380ca2c9 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause

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. * @var Mustache_Engine $mustache The mustache template compiler
  65. */
  66. private $mustache;
  67. /**
  68. * Return an instance of the mustache class.
  69. *
  70. * @since 2.9
  71. * @return Mustache_Engine
  72. */
  73. protected function get_mustache() {
  74. global $CFG;
  75. if ($this->mustache === null) {
  76. require_once("{$CFG->libdir}/filelib.php");
  77. $themename = $this->page->theme->name;
  78. $themerev = theme_get_revision();
  79. // Create new localcache directory.
  80. $cachedir = make_localcache_directory("mustache/$themerev/$themename");
  81. // Remove old localcache directories.
  82. $mustachecachedirs = glob("{$CFG->localcachedir}/mustache/*", GLOB_ONLYDIR);
  83. foreach ($mustachecachedirs as $localcachedir) {
  84. $cachedrev = [];
  85. preg_match("/\/mustache\/([0-9]+)$/", $localcachedir, $cachedrev);
  86. $cachedrev = isset($cachedrev[1]) ? intval($cachedrev[1]) : 0;
  87. if ($cachedrev > 0 && $cachedrev < $themerev) {
  88. fulldelete($localcachedir);
  89. }
  90. }
  91. $loader = new \core\output\mustache_filesystem_loader();
  92. $stringhelper = new \core\output\mustache_string_helper();
  93. $quotehelper = new \core\output\mustache_quote_helper();
  94. $jshelper = new \core\output\mustache_javascript_helper($this->page);
  95. $pixhelper = new \core\output\mustache_pix_helper($this);
  96. $shortentexthelper = new \core\output\mustache_shorten_text_helper();
  97. $userdatehelper = new \core\output\mustache_user_date_helper();
  98. // We only expose the variables that are exposed to JS templates.
  99. $safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this);
  100. $helpers = array('config' => $safeconfig,
  101. 'str' => array($stringhelper, 'str'),
  102. 'quote' => array($quotehelper, 'quote'),
  103. 'js' => array($jshelper, 'help'),
  104. 'pix' => array($pixhelper, 'pix'),
  105. 'shortentext' => array($shortentexthelper, 'shorten'),
  106. 'userdate' => array($userdatehelper, 'transform'),
  107. );
  108. $this->mustache = new \core\output\mustache_engine(array(
  109. 'cache' => $cachedir,
  110. 'escape' => 's',
  111. 'loader' => $loader,
  112. 'helpers' => $helpers,
  113. 'pragmas' => [Mustache_Engine::PRAGMA_BLOCKS],
  114. // Don't allow the JavaScript helper to be executed from within another
  115. // helper. If it's allowed it can be used by users to inject malicious
  116. // JS into the page.
  117. 'blacklistednestedhelpers' => ['js']));
  118. }
  119. return $this->mustache;
  120. }
  121. /**
  122. * Constructor
  123. *
  124. * The constructor takes two arguments. The first is the page that the renderer
  125. * has been created to assist with, and the second is the target.
  126. * The target is an additional identifier that can be used to load different
  127. * renderers for different options.
  128. *
  129. * @param moodle_page $page the page we are doing output for.
  130. * @param string $target one of rendering target constants
  131. */
  132. public function __construct(moodle_page $page, $target) {
  133. $this->opencontainers = $page->opencontainers;
  134. $this->page = $page;
  135. $this->target = $target;
  136. }
  137. /**
  138. * Renders a template by name with the given context.
  139. *
  140. * The provided data needs to be array/stdClass made up of only simple types.
  141. * Simple types are array,stdClass,bool,int,float,string
  142. *
  143. * @since 2.9
  144. * @param array|stdClass $context Context containing data for the template.
  145. * @return string|boolean
  146. */
  147. public function render_from_template($templatename, $context) {
  148. static $templatecache = array();
  149. $mustache = $this->get_mustache();
  150. try {
  151. // Grab a copy of the existing helper to be restored later.
  152. $uniqidhelper = $mustache->getHelper('uniqid');
  153. } catch (Mustache_Exception_UnknownHelperException $e) {
  154. // Helper doesn't exist.
  155. $uniqidhelper = null;
  156. }
  157. // Provide 1 random value that will not change within a template
  158. // but will be different from template to template. This is useful for
  159. // e.g. aria attributes that only work with id attributes and must be
  160. // unique in a page.
  161. $mustache->addHelper('uniqid', new \core\output\mustache_uniqid_helper());
  162. if (isset($templatecache[$templatename])) {
  163. $template = $templatecache[$templatename];
  164. } else {
  165. try {
  166. $template = $mustache->loadTemplate($templatename);
  167. $templatecache[$templatename] = $template;
  168. } catch (Mustache_Exception_UnknownTemplateException $e) {
  169. throw new moodle_exception('Unknown template: ' . $templatename);
  170. }
  171. }
  172. $renderedtemplate = trim($template->render($context));
  173. // If we had an existing uniqid helper then we need to restore it to allow
  174. // handle nested calls of render_from_template.
  175. if ($uniqidhelper) {
  176. $mustache->addHelper('uniqid', $uniqidhelper);
  177. }
  178. return $renderedtemplate;
  179. }
  180. /**
  181. * Returns rendered widget.
  182. *
  183. * The provided widget needs to be an object that extends the renderable
  184. * interface.
  185. * If will then be rendered by a method based upon the classname for the widget.
  186. * For instance a widget of class `crazywidget` will be rendered by a protected
  187. * render_crazywidget method of this renderer.
  188. * If no render_crazywidget method exists and crazywidget implements templatable,
  189. * look for the 'crazywidget' template in the same component and render that.
  190. *
  191. * @param renderable $widget instance with renderable interface
  192. * @return string
  193. */
  194. public function render(renderable $widget) {
  195. $classparts = explode('\\', get_class($widget));
  196. // Strip namespaces.
  197. $classname = array_pop($classparts);
  198. // Remove _renderable suffixes
  199. $classname = preg_replace('/_renderable$/', '', $classname);
  200. $rendermethod = 'render_'.$classname;
  201. if (method_exists($this, $rendermethod)) {
  202. return $this->$rendermethod($widget);
  203. }
  204. if ($widget instanceof templatable) {
  205. $component = array_shift($classparts);
  206. if (!$component) {
  207. $component = 'core';
  208. }
  209. $template = $component . '/' . $classname;
  210. $context = $widget->export_for_template($this);
  211. return $this->render_from_template($template, $context);
  212. }
  213. throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.');
  214. }
  215. /**
  216. * Adds a JS action for the element with the provided id.
  217. *
  218. * This method adds a JS event for the provided component action to the page
  219. * and then returns the id that the event has been attached to.
  220. * If no id has been provided then a new ID is generated by {@link html_writer::random_id()}
  221. *
  222. * @param component_action $action
  223. * @param string $id
  224. * @return string id of element, either original submitted or random new if not supplied
  225. */
  226. public function add_action_handler(component_action $action, $id = null) {
  227. if (!$id) {
  228. $id = html_writer::random_id($action->event);
  229. }
  230. $this->page->requires->event_handler("#$id", $action->event, $action->jsfunction, $action->jsfunctionargs);
  231. return $id;
  232. }
  233. /**
  234. * Returns true is output has already started, and false if not.
  235. *
  236. * @return boolean true if the header has been printed.
  237. */
  238. public function has_started() {
  239. return $this->page->state >= moodle_page::STATE_IN_BODY;
  240. }
  241. /**
  242. * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
  243. *
  244. * @param mixed $classes Space-separated string or array of classes
  245. * @return string HTML class attribute value
  246. */
  247. public static function prepare_classes($classes) {
  248. if (is_array($classes)) {
  249. return implode(' ', array_unique($classes));
  250. }
  251. return $classes;
  252. }
  253. /**
  254. * Return the direct URL for an image from the pix folder.
  255. *
  256. * Use this function sparingly and never for icons. For icons use pix_icon or the pix helper in a mustache template.
  257. *
  258. * @deprecated since Moodle 3.3
  259. * @param string $imagename the name of the icon.
  260. * @param string $component specification of one plugin like in get_string()
  261. * @return moodle_url
  262. */
  263. public function pix_url($imagename, $component = 'moodle') {
  264. debugging('pix_url is deprecated. Use image_url for images and pix_icon for icons.', DEBUG_DEVELOPER);
  265. return $this->page->theme->image_url($imagename, $component);
  266. }
  267. /**
  268. * Return the moodle_url for an image.
  269. *
  270. * The exact image location and extension is determined
  271. * automatically by searching for gif|png|jpg|jpeg, please
  272. * note there can not be diferent images with the different
  273. * extension. The imagename is for historical reasons
  274. * a relative path name, it may be changed later for core
  275. * images. It is recommended to not use subdirectories
  276. * in plugin and theme pix directories.
  277. *
  278. * There are three types of images:
  279. * 1/ theme images - stored in theme/mytheme/pix/,
  280. * use component 'theme'
  281. * 2/ core images - stored in /pix/,
  282. * overridden via theme/mytheme/pix_core/
  283. * 3/ plugin images - stored in mod/mymodule/pix,
  284. * overridden via theme/mytheme/pix_plugins/mod/mymodule/,
  285. * example: image_url('comment', 'mod_glossary')
  286. *
  287. * @param string $imagename the pathname of the image
  288. * @param string $component full plugin name (aka component) or 'theme'
  289. * @return moodle_url
  290. */
  291. public function image_url($imagename, $component = 'moodle') {
  292. return $this->page->theme->image_url($imagename, $component);
  293. }
  294. /**
  295. * Return the site's logo URL, if any.
  296. *
  297. * @param int $maxwidth The maximum width, or null when the maximum width does not matter.
  298. * @param int $maxheight The maximum height, or null when the maximum height does not matter.
  299. * @return moodle_url|false
  300. */
  301. public function get_logo_url($maxwidth = null, $maxheight = 200) {
  302. global $CFG;
  303. $logo = get_config('core_admin', 'logo');
  304. if (empty($logo)) {
  305. return false;
  306. }
  307. // 200px high is the default image size which should be displayed at 100px in the page to account for retina displays.
  308. // It's not worth the overhead of detecting and serving 2 different images based on the device.
  309. // Hide the requested size in the file path.
  310. $filepath = ((int) $maxwidth . 'x' . (int) $maxheight) . '/';
  311. // Use $CFG->themerev to prevent browser caching when the file changes.
  312. return moodle_url::make_pluginfile_url(context_system::instance()->id, 'core_admin', 'logo', $filepath,
  313. theme_get_revision(), $logo);
  314. }
  315. /**
  316. * Return the site's compact logo URL, if any.
  317. *
  318. * @param int $maxwidth The maximum width, or null when the maximum width does not matter.
  319. * @param int $maxheight The maximum height, or null when the maximum height does not matter.
  320. * @return moodle_url|false
  321. */
  322. public function get_compact_logo_url($maxwidth = 100, $maxheight = 100) {
  323. global $CFG;
  324. $logo = get_config('core_admin', 'logocompact');
  325. if (empty($logo)) {
  326. return false;
  327. }
  328. // Hide the requested size in the file path.
  329. $filepath = ((int) $maxwidth . 'x' . (int) $maxheight) . '/';
  330. // Use $CFG->themerev to prevent browser caching when the file changes.
  331. return moodle_url::make_pluginfile_url(context_system::instance()->id, 'core_admin', 'logocompact', $filepath,
  332. theme_get_revision(), $logo);
  333. }
  334. /**
  335. * Whether we should display the logo in the navbar.
  336. *
  337. * We will when there are no main logos, and we have compact logo.
  338. *
  339. * @return bool
  340. */
  341. public function should_display_navbar_logo() {
  342. $logo = $this->get_compact_logo_url();
  343. return !empty($logo) && !$this->should_display_main_logo();
  344. }
  345. /**
  346. * Whether we should display the main logo.
  347. *
  348. * @param int $headinglevel The heading level we want to check against.
  349. * @return bool
  350. */
  351. public function should_display_main_logo($headinglevel = 1) {
  352. // Only render the logo if we're on the front page or login page and the we have a logo.
  353. $logo = $this->get_logo_url();
  354. if ($headinglevel == 1 && !empty($logo)) {
  355. if ($this->page->pagelayout == 'frontpage' || $this->page->pagelayout == 'login') {
  356. return true;
  357. }
  358. }
  359. return false;
  360. }
  361. }
  362. /**
  363. * Basis for all plugin renderers.
  364. *
  365. * @copyright Petr Skoda (skodak)
  366. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  367. * @since Moodle 2.0
  368. * @package core
  369. * @category output
  370. */
  371. class plugin_renderer_base extends renderer_base {
  372. /**
  373. * @var renderer_base|core_renderer A reference to the current renderer.
  374. * The renderer provided here will be determined by the page but will in 90%
  375. * of cases by the {@link core_renderer}
  376. */
  377. protected $output;
  378. /**
  379. * Constructor method, calls the parent constructor
  380. *
  381. * @param moodle_page $page
  382. * @param string $target one of rendering target constants
  383. */
  384. public function __construct(moodle_page $page, $target) {
  385. if (empty($target) && $page->pagelayout === 'maintenance') {
  386. // If the page is using the maintenance layout then we're going to force the target to maintenance.
  387. // This way we'll get a special maintenance renderer that is designed to block access to API's that are likely
  388. // unavailable for this page layout.
  389. $target = RENDERER_TARGET_MAINTENANCE;
  390. }
  391. $this->output = $page->get_renderer('core', null, $target);
  392. parent::__construct($page, $target);
  393. }
  394. /**
  395. * Renders the provided widget and returns the HTML to display it.
  396. *
  397. * @param renderable $widget instance with renderable interface
  398. * @return string
  399. */
  400. public function render(renderable $widget) {
  401. $classname = get_class($widget);
  402. // Strip namespaces.
  403. $classname = preg_replace('/^.*\\\/', '', $classname);
  404. // Keep a copy at this point, we may need to look for a deprecated method.
  405. $deprecatedmethod = 'render_'.$classname;
  406. // Remove _renderable suffixes
  407. $classname = preg_replace('/_renderable$/', '', $classname);
  408. $rendermethod = 'render_'.$classname;
  409. if (method_exists($this, $rendermethod)) {
  410. return $this->$rendermethod($widget);
  411. }
  412. if ($rendermethod !== $deprecatedmethod && method_exists($this, $deprecatedmethod)) {
  413. // This is exactly where we don't want to be.
  414. // If you have arrived here you have a renderable component within your plugin that has the name
  415. // blah_renderable, and you have a render method render_blah_renderable on your plugin.
  416. // In 2.8 we revamped output, as part of this change we changed slightly how renderables got rendered
  417. // and the _renderable suffix now gets removed when looking for a render method.
  418. // You need to change your renderers render_blah_renderable to render_blah.
  419. // Until you do this it will not be possible for a theme to override the renderer to override your method.
  420. // Please do it ASAP.
  421. static $debugged = array();
  422. if (!isset($debugged[$deprecatedmethod])) {
  423. debugging(sprintf('Deprecated call. Please rename your renderables render method from %s to %s.',
  424. $deprecatedmethod, $rendermethod), DEBUG_DEVELOPER);
  425. $debugged[$deprecatedmethod] = true;
  426. }
  427. return $this->$deprecatedmethod($widget);
  428. }
  429. // pass to core renderer if method not found here
  430. return $this->output->render($widget);
  431. }
  432. /**
  433. * Magic method used to pass calls otherwise meant for the standard renderer
  434. * to it to ensure we don't go causing unnecessary grief.
  435. *
  436. * @param string $method
  437. * @param array $arguments
  438. * @return mixed
  439. */
  440. public function __call($method, $arguments) {
  441. if (method_exists('renderer_base', $method)) {
  442. throw new coding_exception('Protected method called against '.get_class($this).' :: '.$method);
  443. }
  444. if (method_exists($this->output, $method)) {
  445. return call_user_func_array(array($this->output, $method), $arguments);
  446. } else {
  447. throw new coding_exception('Unknown method called against '.get_class($this).' :: '.$method);
  448. }
  449. }
  450. }
  451. /**
  452. * The standard implementation of the core_renderer interface.
  453. *
  454. * @copyright 2009 Tim Hunt
  455. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  456. * @since Moodle 2.0
  457. * @package core
  458. * @category output
  459. */
  460. class core_renderer extends renderer_base {
  461. /**
  462. * Do NOT use, please use <?php echo $OUTPUT->main_content() ?>
  463. * in layout files instead.
  464. * @deprecated
  465. * @var string used in {@link core_renderer::header()}.
  466. */
  467. const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
  468. /**
  469. * @var string Used to pass information from {@link core_renderer::doctype()} to
  470. * {@link core_renderer::standard_head_html()}.
  471. */
  472. protected $contenttype;
  473. /**
  474. * @var string Used by {@link core_renderer::redirect_message()} method to communicate
  475. * with {@link core_renderer::header()}.
  476. */
  477. protected $metarefreshtag = '';
  478. /**
  479. * @var string Unique token for the closing HTML
  480. */
  481. protected $unique_end_html_token;
  482. /**
  483. * @var string Unique token for performance information
  484. */
  485. protected $unique_performance_info_token;
  486. /**
  487. * @var string Unique token for the main content.
  488. */
  489. protected $unique_main_content_token;
  490. /** @var custom_menu_item language The language menu if created */
  491. protected $language = null;
  492. /**
  493. * Constructor
  494. *
  495. * @param moodle_page $page the page we are doing output for.
  496. * @param string $target one of rendering target constants
  497. */
  498. public function __construct(moodle_page $page, $target) {
  499. $this->opencontainers = $page->opencontainers;
  500. $this->page = $page;
  501. $this->target = $target;
  502. $this->unique_end_html_token = '%%ENDHTML-'.sesskey().'%%';
  503. $this->unique_performance_info_token = '%%PERFORMANCEINFO-'.sesskey().'%%';
  504. $this->unique_main_content_token = '[MAIN CONTENT GOES HERE - '.sesskey().']';
  505. }
  506. /**
  507. * Get the DOCTYPE declaration that should be used with this page. Designed to
  508. * be called in theme layout.php files.
  509. *
  510. * @return string the DOCTYPE declaration that should be used.
  511. */
  512. public function doctype() {
  513. if ($this->page->theme->doctype === 'html5') {
  514. $this->contenttype = 'text/html; charset=utf-8';
  515. return "<!DOCTYPE html>\n";
  516. } else if ($this->page->theme->doctype === 'xhtml5') {
  517. $this->contenttype = 'application/xhtml+xml; charset=utf-8';
  518. return "<!DOCTYPE html>\n";
  519. } else {
  520. // legacy xhtml 1.0
  521. $this->contenttype = 'text/html; charset=utf-8';
  522. return ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n");
  523. }
  524. }
  525. /**
  526. * The attributes that should be added to the <html> tag. Designed to
  527. * be called in theme layout.php files.
  528. *
  529. * @return string HTML fragment.
  530. */
  531. public function htmlattributes() {
  532. $return = get_html_lang(true);
  533. $attributes = array();
  534. if ($this->page->theme->doctype !== 'html5') {
  535. $attributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
  536. }
  537. // Give plugins an opportunity to add things like xml namespaces to the html element.
  538. // This function should return an array of html attribute names => values.
  539. $pluginswithfunction = get_plugins_with_function('add_htmlattributes', 'lib.php');
  540. foreach ($pluginswithfunction as $plugins) {
  541. foreach ($plugins as $function) {
  542. $newattrs = $function();
  543. unset($newattrs['dir']);
  544. unset($newattrs['lang']);
  545. unset($newattrs['xmlns']);
  546. unset($newattrs['xml:lang']);
  547. $attributes += $newattrs;
  548. }
  549. }
  550. foreach ($attributes as $key => $val) {
  551. $val = s($val);
  552. $return .= " $key=\"$val\"";
  553. }
  554. return $return;
  555. }
  556. /**
  557. * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
  558. * that should be included in the <head> tag. Designed to be called in theme
  559. * layout.php files.
  560. *
  561. * @return string HTML fragment.
  562. */
  563. public function standard_head_html() {
  564. global $CFG, $SESSION, $SITE;
  565. // Before we output any content, we need to ensure that certain
  566. // page components are set up.
  567. // Blocks must be set up early as they may require javascript which
  568. // has to be included in the page header before output is created.
  569. foreach ($this->page->blocks->get_regions() as $region) {
  570. $this->page->blocks->ensure_content_created($region, $this);
  571. }
  572. $output = '';
  573. // Give plugins an opportunity to add any head elements. The callback
  574. // must always return a string containing valid html head content.
  575. $pluginswithfunction = get_plugins_with_function('before_standard_html_head', 'lib.php');
  576. foreach ($pluginswithfunction as $plugins) {
  577. foreach ($plugins as $function) {
  578. $output .= $function();
  579. }
  580. }
  581. // Allow a url_rewrite plugin to setup any dynamic head content.
  582. if (isset($CFG->urlrewriteclass) && !isset($CFG->upgraderunning)) {
  583. $class = $CFG->urlrewriteclass;
  584. $output .= $class::html_head_setup();
  585. }
  586. $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
  587. $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
  588. // This is only set by the {@link redirect()} method
  589. $output .= $this->metarefreshtag;
  590. // Check if a periodic refresh delay has been set and make sure we arn't
  591. // already meta refreshing
  592. if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
  593. $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
  594. }
  595. // Set up help link popups for all links with the helptooltip class
  596. $this->page->requires->js_init_call('M.util.help_popups.setup');
  597. $focus = $this->page->focuscontrol;
  598. if (!empty($focus)) {
  599. if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
  600. // This is a horrifically bad way to handle focus but it is passed in
  601. // through messy formslib::moodleform
  602. $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
  603. } else if (strpos($focus, '.')!==false) {
  604. // Old style of focus, bad way to do it
  605. 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);
  606. $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
  607. } else {
  608. // Focus element with given id
  609. $this->page->requires->js_function_call('focuscontrol', array($focus));
  610. }
  611. }
  612. // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins;
  613. // any other custom CSS can not be overridden via themes and is highly discouraged
  614. $urls = $this->page->theme->css_urls($this->page);
  615. foreach ($urls as $url) {
  616. $this->page->requires->css_theme($url);
  617. }
  618. // Get the theme javascript head and footer
  619. if ($jsurl = $this->page->theme->javascript_url(true)) {
  620. $this->page->requires->js($jsurl, true);
  621. }
  622. if ($jsurl = $this->page->theme->javascript_url(false)) {
  623. $this->page->requires->js($jsurl);
  624. }
  625. // Get any HTML from the page_requirements_manager.
  626. $output .= $this->page->requires->get_head_code($this->page, $this);
  627. // List alternate versions.
  628. foreach ($this->page->alternateversions as $type => $alt) {
  629. $output .= html_writer::empty_tag('link', array('rel' => 'alternate',
  630. 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
  631. }
  632. // Add noindex tag if relevant page and setting applied.
  633. $allowindexing = isset($CFG->allowindexing) ? $CFG->allowindexing : 0;
  634. $loginpages = array('login-index', 'login-signup');
  635. if ($allowindexing == 2 || ($allowindexing == 0 && in_array($this->page->pagetype, $loginpages))) {
  636. if (!isset($CFG->additionalhtmlhead)) {
  637. $CFG->additionalhtmlhead = '';
  638. }
  639. $CFG->additionalhtmlhead .= '<meta name="robots" content="noindex" />';
  640. }
  641. if (!empty($CFG->additionalhtmlhead)) {
  642. $output .= "\n".$CFG->additionalhtmlhead;
  643. }
  644. if ($this->page->pagelayout == 'frontpage') {
  645. $summary = s(strip_tags(format_text($SITE->summary, FORMAT_HTML)));
  646. if (!empty($summary)) {
  647. $output .= "<meta name=\"description\" content=\"$summary\" />\n";
  648. }
  649. }
  650. return $output;
  651. }
  652. /**
  653. * The standard tags (typically skip links) that should be output just inside
  654. * the start of the <body> tag. Designed to be called in theme layout.php files.
  655. *
  656. * @return string HTML fragment.
  657. */
  658. public function standard_top_of_body_html() {
  659. global $CFG;
  660. $output = $this->page->requires->get_top_of_body_code($this);
  661. if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmltopofbody)) {
  662. $output .= "\n".$CFG->additionalhtmltopofbody;
  663. }
  664. // Give subsystems an opportunity to inject extra html content. The callback
  665. // must always return a string containing valid html.
  666. foreach (\core_component::get_core_subsystems() as $name => $path) {
  667. if ($path) {
  668. $output .= component_callback($name, 'before_standard_top_of_body_html', [], '');
  669. }
  670. }
  671. // Give plugins an opportunity to inject extra html content. The callback
  672. // must always return a string containing valid html.
  673. $pluginswithfunction = get_plugins_with_function('before_standard_top_of_body_html', 'lib.php');
  674. foreach ($pluginswithfunction as $plugins) {
  675. foreach ($plugins as $function) {
  676. $output .= $function();
  677. }
  678. }
  679. $output .= $this->maintenance_warning();
  680. return $output;
  681. }
  682. /**
  683. * Scheduled maintenance warning message.
  684. *
  685. * Note: This is a nasty hack to display maintenance notice, this should be moved
  686. * to some general notification area once we have it.
  687. *
  688. * @return string
  689. */
  690. public function maintenance_warning() {
  691. global $CFG;
  692. $output = '';
  693. if (isset($CFG->maintenance_later) and $CFG->maintenance_later > time()) {
  694. $timeleft = $CFG->maintenance_later - time();
  695. // If timeleft less than 30 sec, set the class on block to error to highlight.
  696. $errorclass = ($timeleft < 30) ? 'alert-error alert-danger' : 'alert-warning';
  697. $output .= $this->box_start($errorclass . ' moodle-has-zindex maintenancewarning m-a-1 alert');
  698. $a = new stdClass();
  699. $a->hour = (int)($timeleft / 3600);
  700. $a->min = (int)(($timeleft / 60) % 60);
  701. $a->sec = (int)($timeleft % 60);
  702. if ($a->hour > 0) {
  703. $output .= get_string('maintenancemodeisscheduledlong', 'admin', $a);
  704. } else {
  705. $output .= get_string('maintenancemodeisscheduled', 'admin', $a);
  706. }
  707. $output .= $this->box_end();
  708. $this->page->requires->yui_module('moodle-core-maintenancemodetimer', 'M.core.maintenancemodetimer',
  709. array(array('timeleftinsec' => $timeleft)));
  710. $this->page->requires->strings_for_js(
  711. array('maintenancemodeisscheduled', 'maintenancemodeisscheduledlong', 'sitemaintenance'),
  712. 'admin');
  713. }
  714. return $output;
  715. }
  716. /**
  717. * The standard tags (typically performance information and validation links,
  718. * if we are in developer debug mode) that should be output in the footer area
  719. * of the page. Designed to be called in theme layout.php files.
  720. *
  721. * @return string HTML fragment.
  722. */
  723. public function standard_footer_html() {
  724. global $CFG, $SCRIPT;
  725. $output = '';
  726. if (during_initial_install()) {
  727. // Debugging info can not work before install is finished,
  728. // in any case we do not want any links during installation!
  729. return $output;
  730. }
  731. // Give plugins an opportunity to add any footer elements.
  732. // The callback must always return a string containing valid html footer content.
  733. $pluginswithfunction = get_plugins_with_function('standard_footer_html', 'lib.php');
  734. foreach ($pluginswithfunction as $plugins) {
  735. foreach ($plugins as $function) {
  736. $output .= $function();
  737. }
  738. }
  739. // This function is normally called from a layout.php file in {@link core_renderer::header()}
  740. // but some of the content won't be known until later, so we return a placeholder
  741. // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
  742. $output .= $this->unique_performance_info_token;
  743. if ($this->page->devicetypeinuse == 'legacy') {
  744. // The legacy theme is in use print the notification
  745. $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse'));
  746. }
  747. // Get links to switch device types (only shown for users not on a default device)
  748. $output .= $this->theme_switch_links();
  749. if (!empty($CFG->debugpageinfo)) {
  750. $output .= '<div class="performanceinfo pageinfo">' . get_string('pageinfodebugsummary', 'core_admin',
  751. $this->page->debug_summary()) . '</div>';
  752. }
  753. if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', context_system::instance())) { // Only in developer mode
  754. // Add link to profiling report if necessary
  755. if (function_exists('profiling_is_running') && profiling_is_running()) {
  756. $txt = get_string('profiledscript', 'admin');
  757. $title = get_string('profiledscriptview', 'admin');
  758. $url = $CFG->wwwroot . '/admin/tool/profiling/index.php?script=' . urlencode($SCRIPT);
  759. $link= '<a title="' . $title . '" href="' . $url . '">' . $txt . '</a>';
  760. $output .= '<div class="profilingfooter">' . $link . '</div>';
  761. }
  762. $purgeurl = new moodle_url('/admin/purgecaches.php', array('confirm' => 1,
  763. 'sesskey' => sesskey(), 'returnurl' => $this->page->url->out_as_local_url(false)));
  764. $output .= '<div class="purgecaches">' .
  765. html_writer::link($purgeurl, get_string('purgecaches', 'admin')) . '</div>';
  766. }
  767. if (!empty($CFG->debugvalidators)) {
  768. // NOTE: this is not a nice hack, $this->page->url is not always accurate and
  769. // $FULLME neither, it is not a bug if it fails. --skodak.
  770. $output .= '<div class="validators"><ul class="list-unstyled ml-1">
  771. <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
  772. <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
  773. <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>
  774. </ul></div>';
  775. }
  776. return $output;
  777. }
  778. /**
  779. * Returns standard main content placeholder.
  780. * Designed to be called in theme layout.php files.
  781. *
  782. * @return string HTML fragment.
  783. */
  784. public function main_content() {
  785. // This is here because it is the only place we can inject the "main" role over the entire main content area
  786. // without requiring all theme's to manually do it, and without creating yet another thing people need to
  787. // remember in the theme.
  788. // This is an unfortunate hack. DO NO EVER add anything more here.
  789. // DO NOT add classes.
  790. // DO NOT add an id.
  791. return '<div role="main">'.$this->unique_main_content_token.'</div>';
  792. }
  793. /**
  794. * Returns standard navigation between activities in a course.
  795. *
  796. * @return string the navigation HTML.
  797. */
  798. public function activity_navigation() {
  799. // First we should check if we want to add navigation.
  800. $context = $this->page->context;
  801. if (($this->page->pagelayout !== 'incourse' && $this->page->pagelayout !== 'frametop')
  802. || $context->contextlevel != CONTEXT_MODULE) {
  803. return '';
  804. }
  805. // If the activity is in stealth mode, show no links.
  806. if ($this->page->cm->is_stealth()) {
  807. return '';
  808. }
  809. // Get a list of all the activities in the course.
  810. $course = $this->page->cm->get_course();
  811. $modules = get_fast_modinfo($course->id)->get_cms();
  812. // Put the modules into an array in order by the position they are shown in the course.
  813. $mods = [];
  814. $activitylist = [];
  815. foreach ($modules as $module) {
  816. // Only add activities the user can access, aren't in stealth mode and have a url (eg. mod_label does not).
  817. if (!$module->uservisible || $module->is_stealth() || empty($module->url)) {
  818. continue;
  819. }
  820. $mods[$module->id] = $module;
  821. // No need to add the current module to the list for the activity dropdown menu.
  822. if ($module->id == $this->page->cm->id) {
  823. continue;
  824. }
  825. // Module name.
  826. $modname = $module->get_formatted_name();
  827. // Display the hidden text if necessary.
  828. if (!$module->visible) {
  829. $modname .= ' ' . get_string('hiddenwithbrackets');
  830. }
  831. // Module URL.
  832. $linkurl = new moodle_url($module->url, array('forceview' => 1));
  833. // Add module URL (as key) and name (as value) to the activity list array.
  834. $activitylist[$linkurl->out(false)] = $modname;
  835. }
  836. $nummods = count($mods);
  837. // If there is only one mod then do nothing.
  838. if ($nummods == 1) {
  839. return '';
  840. }
  841. // Get an array of just the course module ids used to get the cmid value based on their position in the course.
  842. $modids = array_keys($mods);
  843. // Get the position in the array of the course module we are viewing.
  844. $position = array_search($this->page->cm->id, $modids);
  845. $prevmod = null;
  846. $nextmod = null;
  847. // Check if we have a previous mod to show.
  848. if ($position > 0) {
  849. $prevmod = $mods[$modids[$position - 1]];
  850. }
  851. // Check if we have a next mod to show.
  852. if ($position < ($nummods - 1)) {
  853. $nextmod = $mods[$modids[$position + 1]];
  854. }
  855. $activitynav = new \core_course\output\activity_navigation($prevmod, $nextmod, $activitylist);
  856. $renderer = $this->page->get_renderer('core', 'course');
  857. return $renderer->render($activitynav);
  858. }
  859. /**
  860. * The standard tags (typically script tags that are not needed earlier) that
  861. * should be output after everything else. Designed to be called in theme layout.php files.
  862. *
  863. * @return string HTML fragment.
  864. */
  865. public function standard_end_of_body_html() {
  866. global $CFG;
  867. // This function is normally called from a layout.php file in {@link core_renderer::header()}
  868. // but some of the content won't be known until later, so we return a placeholder
  869. // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
  870. $output = '';
  871. if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlfooter)) {
  872. $output .= "\n".$CFG->additionalhtmlfooter;
  873. }
  874. $output .= $this->unique_end_html_token;
  875. return $output;
  876. }
  877. /**
  878. * The standard HTML that should be output just before the <footer> tag.
  879. * Designed to be called in theme layout.php files.
  880. *
  881. * @return string HTML fragment.
  882. */
  883. public function standard_after_main_region_html() {
  884. global $CFG;
  885. $output = '';
  886. if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlbottomofbody)) {
  887. $output .= "\n".$CFG->additionalhtmlbottomofbody;
  888. }
  889. // Give subsystems an opportunity to inject extra html content. The callback
  890. // must always return a string containing valid html.
  891. foreach (\core_component::get_core_subsystems() as $name => $path) {
  892. if ($path) {
  893. $output .= component_callback($name, 'standard_after_main_region_html', [], '');
  894. }
  895. }
  896. // Give plugins an opportunity to inject extra html content. The callback
  897. // must always return a string containing valid html.
  898. $pluginswithfunction = get_plugins_with_function('standard_after_main_region_html', 'lib.php');
  899. foreach ($pluginswithfunction as $plugins) {
  900. foreach ($plugins as $function) {
  901. $output .= $function();
  902. }
  903. }
  904. return $output;
  905. }
  906. /**
  907. * Return the standard string that says whether you are logged in (and switched
  908. * roles/logged in as another user).
  909. * @param bool $withlinks if false, then don't include any links in the HTML produced.
  910. * If not set, the default is the nologinlinks option from the theme config.php file,
  911. * and if that is not set, then links are included.
  912. * @return string HTML fragment.
  913. */
  914. public function login_info($withlinks = null) {
  915. global $USER, $CFG, $DB, $SESSION;
  916. if (during_initial_install()) {
  917. return '';
  918. }
  919. if (is_null($withlinks)) {
  920. $withlinks = empty($this->page->layout_options['nologinlinks']);
  921. }
  922. $course = $this->page->course;
  923. if (\core\session\manager::is_loggedinas()) {
  924. $realuser = \core\session\manager::get_realuser();
  925. $fullname = fullname($realuser, true);
  926. if ($withlinks) {
  927. $loginastitle = get_string('loginas');
  928. $realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&amp;sesskey=".sesskey()."\"";
  929. $realuserinfo .= "title =\"".$loginastitle."\">$fullname</a>] ";
  930. } else {
  931. $realuserinfo = " [$fullname] ";
  932. }
  933. } else {
  934. $realuserinfo = '';
  935. }
  936. $loginpage = $this->is_login_page();
  937. $loginurl = get_login_url();
  938. if (empty($course->id)) {
  939. // $course->id is not defined during installation
  940. return '';
  941. } else if (isloggedin()) {
  942. $context = context_course::instance($course->id);
  943. $fullname = fullname($USER, true);
  944. // Since Moodle 2.0 this link always goes to the public profile page (not the course profile page)
  945. if ($withlinks) {
  946. $linktitle = get_string('viewprofile');
  947. $username = "<a href=\"$CFG->wwwroot/user/profile.php?id=$USER->id\" title=\"$linktitle\">$fullname</a>";
  948. } else {
  949. $username = $fullname;
  950. }
  951. if (is_mnet_remote_user($USER) and $idprovider = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid))) {
  952. if ($withlinks) {
  953. $username .= " from <a href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>";
  954. } else {
  955. $username .= " from {$idprovider->name}";
  956. }
  957. }
  958. if (isguestuser()) {
  959. $loggedinas = $realuserinfo.get_string('loggedinasguest');
  960. if (!$loginpage && $withlinks) {
  961. $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
  962. }
  963. } else if (is_role_switched($course->id)) { // Has switched roles
  964. $rolename = '';
  965. if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
  966. $rolename = ': '.role_get_name($role, $context);
  967. }
  968. $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename;
  969. if ($withlinks) {
  970. $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>0, 'returnurl'=>$this->page->url->out_as_local_url(false)));
  971. $loggedinas .= ' ('.html_writer::tag('a', get_string('switchrolereturn'), array('href' => $url)).')';
  972. }
  973. } else {
  974. $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username);
  975. if ($withlinks) {
  976. $loggedinas .= " (<a href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)';
  977. }
  978. }
  979. } else {
  980. $loggedinas = get_string('loggedinnot', 'moodle');
  981. if (!$loginpage && $withlinks) {
  982. $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
  983. }
  984. }
  985. $loggedinas = '<div class="logininfo">'.$loggedinas.'</div>';
  986. if (isset($SESSION->justloggedin)) {
  987. unset($SESSION->justloggedin);
  988. if (!empty($CFG->displayloginfailures)) {
  989. if (!isguestuser()) {
  990. // Include this file only when required.
  991. require_once($CFG->dirroot . '/user/lib.php');
  992. if ($count = user_count_login_failures($USER)) {
  993. $loggedinas .= '<div class="loginfailures">';
  994. $a = new stdClass();
  995. $a->attempts = $count;
  996. $loggedinas .= get_string('failedloginattempts', '', $a);
  997. if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
  998. $loggedinas .= ' ('.html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
  999. 'id' => 0 , 'modid' => 'site_errors')), get_string('logs')).')';
  1000. }
  1001. $loggedinas .= '</div>';
  1002. }
  1003. }
  1004. }
  1005. }
  1006. return $loggedinas;
  1007. }
  1008. /**
  1009. * Check whether the current page is a login page.
  1010. *
  1011. * @since Moodle 2.9
  1012. * @return bool
  1013. */
  1014. protected function is_login_page() {
  1015. // This is a real bit of a hack, but its a rarety that we need to do something like this.
  1016. // In fact the login pages should be only these two pages and as exposing this as an option for all pages
  1017. // could lead to abuse (or at least unneedingly complex code) the hack is the way to go.
  1018. return in_array(
  1019. $this->page->url->out_as_local_url(false, array()),
  1020. array(
  1021. '/login/index.php',
  1022. '/login/forgot_password.php',
  1023. )
  1024. );
  1025. }
  1026. /**
  1027. * Return the 'back' link that normally appears in the footer.
  1028. *
  1029. * @return string HTML fragment.
  1030. */
  1031. public function home_link() {
  1032. global $CFG, $SITE;
  1033. if ($this->page->pagetype == 'site-index') {
  1034. // Special case for site home page - please do not remove
  1035. return '<div class="sitelink">' .
  1036. '<a title="Moodle" href="http://moodle.org/">' .
  1037. '<img src="' . $this->image_url('moodlelogo_grayhat') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
  1038. } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
  1039. // Special case for during install/upgrade.
  1040. return '<div class="sitelink">'.
  1041. '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
  1042. '<img src="' . $this->image_url('moodlelogo_grayhat') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
  1043. } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
  1044. return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
  1045. get_string('home') . '</a></div>';
  1046. } else {
  1047. return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
  1048. format_string($this->page->course->shortname, true, array('context' => $this->page->context)) . '</a></div>';
  1049. }
  1050. }
  1051. /**
  1052. * Redirects the user by any means possible given the current state
  1053. *
  1054. * This function should not be called directly, it should always be called using
  1055. * the redirect function in lib/weblib.php
  1056. *
  1057. * The redirect function should really only be called before page output has started
  1058. * however it will allow itself to be called during the state STATE_IN_BODY
  1059. *
  1060. * @param string $encodedurl The URL to send to encoded if required
  1061. * @param string $message The message to display to the user if any
  1062. * @param int …

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