PageRenderTime 67ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/weblib.php

https://github.com/glovenone/moodle
PHP | 3312 lines | 1690 code | 387 blank | 1235 comment | 395 complexity | f73eec5ab04b5fd484d6012f82d15678 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, BSD-3-Clause, AGPL-3.0, MPL-2.0-no-copyleft-exception, Apache-2.0
  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. * Library of functions for web output
  18. *
  19. * Library of all general-purpose Moodle PHP functions and constants
  20. * that produce HTML output
  21. *
  22. * Other main libraries:
  23. * - datalib.php - functions that access the database.
  24. * - moodlelib.php - general-purpose Moodle functions.
  25. *
  26. * @package core
  27. * @subpackage lib
  28. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  29. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30. */
  31. defined('MOODLE_INTERNAL') || die();
  32. /// Constants
  33. /// Define text formatting types ... eventually we can add Wiki, BBcode etc
  34. /**
  35. * Does all sorts of transformations and filtering
  36. */
  37. define('FORMAT_MOODLE', '0'); // Does all sorts of transformations and filtering
  38. /**
  39. * Plain HTML (with some tags stripped)
  40. */
  41. define('FORMAT_HTML', '1'); // Plain HTML (with some tags stripped)
  42. /**
  43. * Plain text (even tags are printed in full)
  44. */
  45. define('FORMAT_PLAIN', '2'); // Plain text (even tags are printed in full)
  46. /**
  47. * Wiki-formatted text
  48. * Deprecated: left here just to note that '3' is not used (at the moment)
  49. * and to catch any latent wiki-like text (which generates an error)
  50. */
  51. define('FORMAT_WIKI', '3'); // Wiki-formatted text
  52. /**
  53. * Markdown-formatted text http://daringfireball.net/projects/markdown/
  54. */
  55. define('FORMAT_MARKDOWN', '4'); // Markdown-formatted text http://daringfireball.net/projects/markdown/
  56. /**
  57. * A moodle_url comparison using this flag will return true if the base URLs match, params are ignored
  58. */
  59. define('URL_MATCH_BASE', 0);
  60. /**
  61. * A moodle_url comparison using this flag will return true if the base URLs match and the params of url1 are part of url2
  62. */
  63. define('URL_MATCH_PARAMS', 1);
  64. /**
  65. * A moodle_url comparison using this flag will return true if the two URLs are identical, except for the order of the params
  66. */
  67. define('URL_MATCH_EXACT', 2);
  68. /**
  69. * Allowed tags - string of html tags that can be tested against for safe html tags
  70. * @global string $ALLOWED_TAGS
  71. * @name $ALLOWED_TAGS
  72. */
  73. global $ALLOWED_TAGS;
  74. $ALLOWED_TAGS =
  75. '<p><br><b><i><u><font><table><tbody><thead><tfoot><span><div><tr><td><th><ol><ul><dl><li><dt><dd><h1><h2><h3><h4><h5><h6><hr><img><a><strong><emphasis><em><sup><sub><address><cite><blockquote><pre><strike><param><acronym><nolink><lang><tex><algebra><math><mi><mn><mo><mtext><mspace><ms><mrow><mfrac><msqrt><mroot><mstyle><merror><mpadded><mphantom><mfenced><msub><msup><msubsup><munder><mover><munderover><mmultiscripts><mtable><mtr><mtd><maligngroup><malignmark><maction><cn><ci><apply><reln><fn><interval><inverse><sep><condition><declare><lambda><compose><ident><quotient><exp><factorial><divide><max><min><minus><plus><power><rem><times><root><gcd><and><or><xor><not><implies><forall><exists><abs><conjugate><eq><neq><gt><lt><geq><leq><ln><log><int><diff><partialdiff><lowlimit><uplimit><bvar><degree><set><list><union><intersect><in><notin><subset><prsubset><notsubset><notprsubset><setdiff><sum><product><limit><tendsto><mean><sdev><variance><median><mode><moment><vector><matrix><matrixrow><determinant><transpose><selector><annotation><semantics><annotation-xml><tt><code>';
  76. /**
  77. * Allowed protocols - array of protocols that are safe to use in links and so on
  78. * @global string $ALLOWED_PROTOCOLS
  79. */
  80. $ALLOWED_PROTOCOLS = array('http', 'https', 'ftp', 'news', 'mailto', 'rtsp', 'teamspeak', 'gopher', 'mms',
  81. 'color', 'callto', 'cursor', 'text-align', 'font-size', 'font-weight', 'font-style', 'font-family',
  82. 'border', 'border-bottom', 'border-left', 'border-top', 'border-right', 'margin', 'margin-bottom', 'margin-left', 'margin-top', 'margin-right',
  83. 'padding', 'padding-bottom', 'padding-left', 'padding-top', 'padding-right', 'vertical-align',
  84. 'background', 'background-color', 'text-decoration'); // CSS as well to get through kses
  85. /// Functions
  86. /**
  87. * Add quotes to HTML characters
  88. *
  89. * Returns $var with HTML characters (like "<", ">", etc.) properly quoted.
  90. * This function is very similar to {@link p()}
  91. *
  92. * @todo Remove obsolete param $obsolete if not used anywhere
  93. *
  94. * @param string $var the string potentially containing HTML characters
  95. * @param boolean $obsolete no longer used.
  96. * @return string
  97. */
  98. function s($var, $obsolete = false) {
  99. if ($var === '0' or $var === false or $var === 0) {
  100. return '0';
  101. }
  102. return preg_replace("/&amp;#(\d+|x[0-7a-fA-F]+);/i", "&#$1;", htmlspecialchars($var, ENT_QUOTES, 'UTF-8', true));
  103. }
  104. /**
  105. * Add quotes to HTML characters
  106. *
  107. * Prints $var with HTML characters (like "<", ">", etc.) properly quoted.
  108. * This function simply calls {@link s()}
  109. * @see s()
  110. *
  111. * @todo Remove obsolete param $obsolete if not used anywhere
  112. *
  113. * @param string $var the string potentially containing HTML characters
  114. * @param boolean $obsolete no longer used.
  115. * @return string
  116. */
  117. function p($var, $obsolete = false) {
  118. echo s($var, $obsolete);
  119. }
  120. /**
  121. * Does proper javascript quoting.
  122. *
  123. * Do not use addslashes anymore, because it does not work when magic_quotes_sybase is enabled.
  124. *
  125. * @param mixed $var String, Array, or Object to add slashes to
  126. * @return mixed quoted result
  127. */
  128. function addslashes_js($var) {
  129. if (is_string($var)) {
  130. $var = str_replace('\\', '\\\\', $var);
  131. $var = str_replace(array('\'', '"', "\n", "\r", "\0"), array('\\\'', '\\"', '\\n', '\\r', '\\0'), $var);
  132. $var = str_replace('</', '<\/', $var); // XHTML compliance
  133. } else if (is_array($var)) {
  134. $var = array_map('addslashes_js', $var);
  135. } else if (is_object($var)) {
  136. $a = get_object_vars($var);
  137. foreach ($a as $key=>$value) {
  138. $a[$key] = addslashes_js($value);
  139. }
  140. $var = (object)$a;
  141. }
  142. return $var;
  143. }
  144. /**
  145. * Remove query string from url
  146. *
  147. * Takes in a URL and returns it without the querystring portion
  148. *
  149. * @param string $url the url which may have a query string attached
  150. * @return string The remaining URL
  151. */
  152. function strip_querystring($url) {
  153. if ($commapos = strpos($url, '?')) {
  154. return substr($url, 0, $commapos);
  155. } else {
  156. return $url;
  157. }
  158. }
  159. /**
  160. * Returns the URL of the HTTP_REFERER, less the querystring portion if required
  161. *
  162. * @uses $_SERVER
  163. * @param boolean $stripquery if true, also removes the query part of the url.
  164. * @return string The resulting referer or empty string
  165. */
  166. function get_referer($stripquery=true) {
  167. if (isset($_SERVER['HTTP_REFERER'])) {
  168. if ($stripquery) {
  169. return strip_querystring($_SERVER['HTTP_REFERER']);
  170. } else {
  171. return $_SERVER['HTTP_REFERER'];
  172. }
  173. } else {
  174. return '';
  175. }
  176. }
  177. /**
  178. * Returns the name of the current script, WITH the querystring portion.
  179. *
  180. * This function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
  181. * return different things depending on a lot of things like your OS, Web
  182. * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.)
  183. * <b>NOTE:</b> This function returns false if the global variables needed are not set.
  184. *
  185. * @global string
  186. * @return mixed String, or false if the global variables needed are not set
  187. */
  188. function me() {
  189. global $ME;
  190. return $ME;
  191. }
  192. /**
  193. * Returns the name of the current script, WITH the full URL.
  194. *
  195. * This function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
  196. * return different things depending on a lot of things like your OS, Web
  197. * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.
  198. * <b>NOTE:</b> This function returns false if the global variables needed are not set.
  199. *
  200. * Like {@link me()} but returns a full URL
  201. * @see me()
  202. *
  203. * @global string
  204. * @return mixed String, or false if the global variables needed are not set
  205. */
  206. function qualified_me() {
  207. global $FULLME;
  208. return $FULLME;
  209. }
  210. /**
  211. * Class for creating and manipulating urls.
  212. *
  213. * It can be used in moodle pages where config.php has been included without any further includes.
  214. *
  215. * It is useful for manipulating urls with long lists of params.
  216. * One situation where it will be useful is a page which links to itself to perform various actions
  217. * and / or to process form data. A moodle_url object :
  218. * can be created for a page to refer to itself with all the proper get params being passed from page call to
  219. * page call and methods can be used to output a url including all the params, optionally adding and overriding
  220. * params and can also be used to
  221. * - output the url without any get params
  222. * - and output the params as hidden fields to be output within a form
  223. *
  224. * @link http://docs.moodle.org/en/Development:lib/weblib.php_moodle_url See short write up here
  225. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  226. * @package moodlecore
  227. */
  228. class moodle_url {
  229. /**
  230. * Scheme, ex.: http, https
  231. * @var string
  232. */
  233. protected $scheme = '';
  234. /**
  235. * hostname
  236. * @var string
  237. */
  238. protected $host = '';
  239. /**
  240. * Port number, empty means default 80 or 443 in case of http
  241. * @var unknown_type
  242. */
  243. protected $port = '';
  244. /**
  245. * Username for http auth
  246. * @var string
  247. */
  248. protected $user = '';
  249. /**
  250. * Password for http auth
  251. * @var string
  252. */
  253. protected $pass = '';
  254. /**
  255. * Script path
  256. * @var string
  257. */
  258. protected $path = '';
  259. /**
  260. * Optional slash argument value
  261. * @var string
  262. */
  263. protected $slashargument = '';
  264. /**
  265. * Anchor, may be also empty, null means none
  266. * @var string
  267. */
  268. protected $anchor = null;
  269. /**
  270. * Url parameters as associative array
  271. * @var array
  272. */
  273. protected $params = array(); // Associative array of query string params
  274. /**
  275. * Create new instance of moodle_url.
  276. *
  277. * @param moodle_url|string $url - moodle_url means make a copy of another
  278. * moodle_url and change parameters, string means full url or shortened
  279. * form (ex.: '/course/view.php'). It is strongly encouraged to not include
  280. * query string because it may result in double encoded values. Use the
  281. * $params instead. For admin URLs, just use /admin/script.php, this
  282. * class takes care of the $CFG->admin issue.
  283. * @param array $params these params override current params or add new
  284. */
  285. public function __construct($url, array $params = null) {
  286. global $CFG;
  287. if ($url instanceof moodle_url) {
  288. $this->scheme = $url->scheme;
  289. $this->host = $url->host;
  290. $this->port = $url->port;
  291. $this->user = $url->user;
  292. $this->pass = $url->pass;
  293. $this->path = $url->path;
  294. $this->slashargument = $url->slashargument;
  295. $this->params = $url->params;
  296. $this->anchor = $url->anchor;
  297. } else {
  298. // detect if anchor used
  299. $apos = strpos($url, '#');
  300. if ($apos !== false) {
  301. $anchor = substr($url, $apos);
  302. $anchor = ltrim($anchor, '#');
  303. $this->set_anchor($anchor);
  304. $url = substr($url, 0, $apos);
  305. }
  306. // normalise shortened form of our url ex.: '/course/view.php'
  307. if (strpos($url, '/') === 0) {
  308. // we must not use httpswwwroot here, because it might be url of other page,
  309. // devs have to use httpswwwroot explicitly when creating new moodle_url
  310. $url = $CFG->wwwroot.$url;
  311. }
  312. // now fix the admin links if needed, no need to mess with httpswwwroot
  313. if ($CFG->admin !== 'admin') {
  314. if (strpos($url, "$CFG->wwwroot/admin/") === 0) {
  315. $url = str_replace("$CFG->wwwroot/admin/", "$CFG->wwwroot/$CFG->admin/", $url);
  316. }
  317. }
  318. // parse the $url
  319. $parts = parse_url($url);
  320. if ($parts === false) {
  321. throw new moodle_exception('invalidurl');
  322. }
  323. if (isset($parts['query'])) {
  324. // note: the values may not be correctly decoded,
  325. // url parameters should be always passed as array
  326. parse_str(str_replace('&amp;', '&', $parts['query']), $this->params);
  327. }
  328. unset($parts['query']);
  329. foreach ($parts as $key => $value) {
  330. $this->$key = $value;
  331. }
  332. // detect slashargument value from path - we do not support directory names ending with .php
  333. $pos = strpos($this->path, '.php/');
  334. if ($pos !== false) {
  335. $this->slashargument = substr($this->path, $pos + 4);
  336. $this->path = substr($this->path, 0, $pos + 4);
  337. }
  338. }
  339. $this->params($params);
  340. }
  341. /**
  342. * Add an array of params to the params for this url.
  343. *
  344. * The added params override existing ones if they have the same name.
  345. *
  346. * @param array $params Defaults to null. If null then returns all params.
  347. * @return array Array of Params for url.
  348. */
  349. public function params(array $params = null) {
  350. $params = (array)$params;
  351. foreach ($params as $key=>$value) {
  352. if (is_int($key)) {
  353. throw new coding_exception('Url parameters can not have numeric keys!');
  354. }
  355. if (!is_string($value)) {
  356. if (is_array($value)) {
  357. throw new coding_exception('Url parameters values can not be arrays!');
  358. }
  359. if (is_object($value) and !method_exists($value, '__toString')) {
  360. throw new coding_exception('Url parameters values can not be objects, unless __toString() is defined!');
  361. }
  362. }
  363. $this->params[$key] = (string)$value;
  364. }
  365. return $this->params;
  366. }
  367. /**
  368. * Remove all params if no arguments passed.
  369. * Remove selected params if arguments are passed.
  370. *
  371. * Can be called as either remove_params('param1', 'param2')
  372. * or remove_params(array('param1', 'param2')).
  373. *
  374. * @param mixed $params either an array of param names, or a string param name,
  375. * @param string $params,... any number of additional param names.
  376. * @return array url parameters
  377. */
  378. public function remove_params($params = null) {
  379. if (!is_array($params)) {
  380. $params = func_get_args();
  381. }
  382. foreach ($params as $param) {
  383. unset($this->params[$param]);
  384. }
  385. return $this->params;
  386. }
  387. /**
  388. * Remove all url parameters
  389. * @param $params
  390. * @return void
  391. */
  392. public function remove_all_params($params = null) {
  393. $this->params = array();
  394. $this->slashargument = '';
  395. }
  396. /**
  397. * Add a param to the params for this url.
  398. *
  399. * The added param overrides existing one if they have the same name.
  400. *
  401. * @param string $paramname name
  402. * @param string $newvalue Param value. If new value specified current value is overriden or parameter is added
  403. * @return mixed string parameter value, null if parameter does not exist
  404. */
  405. public function param($paramname, $newvalue = '') {
  406. if (func_num_args() > 1) {
  407. // set new value
  408. $this->params(array($paramname=>$newvalue));
  409. }
  410. if (isset($this->params[$paramname])) {
  411. return $this->params[$paramname];
  412. } else {
  413. return null;
  414. }
  415. }
  416. /**
  417. * Merges parameters and validates them
  418. * @param array $overrideparams
  419. * @return array merged parameters
  420. */
  421. protected function merge_overrideparams(array $overrideparams = null) {
  422. $overrideparams = (array)$overrideparams;
  423. $params = $this->params;
  424. foreach ($overrideparams as $key=>$value) {
  425. if (is_int($key)) {
  426. throw new coding_exception('Overridden parameters can not have numeric keys!');
  427. }
  428. if (is_array($value)) {
  429. throw new coding_exception('Overridden parameters values can not be arrays!');
  430. }
  431. if (is_object($value) and !method_exists($value, '__toString')) {
  432. throw new coding_exception('Overridden parameters values can not be objects, unless __toString() is defined!');
  433. }
  434. $params[$key] = (string)$value;
  435. }
  436. return $params;
  437. }
  438. /**
  439. * Get the params as as a query string.
  440. * This method should not be used outside of this method.
  441. *
  442. * @param boolean $escaped Use &amp; as params separator instead of plain &
  443. * @param array $overrideparams params to add to the output params, these
  444. * override existing ones with the same name.
  445. * @return string query string that can be added to a url.
  446. */
  447. public function get_query_string($escaped = true, array $overrideparams = null) {
  448. $arr = array();
  449. if ($overrideparams !== null) {
  450. $params = $this->merge_overrideparams($overrideparams);
  451. } else {
  452. $params = $this->params;
  453. }
  454. foreach ($params as $key => $val) {
  455. $arr[] = rawurlencode($key)."=".rawurlencode($val);
  456. }
  457. if ($escaped) {
  458. return implode('&amp;', $arr);
  459. } else {
  460. return implode('&', $arr);
  461. }
  462. }
  463. /**
  464. * Shortcut for printing of encoded URL.
  465. * @return string
  466. */
  467. public function __toString() {
  468. return $this->out(true);
  469. }
  470. /**
  471. * Output url
  472. *
  473. * If you use the returned URL in HTML code, you want the escaped ampersands. If you use
  474. * the returned URL in HTTP headers, you want $escaped=false.
  475. *
  476. * @param boolean $escaped Use &amp; as params separator instead of plain &
  477. * @param array $overrideparams params to add to the output url, these override existing ones with the same name.
  478. * @return string Resulting URL
  479. */
  480. public function out($escaped = true, array $overrideparams = null) {
  481. if (!is_bool($escaped)) {
  482. debugging('Escape parameter must be of type boolean, '.gettype($escaped).' given instead.');
  483. }
  484. $uri = $this->out_omit_querystring().$this->slashargument;
  485. $querystring = $this->get_query_string($escaped, $overrideparams);
  486. if ($querystring !== '') {
  487. $uri .= '?' . $querystring;
  488. }
  489. if (!is_null($this->anchor)) {
  490. $uri .= '#'.$this->anchor;
  491. }
  492. return $uri;
  493. }
  494. /**
  495. * Returns url without parameters, everything before '?'.
  496. *
  497. * @param bool $includeanchor if {@link self::anchor} is defined, should it be returned?
  498. * @return string
  499. */
  500. public function out_omit_querystring($includeanchor = false) {
  501. $uri = $this->scheme ? $this->scheme.':'.((strtolower($this->scheme) == 'mailto') ? '':'//'): '';
  502. $uri .= $this->user ? $this->user.($this->pass? ':'.$this->pass:'').'@':'';
  503. $uri .= $this->host ? $this->host : '';
  504. $uri .= $this->port ? ':'.$this->port : '';
  505. $uri .= $this->path ? $this->path : '';
  506. if ($includeanchor and !is_null($this->anchor)) {
  507. $uri .= '#' . $this->anchor;
  508. }
  509. return $uri;
  510. }
  511. /**
  512. * Compares this moodle_url with another
  513. * See documentation of constants for an explanation of the comparison flags.
  514. * @param moodle_url $url The moodle_url object to compare
  515. * @param int $matchtype The type of comparison (URL_MATCH_BASE, URL_MATCH_PARAMS, URL_MATCH_EXACT)
  516. * @return boolean
  517. */
  518. public function compare(moodle_url $url, $matchtype = URL_MATCH_EXACT) {
  519. $baseself = $this->out_omit_querystring();
  520. $baseother = $url->out_omit_querystring();
  521. // Append index.php if there is no specific file
  522. if (substr($baseself,-1)=='/') {
  523. $baseself .= 'index.php';
  524. }
  525. if (substr($baseother,-1)=='/') {
  526. $baseother .= 'index.php';
  527. }
  528. // Compare the two base URLs
  529. if ($baseself != $baseother) {
  530. return false;
  531. }
  532. if ($matchtype == URL_MATCH_BASE) {
  533. return true;
  534. }
  535. $urlparams = $url->params();
  536. foreach ($this->params() as $param => $value) {
  537. if ($param == 'sesskey') {
  538. continue;
  539. }
  540. if (!array_key_exists($param, $urlparams) || $urlparams[$param] != $value) {
  541. return false;
  542. }
  543. }
  544. if ($matchtype == URL_MATCH_PARAMS) {
  545. return true;
  546. }
  547. foreach ($urlparams as $param => $value) {
  548. if ($param == 'sesskey') {
  549. continue;
  550. }
  551. if (!array_key_exists($param, $this->params()) || $this->param($param) != $value) {
  552. return false;
  553. }
  554. }
  555. return true;
  556. }
  557. /**
  558. * Sets the anchor for the URI (the bit after the hash)
  559. * @param string $anchor null means remove previous
  560. */
  561. public function set_anchor($anchor) {
  562. if (is_null($anchor)) {
  563. // remove
  564. $this->anchor = null;
  565. } else if ($anchor === '') {
  566. // special case, used as empty link
  567. $this->anchor = '';
  568. } else if (preg_match('|[a-zA-Z\_\:][a-zA-Z0-9\_\-\.\:]*|', $anchor)) {
  569. // Match the anchor against the NMTOKEN spec
  570. $this->anchor = $anchor;
  571. } else {
  572. // bad luck, no valid anchor found
  573. $this->anchor = null;
  574. }
  575. }
  576. /**
  577. * Sets the url slashargument value
  578. * @param string $path usually file path
  579. * @param string $parameter name of page parameter if slasharguments not supported
  580. * @param bool $supported usually null, then it depends on $CFG->slasharguments, use true or false for other servers
  581. * @return void
  582. */
  583. public function set_slashargument($path, $parameter = 'file', $supported = NULL) {
  584. global $CFG;
  585. if (is_null($supported)) {
  586. $supported = $CFG->slasharguments;
  587. }
  588. if ($supported) {
  589. $parts = explode('/', $path);
  590. $parts = array_map('rawurlencode', $parts);
  591. $path = implode('/', $parts);
  592. $this->slashargument = $path;
  593. unset($this->params[$parameter]);
  594. } else {
  595. $this->slashargument = '';
  596. $this->params[$parameter] = $path;
  597. }
  598. }
  599. // == static factory methods ==
  600. /**
  601. * General moodle file url.
  602. * @param string $urlbase the script serving the file
  603. * @param string $path
  604. * @param bool $forcedownload
  605. * @return moodle_url
  606. */
  607. public static function make_file_url($urlbase, $path, $forcedownload = false) {
  608. global $CFG;
  609. $params = array();
  610. if ($forcedownload) {
  611. $params['forcedownload'] = 1;
  612. }
  613. $url = new moodle_url($urlbase, $params);
  614. $url->set_slashargument($path);
  615. return $url;
  616. }
  617. /**
  618. * Factory method for creation of url pointing to plugin file.
  619. * Please note this method can be used only from the plugins to
  620. * create urls of own files, it must not be used outside of plugins!
  621. * @param int $contextid
  622. * @param string $component
  623. * @param string $area
  624. * @param int $itemid
  625. * @param string $pathname
  626. * @param string $filename
  627. * @param bool $forcedownload
  628. * @return moodle_url
  629. */
  630. public static function make_pluginfile_url($contextid, $component, $area, $itemid, $pathname, $filename, $forcedownload = false) {
  631. global $CFG;
  632. $urlbase = "$CFG->httpswwwroot/pluginfile.php";
  633. if ($itemid === NULL) {
  634. return self::make_file_url($urlbase, "/$contextid/$component/$area".$pathname.$filename, $forcedownload);
  635. } else {
  636. return self::make_file_url($urlbase, "/$contextid/$component/$area/$itemid".$pathname.$filename, $forcedownload);
  637. }
  638. }
  639. /**
  640. * Factory method for creation of url pointing to draft
  641. * file of current user.
  642. * @param int $draftid draft item id
  643. * @param string $pathname
  644. * @param string $filename
  645. * @param bool $forcedownload
  646. * @return moodle_url
  647. */
  648. public static function make_draftfile_url($draftid, $pathname, $filename, $forcedownload = false) {
  649. global $CFG, $USER;
  650. $urlbase = "$CFG->httpswwwroot/draftfile.php";
  651. $context = get_context_instance(CONTEXT_USER, $USER->id);
  652. return self::make_file_url($urlbase, "/$context->id/user/draft/$draftid".$pathname.$filename, $forcedownload);
  653. }
  654. /**
  655. * Factory method for creating of links to legacy
  656. * course files.
  657. * @param int $courseid
  658. * @param string $filepath
  659. * @param bool $forcedownload
  660. * @return moodle_url
  661. */
  662. public static function make_legacyfile_url($courseid, $filepath, $forcedownload = false) {
  663. global $CFG;
  664. $urlbase = "$CFG->wwwroot/file.php";
  665. return self::make_file_url($urlbase, '/'.$courseid.'/'.$filepath, $forcedownload);
  666. }
  667. }
  668. /**
  669. * Determine if there is data waiting to be processed from a form
  670. *
  671. * Used on most forms in Moodle to check for data
  672. * Returns the data as an object, if it's found.
  673. * This object can be used in foreach loops without
  674. * casting because it's cast to (array) automatically
  675. *
  676. * Checks that submitted POST data exists and returns it as object.
  677. *
  678. * @uses $_POST
  679. * @return mixed false or object
  680. */
  681. function data_submitted() {
  682. if (empty($_POST)) {
  683. return false;
  684. } else {
  685. return (object)$_POST;
  686. }
  687. }
  688. /**
  689. * Given some normal text this function will break up any
  690. * long words to a given size by inserting the given character
  691. *
  692. * It's multibyte savvy and doesn't change anything inside html tags.
  693. *
  694. * @param string $string the string to be modified
  695. * @param int $maxsize maximum length of the string to be returned
  696. * @param string $cutchar the string used to represent word breaks
  697. * @return string
  698. */
  699. function break_up_long_words($string, $maxsize=20, $cutchar=' ') {
  700. /// Loading the textlib singleton instance. We are going to need it.
  701. $textlib = textlib_get_instance();
  702. /// First of all, save all the tags inside the text to skip them
  703. $tags = array();
  704. filter_save_tags($string,$tags);
  705. /// Process the string adding the cut when necessary
  706. $output = '';
  707. $length = $textlib->strlen($string);
  708. $wordlength = 0;
  709. for ($i=0; $i<$length; $i++) {
  710. $char = $textlib->substr($string, $i, 1);
  711. if ($char == ' ' or $char == "\t" or $char == "\n" or $char == "\r" or $char == "<" or $char == ">") {
  712. $wordlength = 0;
  713. } else {
  714. $wordlength++;
  715. if ($wordlength > $maxsize) {
  716. $output .= $cutchar;
  717. $wordlength = 0;
  718. }
  719. }
  720. $output .= $char;
  721. }
  722. /// Finally load the tags back again
  723. if (!empty($tags)) {
  724. $output = str_replace(array_keys($tags), $tags, $output);
  725. }
  726. return $output;
  727. }
  728. /**
  729. * Try and close the current window using JavaScript, either immediately, or after a delay.
  730. *
  731. * Echo's out the resulting XHTML & javascript
  732. *
  733. * @global object
  734. * @global object
  735. * @param integer $delay a delay in seconds before closing the window. Default 0.
  736. * @param boolean $reloadopener if true, we will see if this window was a pop-up, and try
  737. * to reload the parent window before this one closes.
  738. */
  739. function close_window($delay = 0, $reloadopener = false) {
  740. global $PAGE, $OUTPUT;
  741. if (!$PAGE->headerprinted) {
  742. $PAGE->set_title(get_string('closewindow'));
  743. echo $OUTPUT->header();
  744. } else {
  745. $OUTPUT->container_end_all(false);
  746. }
  747. if ($reloadopener) {
  748. // Trigger the reload immediately, even if the reload is after a delay.
  749. $PAGE->requires->js_function_call('window.opener.location.reload', array(true));
  750. }
  751. $OUTPUT->notification(get_string('windowclosing'), 'notifysuccess');
  752. $PAGE->requires->js_function_call('close_window', array(new stdClass()), false, $delay);
  753. echo $OUTPUT->footer();
  754. exit;
  755. }
  756. /**
  757. * Returns a string containing a link to the user documentation for the current
  758. * page. Also contains an icon by default. Shown to teachers and admin only.
  759. *
  760. * @global object
  761. * @global object
  762. * @param string $text The text to be displayed for the link
  763. * @param string $iconpath The path to the icon to be displayed
  764. * @return string The link to user documentation for this current page
  765. */
  766. function page_doc_link($text='') {
  767. global $CFG, $PAGE, $OUTPUT;
  768. if (empty($CFG->docroot) || during_initial_install()) {
  769. return '';
  770. }
  771. if (!has_capability('moodle/site:doclinks', $PAGE->context)) {
  772. return '';
  773. }
  774. $path = $PAGE->docspath;
  775. if (!$path) {
  776. return '';
  777. }
  778. return $OUTPUT->doc_link($path, $text);
  779. }
  780. /**
  781. * Validates an email to make sure it makes sense.
  782. *
  783. * @param string $address The email address to validate.
  784. * @return boolean
  785. */
  786. function validate_email($address) {
  787. return (preg_match('#^[-!\#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+'.
  788. '(\.[-!\#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+)*'.
  789. '@'.
  790. '[-!\#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.
  791. '[-!\#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$#',
  792. $address));
  793. }
  794. /**
  795. * Extracts file argument either from file parameter or PATH_INFO
  796. * Note: $scriptname parameter is not needed anymore
  797. *
  798. * @global string
  799. * @uses $_SERVER
  800. * @uses PARAM_PATH
  801. * @return string file path (only safe characters)
  802. */
  803. function get_file_argument() {
  804. global $SCRIPT;
  805. $relativepath = optional_param('file', FALSE, PARAM_PATH);
  806. if ($relativepath !== false and $relativepath !== '') {
  807. return $relativepath;
  808. }
  809. $relativepath = false;
  810. // then try extract file from the slasharguments
  811. if (stripos($_SERVER['SERVER_SOFTWARE'], 'iis') !== false) {
  812. // NOTE: ISS tends to convert all file paths to single byte DOS encoding,
  813. // we can not use other methods because they break unicode chars,
  814. // the only way is to use URL rewriting
  815. if (isset($_SERVER['PATH_INFO']) and $_SERVER['PATH_INFO'] !== '') {
  816. // check that PATH_INFO works == must not contain the script name
  817. if (strpos($_SERVER['PATH_INFO'], $SCRIPT) === false) {
  818. $relativepath = clean_param(urldecode($_SERVER['PATH_INFO']), PARAM_PATH);
  819. }
  820. }
  821. } else {
  822. // all other apache-like servers depend on PATH_INFO
  823. if (isset($_SERVER['PATH_INFO'])) {
  824. if (isset($_SERVER['SCRIPT_NAME']) and strpos($_SERVER['PATH_INFO'], $_SERVER['SCRIPT_NAME']) === 0) {
  825. $relativepath = substr($_SERVER['PATH_INFO'], strlen($_SERVER['SCRIPT_NAME']));
  826. } else {
  827. $relativepath = $_SERVER['PATH_INFO'];
  828. }
  829. $relativepath = clean_param($relativepath, PARAM_PATH);
  830. }
  831. }
  832. return $relativepath;
  833. }
  834. /**
  835. * Just returns an array of text formats suitable for a popup menu
  836. *
  837. * @uses FORMAT_MOODLE
  838. * @uses FORMAT_HTML
  839. * @uses FORMAT_PLAIN
  840. * @uses FORMAT_MARKDOWN
  841. * @return array
  842. */
  843. function format_text_menu() {
  844. return array (FORMAT_MOODLE => get_string('formattext'),
  845. FORMAT_HTML => get_string('formathtml'),
  846. FORMAT_PLAIN => get_string('formatplain'),
  847. FORMAT_MARKDOWN => get_string('formatmarkdown'));
  848. }
  849. /**
  850. * Given text in a variety of format codings, this function returns
  851. * the text as safe HTML.
  852. *
  853. * This function should mainly be used for long strings like posts,
  854. * answers, glossary items etc. For short strings @see format_string().
  855. *
  856. * <pre>
  857. * Options:
  858. * trusted : If true the string won't be cleaned. Default false required noclean=true.
  859. * noclean : If true the string won't be cleaned. Default false required trusted=true.
  860. * nocache : If true the strign will not be cached and will be formatted every call. Default false.
  861. * filter : If true the string will be run through applicable filters as well. Default true.
  862. * para : If true then the returned string will be wrapped in div tags. Default true.
  863. * newlines : If true then lines newline breaks will be converted to HTML newline breaks. Default true.
  864. * context : The context that will be used for filtering.
  865. * overflowdiv : If set to true the formatted text will be encased in a div
  866. * with the class no-overflow before being returned. Default false.
  867. * allowid : If true then id attributes will not be removed, even when
  868. * using htmlpurifier. Default false.
  869. * </pre>
  870. *
  871. * @todo Finish documenting this function
  872. *
  873. * @staticvar array $croncache
  874. * @param string $text The text to be formatted. This is raw text originally from user input.
  875. * @param int $format Identifier of the text format to be used
  876. * [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_MARKDOWN]
  877. * @param object/array $options text formatting options
  878. * @param int $courseid_do_not_use deprecated course id, use context option instead
  879. * @return string
  880. */
  881. function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_do_not_use = NULL) {
  882. global $CFG, $COURSE, $DB, $PAGE;
  883. static $croncache = array();
  884. if ($text === '' || is_null($text)) {
  885. return ''; // no need to do any filters and cleaning
  886. }
  887. $options = (array)$options; // detach object, we can not modify it
  888. if (!isset($options['trusted'])) {
  889. $options['trusted'] = false;
  890. }
  891. if (!isset($options['noclean'])) {
  892. if ($options['trusted'] and trusttext_active()) {
  893. // no cleaning if text trusted and noclean not specified
  894. $options['noclean'] = true;
  895. } else {
  896. $options['noclean'] = false;
  897. }
  898. }
  899. if (!isset($options['nocache'])) {
  900. $options['nocache'] = false;
  901. }
  902. if (!isset($options['filter'])) {
  903. $options['filter'] = true;
  904. }
  905. if (!isset($options['para'])) {
  906. $options['para'] = true;
  907. }
  908. if (!isset($options['newlines'])) {
  909. $options['newlines'] = true;
  910. }
  911. if (!isset($options['overflowdiv'])) {
  912. $options['overflowdiv'] = false;
  913. }
  914. // Calculate best context
  915. if (empty($CFG->version) or $CFG->version < 2010072800 or during_initial_install()) {
  916. // do not filter anything during installation or before upgrade completes
  917. $context = null;
  918. } else if (isset($options['context'])) { // first by explicit passed context option
  919. if (is_object($options['context'])) {
  920. $context = $options['context'];
  921. } else {
  922. $context = get_context_instance_by_id($options['context']);
  923. }
  924. } else if ($courseid_do_not_use) {
  925. // legacy courseid
  926. $context = get_context_instance(CONTEXT_COURSE, $courseid_do_not_use);
  927. } else {
  928. // fallback to $PAGE->context this may be problematic in CLI and other non-standard pages :-(
  929. $context = $PAGE->context;
  930. }
  931. if (!$context) {
  932. // either install/upgrade or something has gone really wrong because context does not exist (yet?)
  933. $options['nocache'] = true;
  934. $options['filter'] = false;
  935. }
  936. if ($options['filter']) {
  937. $filtermanager = filter_manager::instance();
  938. } else {
  939. $filtermanager = new null_filter_manager();
  940. }
  941. if (!empty($CFG->cachetext) and empty($options['nocache'])) {
  942. $hashstr = $text.'-'.$filtermanager->text_filtering_hash($context).'-'.$context->id.'-'.current_language().'-'.
  943. (int)$format.(int)$options['trusted'].(int)$options['noclean'].
  944. (int)$options['para'].(int)$options['newlines'];
  945. $time = time() - $CFG->cachetext;
  946. $md5key = md5($hashstr);
  947. if (CLI_SCRIPT) {
  948. if (isset($croncache[$md5key])) {
  949. return $croncache[$md5key];
  950. }
  951. }
  952. if ($oldcacheitem = $DB->get_record('cache_text', array('md5key'=>$md5key), '*', IGNORE_MULTIPLE)) {
  953. if ($oldcacheitem->timemodified >= $time) {
  954. if (CLI_SCRIPT) {
  955. if (count($croncache) > 150) {
  956. reset($croncache);
  957. $key = key($croncache);
  958. unset($croncache[$key]);
  959. }
  960. $croncache[$md5key] = $oldcacheitem->formattedtext;
  961. }
  962. return $oldcacheitem->formattedtext;
  963. }
  964. }
  965. }
  966. switch ($format) {
  967. case FORMAT_HTML:
  968. if (!$options['noclean']) {
  969. $text = clean_text($text, FORMAT_HTML, $options);
  970. }
  971. $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_HTML, 'noclean' => $options['noclean']));
  972. break;
  973. case FORMAT_PLAIN:
  974. $text = s($text); // cleans dangerous JS
  975. $text = rebuildnolinktag($text);
  976. $text = str_replace(' ', '&nbsp; ', $text);
  977. $text = nl2br($text);
  978. break;
  979. case FORMAT_WIKI:
  980. // this format is deprecated
  981. $text = '<p>NOTICE: Wiki-like formatting has been removed from Moodle. You should not be seeing
  982. this message as all texts should have been converted to Markdown format instead.
  983. Please post a bug report to http://moodle.org/bugs with information about where you
  984. saw this message.</p>'.s($text);
  985. break;
  986. case FORMAT_MARKDOWN:
  987. $text = markdown_to_html($text);
  988. if (!$options['noclean']) {
  989. $text = clean_text($text, FORMAT_HTML, $options);
  990. }
  991. $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_MARKDOWN, 'noclean' => $options['noclean']));
  992. break;
  993. default: // FORMAT_MOODLE or anything else
  994. $text = text_to_html($text, null, $options['para'], $options['newlines']);
  995. if (!$options['noclean']) {
  996. $text = clean_text($text, FORMAT_HTML, $options);
  997. }
  998. $text = $filtermanager->filter_text($text, $context, array('originalformat' => $format, 'noclean' => $options['noclean']));
  999. break;
  1000. }
  1001. if ($options['filter']) {
  1002. // at this point there should not be any draftfile links any more,
  1003. // this happens when developers forget to post process the text.
  1004. // The only potential problem is that somebody might try to format
  1005. // the text before storing into database which would be itself big bug.
  1006. $text = str_replace("\"$CFG->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/brokenfile.php#", $text);
  1007. }
  1008. // Warn people that we have removed this old mechanism, just in case they
  1009. // were stupid enough to rely on it.
  1010. if (isset($CFG->currenttextiscacheable)) {
  1011. debugging('Once upon a time, Moodle had a truly evil use of global variables ' .
  1012. 'called $CFG->currenttextiscacheable. The good news is that this no ' .
  1013. 'longer exists. The bad news is that you seem to be using a filter that '.
  1014. 'relies on it. Please seek out and destroy that filter code.', DEBUG_DEVELOPER);
  1015. }
  1016. if (!empty($options['overflowdiv'])) {
  1017. $text = html_writer::tag('div', $text, array('class'=>'no-overflow'));
  1018. }
  1019. if (empty($options['nocache']) and !empty($CFG->cachetext)) {
  1020. if (CLI_SCRIPT) {
  1021. // special static cron cache - no need to store it in db if its not already there
  1022. if (count($croncache) > 150) {
  1023. reset($croncache);
  1024. $key = key($croncache);
  1025. unset($croncache[$key]);
  1026. }
  1027. $croncache[$md5key] = $text;
  1028. return $text;
  1029. }
  1030. $newcacheitem = new stdClass();
  1031. $newcacheitem->md5key = $md5key;
  1032. $newcacheitem->formattedtext = $text;
  1033. $newcacheitem->timemodified = time();
  1034. if ($oldcacheitem) { // See bug 4677 for discussion
  1035. $newcacheitem->id = $oldcacheitem->id;
  1036. try {
  1037. $DB->update_record('cache_text', $newcacheitem); // Update existing record in the cache table
  1038. } catch (dml_exception $e) {
  1039. // It's unlikely that the cron cache cleaner could have
  1040. // deleted this entry in the meantime, as it allows
  1041. // some extra time to cover these cases.
  1042. }
  1043. } else {
  1044. try {
  1045. $DB->insert_record('cache_text', $newcacheitem); // Insert a new record in the cache table
  1046. } catch (dml_exception $e) {
  1047. // Again, it's possible that another user has caused this
  1048. // record to be created already in the time that it took
  1049. // to traverse this function. That's OK too, as the
  1050. // call above handles duplicate entries, and eventually
  1051. // the cron cleaner will delete them.
  1052. }
  1053. }
  1054. }
  1055. return $text;
  1056. }
  1057. /**
  1058. * Resets all data related to filters, called during upgrade or when filter settings change.
  1059. *
  1060. * @global object
  1061. * @global object
  1062. * @return void
  1063. */
  1064. function reset_text_filters_cache() {
  1065. global $CFG, $DB;
  1066. $DB->delete_records('cache_text');
  1067. $purifdir = $CFG->dataroot.'/cache/htmlpurifier';
  1068. remove_dir($purifdir, true);
  1069. }
  1070. /**
  1071. * Given a simple string, this function returns the string
  1072. * processed by enabled string filters if $CFG->filterall is enabled
  1073. *
  1074. * This function should be used to print short strings (non html) that
  1075. * need filter processing e.g. activity titles, post subjects,
  1076. * glossary concepts.
  1077. *
  1078. * @global object
  1079. * @global object
  1080. * @global object
  1081. * @staticvar bool $strcache
  1082. * @param string $string The string to be filtered.
  1083. * @param boolean $striplinks To strip any link in the result text.
  1084. Moodle 1.8 default changed from false to true! MDL-8713
  1085. * @param array $options options array/object or courseid
  1086. * @return string
  1087. */
  1088. function format_string($string, $striplinks = true, $options = NULL) {
  1089. global $CFG, $COURSE, $PAGE;
  1090. //We'll use a in-memory cache here to speed up repeated strings
  1091. static $strcache = false;
  1092. if (empty($CFG->version) or $CFG->version < 2010072800 or during_initial_install()) {
  1093. // do not filter anything during installation or before upgrade completes
  1094. return $string = strip_tags($string);
  1095. }
  1096. if ($strcache === false or count($strcache) > 2000) { // this number might need some tuning to limit memory usage in cron
  1097. $strcache = array();
  1098. }
  1099. if (is_numeric($options)) {
  1100. // legacy courseid usage
  1101. $options = array('context'=>get_context_instance(CONTEXT_COURSE, $options));
  1102. } else {
  1103. $options = (array)$options; // detach object, we can not modify it
  1104. }
  1105. if (empty($options['context'])) {
  1106. // fallback to $PAGE->context this may be problematic in CLI and other non-standard pages :-(
  1107. $options['context'] = $PAGE->context;
  1108. } else if (is_numeric($options['context'])) {
  1109. $options['context'] = get_context_instance_by_id($options['context']);
  1110. }
  1111. if (!$options['context']) {
  1112. // we did not find any context? weird
  1113. return $string = strip_tags($string);
  1114. }
  1115. //Calculate md5
  1116. $md5 = md5($string.'<+>'.$striplinks.'<+>'.$options['context']->id.'<+>'.current_language());
  1117. //Fetch from cache if possible
  1118. if (isset($strcache[$md5])) {
  1119. return $strcache[$md5];
  1120. }
  1121. // First replace all ampersands not followed by html entity code
  1122. // Regular expression moved to its own method for easier unit testing
  1123. $string = replace_ampersands_not_followed_by_entity($string);
  1124. if (!empty($CFG->filterall)) {
  1125. $string = filter_manager::instance()->filter_string($string, $options['context']);
  1126. }
  1127. // If the site requires it, strip ALL tags from this string
  1128. if (!empty($CFG->formatstringstriptags)) {
  1129. $string = strip_tags($string);
  1130. } else {
  1131. // Otherwise strip just links if that is required (default)
  1132. if ($striplinks) { //strip links in string
  1133. $string = strip_links($string);
  1134. }
  1135. $string = clean_text($string);
  1136. }
  1137. //Store to cache
  1138. $strcache[$md5] = $string;
  1139. return $string;
  1140. }
  1141. /**
  1142. * Given a string, performs a negative lookahead looking for any ampersand character
  1143. * that is not followed by a proper HTML entity. If any is found, it is replaced
  1144. * by &amp;. The string is then returned.
  1145. *
  1146. * @param string $string
  1147. * @return string
  1148. */
  1149. function replace_ampersands_not_followed_by_entity($string) {
  1150. return preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&amp;", $string);
  1151. }
  1152. /**
  1153. * Given a string, replaces all <a>.*</a> by .* and returns the string.
  1154. *
  1155. * @param string $string
  1156. * @return string
  1157. */
  1158. function strip_links($string) {
  1159. return preg_replace('/(<a\s[^>]+?>)(.+?)(<\/a>)/is','$2',$string);
  1160. }
  1161. /**
  1162. * This expression turns links into something nice in a text format. (Russell Jungwirth)
  1163. *
  1164. * @param string $string
  1165. * @return string
  1166. */
  1167. function wikify_links($string) {
  1168. return preg_replace('~(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)~i','$3 [ $2 ]', $string);
  1169. }
  1170. /**
  1171. * Replaces non-standard HTML entities
  1172. *
  1173. * @param string $string
  1174. * @return string
  1175. */
  1176. function fix_non_standard_entities($string) {
  1177. $text = preg_replace('/&#0*([0-9]+);?/', '&#$1;', $string);
  1178. $text = preg_replace('/&#x0*([0-9a-fA-F]+);?/', '&#x$1;', $text);
  1179. $text = preg_replace('[\x00-\x08\x0b-\x0c\x0e-\x1f]', '', $text);
  1180. return $text;
  1181. }
  1182. /**
  1183. * Given text in a variety of format codings, this function returns
  1184. * the text as plain text suitable for plain email.
  1185. *
  1186. * @uses FORMAT_MOODLE
  1187. * @uses FORMAT_HTML
  1188. * @uses FORMAT_PLAIN
  1189. * @uses FORMAT_WIKI
  1190. * @uses FORMAT_MARKDOWN
  1191. * @param string $text The text to be formatted. This is raw text originally from user input.
  1192. * @param int $format Identifier of the text format to be used
  1193. * [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN]
  1194. * @return string
  1195. */
  1196. function format_text_email($text, $format) {
  1197. switch ($format) {
  1198. case FORMAT_PLAIN:
  1199. return $text;
  1200. break;
  1201. case FORMAT_WIKI:
  1202. // there should not be any of these any more!
  1203. $text = wikify_links($text);
  1204. return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
  1205. break;
  1206. case FORMAT_HTML:
  1207. return html_to_text($text);
  1208. break;
  1209. case FORMAT_MOODLE:
  1210. case FORMAT_MARKDOWN:
  1211. default:
  1212. $text = wikify_links($text);
  1213. return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
  1214. break;
  1215. }
  1216. }
  1217. /**
  1218. * Formats activity intro text
  1219. *
  1220. * @global object
  1221. * @uses CONTEXT_MODULE
  1222. * @param string $module name of module
  1223. * @param object $activity instance of activity
  1224. * @param int $cmid course module id
  1225. * @param bool $filter filter resulting html text
  1226. * @return text
  1227. */
  1228. function format_module_intro($module, $activity, $cmid, $filter=true) {
  1229. global $CFG;
  1230. require_once("$CFG->libdir/filelib.php");
  1231. $context = get_context_instance(CONTEXT_MODULE, $cmid);
  1232. $options = array('noclean'=>true, 'para'=>false, 'filter'=>$filter, 'context'=>$context, 'overflowdiv'=>true);
  1233. $intro = file_rewrite_pluginfile_urls($activity->intro, 'pluginfile.php', $context->id, 'mod_'.$module, 'intro', null);
  1234. return trim(format_text($intro, $activity->introformat, $options, null));
  1235. }
  1236. /**
  1237. * Legacy function, used for cleaning of old forum and glossary text only.
  1238. *
  1239. * @global object
  1240. * @param string $text text that may contain legacy TRUSTTEXT marker
  1241. * @return text without legacy TRUSTTEXT marker
  1242. */
  1243. function trusttext_strip($text) {
  1244. while (true) { //removing nested TRUSTTEXT
  1245. $orig = $text;
  1246. $text = str_replace('#####TRUSTTEXT#####', '', $text);
  1247. if (strcmp($orig, $text) === 0) {
  1248. return $text;
  1249. }
  1250. }
  1251. }
  1252. /**
  1253. * Must be called before editing of all texts
  1254. * with trust flag. Removes all XSS nasties
  1255. * from texts stored in database if needed.
  1256. *
  1257. * @param object $object data object with xxx, xxxformat and xxxtrust fields
  1258. * @param string $field name of text field
  1259. * @param object $context active context
  1260. * @return object updated $object
  1261. */
  1262. function trusttext_pre_edit($object, $field, $context) {
  1263. $trustfield = $field.'trust';
  1264. $formatfield = $field.'format';
  1265. if (!$object->$trustfield or !trusttext_trusted($context)) {
  1266. $object->$field = clean_text($object->$field, $object->$formatfield);
  1267. }
  1268. return $object;
  1269. }
  1270. /**
  1271. * Is current user trusted to enter no dangerous XSS in this context?
  1272. *
  1273. * Please note the user must be in fact trusted everywhere on this server!!
  1274. *
  1275. * @param object $context
  1276. * @return bool true if user trusted
  1277. */
  1278. function trusttext_trusted($context) {
  1279. return (trusttext_active() and has_capability('moodle/site:trustcontent', $context));
  1280. }
  1281. /**
  1282. * Is trusttext feature active?
  1283. *
  1284. * @global object
  1285. * @param object $context
  1286. * @return bool
  1287. */
  1288. function trusttext_active() {
  1289. global $CFG;
  1290. return !empty($CFG->enabletrusttext);
  1291. }
  1292. /**
  1293. * Given raw text (eg typed in by a user), this function cleans it up
  1294. * and removes any nasty tags that could mess up Moodle pages.
  1295. *
  1296. * NOTE: the format parameter was deprecated because we can safely clean only HTML.
  1297. *
  1298. * @param string $text The text to be cleaned
  1299. * @param int $format deprecated parameter, should always contain FORMAT_HTML or FORMAT_MOODLE
  1300. * @param array $options Array of options; currently only option supported is 'allowid' (if true,
  1301. * does not remove id attributes when cleaning)
  1302. * @return string The cleaned up text
  1303. */
  1304. function clean_text($text, $format = FORMAT_HTML, $options = array()) {
  1305. global $ALLOWED_TAGS, $CFG;
  1306. if (empty($text) or is_numeric($text)) {
  1307. return (string)$text;
  1308. }
  1309. if ($format != FORMAT_HTML and $format != FORMAT_HTML) {
  1310. // TODO: we need to standardise cleanup of text when loading it into editor first
  1311. //debugging('clean_text() is designed to work only with html');
  1312. }
  1313. if ($format == FORMAT_PLAIN) {
  1314. return $text;
  1315. }
  1316. if (!empty($CFG->enablehtmlpurifier)) {
  1317. $text = purify_html($text, $options);
  1318. } else {
  1319. /// Fix non standard entity notations
  1320. $text = fix_non_standard_entities($text);
  1321. /// Remove tags that are not allowed
  1322. $text = strip_tags($text, $ALLOWED_TAGS);
  1323. /// Clean up embedded scripts and , using kses
  1324. $text = cleanAttributes($text);
  1325. /// Again remove tags that are not allowed
  1326. $text = strip_tags($text, $ALLOWED_TAGS);
  1327. }
  1328. // Remove potential script events - some extra protection for undiscovered bugs in our code
  1329. $text = preg_replace("~([^a-z])language([[:space:]]*)=~i", "$1Xlanguage=", $text);
  1330. $text = preg_replace("~([^a-z])on([a-z]+)([[:space:]]*)=~i", "$1Xon$2=", $text);
  1331. return $text;
  1332. }
  1333. /**
  1334. * KSES replacement cleaning function - uses HTML Purifier.
  1335. *
  1336. * @global object
  1337. * @param string $text The (X)HTML string to purify
  1338. * @param array $options Array of options; currently only option supported is 'allowid' (if set,
  1339. * does not remove id attributes when cleaning)
  1340. */
  1341. function purify_html($text, $options = array()) {
  1342. global $CFG;
  1343. $type = !empty($options['allowid']) ? 'allowid' : 'normal';
  1344. static $purifiers = array();
  1345. if (empty($purifiers[$type])) {
  1346. // make sure the serializer dir exists, it should be fine if it disappears later during cache reset
  1347. $cachedir = $CFG->dataroot.'/cache/htmlpurifier';
  1348. check_dir_exists($cachedir);
  1349. require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.safe-includes.php';
  1350. $config = HTMLPurifier_Config::createDefault();
  1351. $config->set('HTML.DefinitionID', 'moodlehtml');
  1352. $config->set('HTML.DefinitionRev', 2);
  1353. $config->set('Cache.SerializerPath', $cachedir);
  1354. $config->set('Cache.SerializerPermissions', $CFG->directorypermissions);
  1355. $config->set('Core.NormalizeNewlines', false);
  1356. $config->set('Core.ConvertDocumentToFragment', true);
  1357. $config->set('Core.Encoding', 'UTF-8');
  1358. $config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
  1359. $config->set('URI.AllowedSchemes', array('http'=>true, 'https'=>true, 'ftp'=>true, 'irc'=>true, 'nntp'=>true, 'news'=>true, 'rtsp'=>true, 'teamspeak'=>true, 'gopher'=>true, 'mms'=>true));
  1360. $config->set('Attr.AllowedFrameTargets', array('_blank'));
  1361. if (!empty($CFG->allowobjectembed)) {
  1362. $config->set('HTML.SafeObject', true);
  1363. $config->set('Output.FlashCompat', true);
  1364. $config->set('HTML.SafeEmbed', true);
  1365. }
  1366. if ($type === 'allowid') {
  1367. $config->set('Attr.EnableID', true);
  1368. }
  1369. if ($def = $config->maybeGetRawHTMLDefinition()) {
  1370. $def->addElement('nolink', 'Block', 'Flow', array()); // skip our filters inside
  1371. $def->addElement('tex', 'Inline', 'Inline', array()); // tex syntax, equivalent to $$xx$$
  1372. $def->addElement('algebra', 'Inline', 'Inline', array()); // algebra syntax, equivalent to @@xx@@
  1373. $def->addElement('lang', 'Block', 'Flow', array(), array('lang'=>'CDATA')); // old and future style multilang - only our hacked lang attribute
  1374. $def->addAttribute('span', 'xxxlang', 'CDATA'); // current problematic multilang
  1375. }
  1376. $purifier = new HTMLPurifier($config);
  1377. $purifiers[$type] = $purifier;
  1378. } else {
  1379. $purifier = $purifiers[$type];
  1380. }
  1381. $multilang = (strpos($text, 'class="multilang"') !== false);
  1382. if ($multilang) {
  1383. $text = preg_replace('/<span(\s+lang="([a-zA-Z0-9_-]+)"|\s+class="multilang"){2}\s*>/', '<span xxxlang="${2}">', $text);
  1384. }
  1385. $text = $purifier->purify($text);
  1386. if ($multilang) {
  1387. $text = preg_replace('/<span xxxlang="([a-zA-Z0-9_-]+)">/', '<span lang="${1}" class="multilang">', $text);
  1388. }
  1389. return $text;
  1390. }
  1391. /**
  1392. * This function takes a string and examines it for HTML tags.
  1393. *
  1394. * If tags are detected it passes the string to a helper function {@link cleanAttributes2()}
  1395. * which checks for attributes and filters them for malicious content
  1396. *
  1397. * @param string $str The string to be examined for html tags
  1398. * @return string
  1399. */
  1400. function cleanAttributes($str){
  1401. $result = preg_replace_callback(
  1402. '%(<[^>]*(>|$)|>)%m', #search for html tags
  1403. "cleanAttributes2",
  1404. $str
  1405. );
  1406. return $result;
  1407. }
  1408. /**
  1409. * This function takes a string with an html tag and strips out any unallowed
  1410. * protocols e.g. javascript:
  1411. *
  1412. * It calls ancillary functions in kses which are prefixed by kses
  1413. *
  1414. * @global object
  1415. * @global string
  1416. * @param array $htmlArray An array from {@link cleanAttributes()}, containing in its 1st
  1417. * element the html to be cleared
  1418. * @return string
  1419. */
  1420. function cleanAttributes2($htmlArray){
  1421. global $CFG, $ALLOWED_PROTOCOLS;
  1422. require_once($CFG->libdir .'/kses.php');
  1423. $htmlTag = $htmlArray[1];
  1424. if (substr($htmlTag, 0, 1) != '<') {
  1425. return '&gt;'; //a single character ">" detected
  1426. }
  1427. if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $htmlTag, $matches)) {
  1428. return ''; // It's seriously malformed
  1429. }
  1430. $slash = trim($matches[1]); //trailing xhtml slash
  1431. $elem = $matches[2]; //the element name
  1432. $attrlist = $matches[3]; // the list of attributes as a string
  1433. $attrArray = kses_hair($attrlist, $ALLOWED_PROTOCOLS);
  1434. $attStr = '';
  1435. foreach ($attrArray as $arreach) {
  1436. $arreach['name'] = strtolower($arreach['name']);
  1437. if ($arreach['name'] == 'style') {
  1438. $value = $arreach['value'];
  1439. while (true) {
  1440. $prevvalue = $value;
  1441. $value = kses_no_null($value);
  1442. $value = preg_replace("/\/\*.*\*\//Us", '', $value);
  1443. $value = kses_decode_entities($value);
  1444. $value = preg_replace('/(&#[0-9]+)(;?)/', "\\1;", $value);
  1445. $value = preg_replace('/(&#x[0-9a-fA-F]+)(;?)/', "\\1;", $value);
  1446. if ($value === $prevvalue) {
  1447. $arreach['value'] = $value;
  1448. break;
  1449. }
  1450. }
  1451. $arreach['value'] = preg_replace("/j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t/i", "Xjavascript", $arreach['value']);
  1452. $arreach['value'] = preg_replace("/v\s*b\s*s\s*c\s*r\s*i\s*p\s*t/i", "Xvbscript", $arreach['value']);
  1453. $arreach['value'] = preg_replace("/e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n/i", "Xexpression", $arreach['value']);
  1454. $arreach['value'] = preg_replace("/b\s*i\s*n\s*d\s*i\s*n\s*g/i", "Xbinding", $arreach['value']);
  1455. } else if ($arreach['name'] == 'href') {
  1456. //Adobe Acrobat Reader XSS protection
  1457. $arreach['value'] = preg_replace('/(\.(pdf|fdf|xfdf|xdp|xfd)[^#]*)#.*$/i', '$1', $arreach['value']);
  1458. }
  1459. $attStr .= ' '.$arreach['name'].'="'.$arreach['value'].'"';
  1460. }
  1461. $xhtml_slash = '';
  1462. if (preg_match('%/\s*$%', $attrlist)) {
  1463. $xhtml_slash = ' /';
  1464. }
  1465. return '<'. $slash . $elem . $attStr . $xhtml_slash .'>';
  1466. }
  1467. /**
  1468. * Given plain text, makes it into HTML as nicely as possible.
  1469. * May contain HTML tags already
  1470. *
  1471. * Do not abuse this function. It is intended as lower level formatting feature used
  1472. * by {@see format_text()} to convert FORMAT_MOODLE to HTML. You are supposed
  1473. * to call format_text() in most of cases.
  1474. *
  1475. * @global object
  1476. * @param string $text The string to convert.
  1477. * @param boolean $smiley_ignored Was used to determine if smiley characters should convert to smiley images, ignored now
  1478. * @param boolean $para If true then the returned string will be wrapped in div tags
  1479. * @param boolean $newlines If true then lines newline breaks will be converted to HTML newline breaks.
  1480. * @return string
  1481. */
  1482. function text_to_html($text, $smiley_ignored=null, $para=true, $newlines=true) {
  1483. global $CFG;
  1484. /// Remove any whitespace that may be between HTML tags
  1485. $text = preg_replace("~>([[:space:]]+)<~i", "><", $text);
  1486. /// Remove any returns that precede or follow HTML tags
  1487. $text = preg_replace("~([\n\r])<~i", " <", $text);
  1488. $text = preg_replace("~>([\n\r])~i", "> ", $text);
  1489. /// Make returns into HTML newlines.
  1490. if ($newlines) {
  1491. $text = nl2br($text);
  1492. }
  1493. /// Wrap the whole thing in a div if required
  1494. if ($para) {
  1495. //return '<p>'.$text.'</p>'; //1.9 version
  1496. return '<div class="text_to_html">'.$text.'</div>';
  1497. } else {
  1498. return $text;
  1499. }
  1500. }
  1501. /**
  1502. * Given Markdown formatted text, make it into XHTML using external function
  1503. *
  1504. * @global object
  1505. * @param string $text The markdown formatted text to be converted.
  1506. * @return string Converted text
  1507. */
  1508. function markdown_to_html($text) {
  1509. global $CFG;
  1510. if ($text === '' or $text === NULL) {
  1511. return $text;
  1512. }
  1513. require_once($CFG->libdir .'/markdown.php');
  1514. return Markdown($text);
  1515. }
  1516. /**
  1517. * Given HTML text, make it into plain text using external function
  1518. *
  1519. * @param string $html The text to be converted.
  1520. * @param integer $width Width to wrap the text at. (optional, default 75 which
  1521. * is a good value for email. 0 means do not limit line length.)
  1522. * @param boolean $dolinks By default, any links in the HTML are collected, and
  1523. * printed as a list at the end of the HTML. If you don't want that, set this
  1524. * argument to false.
  1525. * @return string plain text equivalent of the HTML.
  1526. */
  1527. function html_to_text($html, $width = 75, $dolinks = true) {
  1528. global $CFG;
  1529. require_once($CFG->libdir .'/html2text.php');
  1530. $h2t = new html2text($html, false, $dolinks, $width);
  1531. $result = $h2t->get_text();
  1532. return $result;
  1533. }
  1534. /**
  1535. * This function will highlight search words in a given string
  1536. *
  1537. * It cares about HTML and will not ruin links. It's best to use
  1538. * this function after performing any conversions to HTML.
  1539. *
  1540. * @param string $needle The search string. Syntax like "word1 +word2 -word3" is dealt with correctly.
  1541. * @param string $haystack The string (HTML) within which to highlight the search terms.
  1542. * @param boolean $matchcase whether to do case-sensitive. Default case-insensitive.
  1543. * @param string $prefix the string to put before each search term found.
  1544. * @param string $suffix the string to put after each search term found.
  1545. * @return string The highlighted HTML.
  1546. */
  1547. function highlight($needle, $haystack, $matchcase = false,
  1548. $prefix = '<span class="highlight">', $suffix = '</span>') {
  1549. /// Quick bail-out in trivial cases.
  1550. if (empty($needle) or empty($haystack)) {
  1551. return $haystack;
  1552. }
  1553. /// Break up the search term into words, discard any -words and build a regexp.
  1554. $words = preg_split('/ +/', trim($needle));
  1555. foreach ($words as $index => $word) {
  1556. if (strpos($word, '-') === 0) {
  1557. unset($words[$index]);
  1558. } else if (strpos($word, '+') === 0) {
  1559. $words[$index] = '\b' . preg_quote(ltrim($word, '+'), '/') . '\b'; // Match only as a complete word.
  1560. } else {
  1561. $words[$index] = preg_quote($word, '/');
  1562. }
  1563. }
  1564. $regexp = '/(' . implode('|', $words) . ')/u'; // u is do UTF-8 matching.
  1565. if (!$matchcase) {
  1566. $regexp .= 'i';
  1567. }
  1568. /// Another chance to bail-out if $search was only -words
  1569. if (empty($words)) {
  1570. return $haystack;
  1571. }
  1572. /// Find all the HTML tags in the input, and store them in a placeholders array.
  1573. $placeholders = array();
  1574. $matches = array();
  1575. preg_match_all('/<[^>]*>/', $haystack, $matches);
  1576. foreach (array_unique($matches[0]) as $key => $htmltag) {
  1577. $placeholders['<|' . $key . '|>'] = $htmltag;
  1578. }
  1579. /// In $hastack, replace each HTML tag with the corresponding placeholder.
  1580. $haystack = str_replace($placeholders, array_keys($placeholders), $haystack);
  1581. /// In the resulting string, Do the highlighting.
  1582. $haystack = preg_replace($regexp, $prefix . '$1' . $suffix, $haystack);
  1583. /// Turn the placeholders back into HTML tags.
  1584. $haystack = str_replace(array_keys($placeholders), $placeholders, $haystack);
  1585. return $haystack;
  1586. }
  1587. /**
  1588. * This function will highlight instances of $needle in $haystack
  1589. *
  1590. * It's faster that the above function {@link highlight()} and doesn't care about
  1591. * HTML or anything.
  1592. *
  1593. * @param string $needle The string to search for
  1594. * @param string $haystack The string to search for $needle in
  1595. * @return string The highlighted HTML
  1596. */
  1597. function highlightfast($needle, $haystack) {
  1598. if (empty($needle) or empty($haystack)) {
  1599. return $haystack;
  1600. }
  1601. $parts = explode(moodle_strtolower($needle), moodle_strtolower($haystack));
  1602. if (count($parts) === 1) {
  1603. return $haystack;
  1604. }
  1605. $pos = 0;
  1606. foreach ($parts as $key => $part) {
  1607. $parts[$key] = substr($haystack, $pos, strlen($part));
  1608. $pos += strlen($part);
  1609. $parts[$key] .= '<span class="highlight">'.substr($haystack, $pos, strlen($needle)).'</span>';
  1610. $pos += strlen($needle);
  1611. }
  1612. return str_replace('<span class="highlight"></span>', '', join('', $parts));
  1613. }
  1614. /**
  1615. * Return a string containing 'lang', xml:lang and optionally 'dir' HTML attributes.
  1616. * Internationalisation, for print_header and backup/restorelib.
  1617. *
  1618. * @param bool $dir Default false
  1619. * @return string Attributes
  1620. */
  1621. function get_html_lang($dir = false) {
  1622. $direction = '';
  1623. if ($dir) {
  1624. if (right_to_left()) {
  1625. $direction = ' dir="rtl"';
  1626. } else {
  1627. $direction = ' dir="ltr"';
  1628. }
  1629. }
  1630. //Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag.
  1631. $language = str_replace('_', '-', current_language());
  1632. @header('Content-Language: '.$language);
  1633. return ($direction.' lang="'.$language.'" xml:lang="'.$language.'"');
  1634. }
  1635. /// STANDARD WEB PAGE PARTS ///////////////////////////////////////////////////
  1636. /**
  1637. * Send the HTTP headers that Moodle requires.
  1638. * @param $cacheable Can this page be cached on back?
  1639. */
  1640. function send_headers($contenttype, $cacheable = true) {
  1641. @header('Content-Type: ' . $contenttype);
  1642. @header('Content-Script-Type: text/javascript');
  1643. @header('Content-Style-Type: text/css');
  1644. if ($cacheable) {
  1645. // Allow caching on "back" (but not on normal clicks)
  1646. @header('Cache-Control: private, pre-check=0, post-check=0, max-age=0');
  1647. @header('Pragma: no-cache');
  1648. @header('Expires: ');
  1649. } else {
  1650. // Do everything we can to always prevent clients and proxies caching
  1651. @header('Cache-Control: no-store, no-cache, must-revalidate');
  1652. @header('Cache-Control: post-check=0, pre-check=0', false);
  1653. @header('Pragma: no-cache');
  1654. @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
  1655. @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
  1656. }
  1657. @header('Accept-Ranges: none');
  1658. }
  1659. /**
  1660. * Return the right arrow with text ('next'), and optionally embedded in a link.
  1661. *
  1662. * @global object
  1663. * @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases).
  1664. * @param string $url An optional link to use in a surrounding HTML anchor.
  1665. * @param bool $accesshide True if text should be hidden (for screen readers only).
  1666. * @param string $addclass Additional class names for the link, or the arrow character.
  1667. * @return string HTML string.
  1668. */
  1669. function link_arrow_right($text, $url='', $accesshide=false, $addclass='') {
  1670. global $OUTPUT; //TODO: move to output renderer
  1671. $arrowclass = 'arrow ';
  1672. if (! $url) {
  1673. $arrowclass .= $addclass;
  1674. }
  1675. $arrow = '<span class="'.$arrowclass.'">'.$OUTPUT->rarrow().'</span>';
  1676. $htmltext = '';
  1677. if ($text) {
  1678. $htmltext = '<span class="arrow_text">'.$text.'</span>&nbsp;';
  1679. if ($accesshide) {
  1680. $htmltext = get_accesshide($htmltext);
  1681. }
  1682. }
  1683. if ($url) {
  1684. $class = 'arrow_link';
  1685. if ($addclass) {
  1686. $class .= ' '.$addclass;
  1687. }
  1688. return '<a class="'.$class.'" href="'.$url.'" title="'.preg_replace('/<.*?>/','',$text).'">'.$htmltext.$arrow.'</a>';
  1689. }
  1690. return $htmltext.$arrow;
  1691. }
  1692. /**
  1693. * Return the left arrow with text ('previous'), and optionally embedded in a link.
  1694. *
  1695. * @global object
  1696. * @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases).
  1697. * @param string $url An optional link to use in a surrounding HTML anchor.
  1698. * @param bool $accesshide True if text should be hidden (for screen readers only).
  1699. * @param string $addclass Additional class names for the link, or the arrow character.
  1700. * @return string HTML string.
  1701. */
  1702. function link_arrow_left($text, $url='', $accesshide=false, $addclass='') {
  1703. global $OUTPUT; // TODO: move to utput renderer
  1704. $arrowclass = 'arrow ';
  1705. if (! $url) {
  1706. $arrowclass .= $addclass;
  1707. }
  1708. $arrow = '<span class="'.$arrowclass.'">'.$OUTPUT->larrow().'</span>';
  1709. $htmltext = '';
  1710. if ($text) {
  1711. $htmltext = '&nbsp;<span class="arrow_text">'.$text.'</span>';
  1712. if ($accesshide) {
  1713. $htmltext = get_accesshide($htmltext);
  1714. }
  1715. }
  1716. if ($url) {
  1717. $class = 'arrow_link';
  1718. if ($addclass) {
  1719. $class .= ' '.$addclass;
  1720. }
  1721. return '<a class="'.$class.'" href="'.$url.'" title="'.preg_replace('/<.*?>/','',$text).'">'.$arrow.$htmltext.'</a>';
  1722. }
  1723. return $arrow.$htmltext;
  1724. }
  1725. /**
  1726. * Return a HTML element with the class "accesshide", for accessibility.
  1727. * Please use cautiously - where possible, text should be visible!
  1728. *
  1729. * @param string $text Plain text.
  1730. * @param string $elem Lowercase element name, default "span".
  1731. * @param string $class Additional classes for the element.
  1732. * @param string $attrs Additional attributes string in the form, "name='value' name2='value2'"
  1733. * @return string HTML string.
  1734. */
  1735. function get_accesshide($text, $elem='span', $class='', $attrs='') {
  1736. return "<$elem class=\"accesshide $class\" $attrs>$text</$elem>";
  1737. }
  1738. /**
  1739. * Return the breadcrumb trail navigation separator.
  1740. *
  1741. * @return string HTML string.
  1742. */
  1743. function get_separator() {
  1744. //Accessibility: the 'hidden' slash is preferred for screen readers.
  1745. return ' '.link_arrow_right($text='/', $url='', $accesshide=true, 'sep').' ';
  1746. }
  1747. /**
  1748. * Print (or return) a collapsible region, that has a caption that can
  1749. * be clicked to expand or collapse the region.
  1750. *
  1751. * If JavaScript is off, then the region will always be expanded.
  1752. *
  1753. * @param string $contents the contents of the box.
  1754. * @param string $classes class names added to the div that is output.
  1755. * @param string $id id added to the div that is output. Must not be blank.
  1756. * @param string $caption text displayed at the top. Clicking on this will cause the region to expand or contract.
  1757. * @param string $userpref the name of the user preference that stores the user's preferred default state.
  1758. * (May be blank if you do not wish the state to be persisted.
  1759. * @param boolean $default Initial collapsed state to use if the user_preference it not set.
  1760. * @param boolean $return if true, return the HTML as a string, rather than printing it.
  1761. * @return string|void If $return is false, returns nothing, otherwise returns a string of HTML.
  1762. */
  1763. function print_collapsible_region($contents, $classes, $id, $caption, $userpref = '', $default = false, $return = false) {
  1764. $output = print_collapsible_region_start($classes, $id, $caption, $userpref, $default, true);
  1765. $output .= $contents;
  1766. $output .= print_collapsible_region_end(true);
  1767. if ($return) {
  1768. return $output;
  1769. } else {
  1770. echo $output;
  1771. }
  1772. }
  1773. /**
  1774. * Print (or return) the start of a collapsible region, that has a caption that can
  1775. * be clicked to expand or collapse the region. If JavaScript is off, then the region
  1776. * will always be expanded.
  1777. *
  1778. * @param string $classes class names added to the div that is output.
  1779. * @param string $id id added to the div that is output. Must not be blank.
  1780. * @param string $caption text displayed at the top. Clicking on this will cause the region to expand or contract.
  1781. * @param string $userpref the name of the user preference that stores the user's preferred default state.
  1782. * (May be blank if you do not wish the state to be persisted.
  1783. * @param boolean $default Initial collapsed state to use if the user_preference it not set.
  1784. * @param boolean $return if true, return the HTML as a string, rather than printing it.
  1785. * @return string|void if $return is false, returns nothing, otherwise returns a string of HTML.
  1786. */
  1787. function print_collapsible_region_start($classes, $id, $caption, $userpref = '', $default = false, $return = false) {
  1788. global $CFG, $PAGE, $OUTPUT;
  1789. // Work out the initial state.
  1790. if (!empty($userpref) and is_string($userpref)) {
  1791. user_preference_allow_ajax_update($userpref, PARAM_BOOL);
  1792. $collapsed = get_user_preferences($userpref, $default);
  1793. } else {
  1794. $collapsed = $default;
  1795. $userpref = false;
  1796. }
  1797. if ($collapsed) {
  1798. $classes .= ' collapsed';
  1799. }
  1800. $output = '';
  1801. $output .= '<div id="' . $id . '" class="collapsibleregion ' . $classes . '">';
  1802. $output .= '<div id="' . $id . '_sizer">';
  1803. $output .= '<div id="' . $id . '_caption" class="collapsibleregioncaption">';
  1804. $output .= $caption . ' ';
  1805. $output .= '</div><div id="' . $id . '_inner" class="collapsibleregioninner">';
  1806. $PAGE->requires->js_init_call('M.util.init_collapsible_region', array($id, $userpref, get_string('clicktohideshow')));
  1807. if ($return) {
  1808. return $output;
  1809. } else {
  1810. echo $output;
  1811. }
  1812. }
  1813. /**
  1814. * Close a region started with print_collapsible_region_start.
  1815. *
  1816. * @param boolean $return if true, return the HTML as a string, rather than printing it.
  1817. * @return string|void if $return is false, returns nothing, otherwise returns a string of HTML.
  1818. */
  1819. function print_collapsible_region_end($return = false) {
  1820. $output = '</div></div></div>';
  1821. if ($return) {
  1822. return $output;
  1823. } else {
  1824. echo $output;
  1825. }
  1826. }
  1827. /**
  1828. * Print a specified group's avatar.
  1829. *
  1830. * @global object
  1831. * @uses CONTEXT_COURSE
  1832. * @param array|stdClass $group A single {@link group} object OR array of groups.
  1833. * @param int $courseid The course ID.
  1834. * @param boolean $large Default small picture, or large.
  1835. * @param boolean $return If false print picture, otherwise return the output as string
  1836. * @param boolean $link Enclose image in a link to view specified course?
  1837. * @return string|void Depending on the setting of $return
  1838. */
  1839. function print_group_picture($group, $courseid, $large=false, $return=false, $link=true) {
  1840. global $CFG;
  1841. if (is_array($group)) {
  1842. $output = '';
  1843. foreach($group as $g) {
  1844. $output .= print_group_picture($g, $courseid, $large, true, $link);
  1845. }
  1846. if ($return) {
  1847. return $output;
  1848. } else {
  1849. echo $output;
  1850. return;
  1851. }
  1852. }
  1853. $context = get_context_instance(CONTEXT_COURSE, $courseid);
  1854. // If there is no picture, do nothing
  1855. if (!$group->picture) {
  1856. return '';
  1857. }
  1858. // If picture is hidden, only show to those with course:managegroups
  1859. if ($group->hidepicture and !has_capability('moodle/course:managegroups', $context)) {
  1860. return '';
  1861. }
  1862. if ($link or has_capability('moodle/site:accessallgroups', $context)) {
  1863. $output = '<a href="'. $CFG->wwwroot .'/user/index.php?id='. $courseid .'&amp;group='. $group->id .'">';
  1864. } else {
  1865. $output = '';
  1866. }
  1867. if ($large) {
  1868. $file = 'f1';
  1869. } else {
  1870. $file = 'f2';
  1871. }
  1872. $grouppictureurl = moodle_url::make_pluginfile_url($context->id, 'group', 'icon', $group->id, '/', $file);
  1873. $output .= '<img class="grouppicture" src="'.$grouppictureurl.'"'.
  1874. ' alt="'.s(get_string('group').' '.$group->name).'" title="'.s($group->name).'"/>';
  1875. if ($link or has_capability('moodle/site:accessallgroups', $context)) {
  1876. $output .= '</a>';
  1877. }
  1878. if ($return) {
  1879. return $output;
  1880. } else {
  1881. echo $output;
  1882. }
  1883. }
  1884. /**
  1885. * Display a recent activity note
  1886. *
  1887. * @uses CONTEXT_SYSTEM
  1888. * @staticvar string $strftimerecent
  1889. * @param object A time object
  1890. * @param object A user object
  1891. * @param string $text Text for display for the note
  1892. * @param string $link The link to wrap around the text
  1893. * @param bool $return If set to true the HTML is returned rather than echo'd
  1894. * @param string $viewfullnames
  1895. */
  1896. function print_recent_activity_note($time, $user, $text, $link, $return=false, $viewfullnames=null) {
  1897. static $strftimerecent = null;
  1898. $output = '';
  1899. if (is_null($viewfullnames)) {
  1900. $context = get_context_instance(CONTEXT_SYSTEM);
  1901. $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
  1902. }
  1903. if (is_null($strftimerecent)) {
  1904. $strftimerecent = get_string('strftimerecent');
  1905. }
  1906. $output .= '<div class="head">';
  1907. $output .= '<div class="date">'.userdate($time, $strftimerecent).'</div>';
  1908. $output .= '<div class="name">'.fullname($user, $viewfullnames).'</div>';
  1909. $output .= '</div>';
  1910. $output .= '<div class="info"><a href="'.$link.'">'.format_string($text,true).'</a></div>';
  1911. if ($return) {
  1912. return $output;
  1913. } else {
  1914. echo $output;
  1915. }
  1916. }
  1917. /**
  1918. * Returns a popup menu with course activity modules
  1919. *
  1920. * Given a course
  1921. * This function returns a small popup menu with all the
  1922. * course activity modules in it, as a navigation menu
  1923. * outputs a simple list structure in XHTML
  1924. * The data is taken from the serialised array stored in
  1925. * the course record
  1926. *
  1927. * @todo Finish documenting this function
  1928. *
  1929. * @global object
  1930. * @uses CONTEXT_COURSE
  1931. * @param course $course A {@link $COURSE} object.
  1932. * @param string $sections
  1933. * @param string $modinfo
  1934. * @param string $strsection
  1935. * @param string $strjumpto
  1936. * @param int $width
  1937. * @param string $cmid
  1938. * @return string The HTML block
  1939. */
  1940. function navmenulist($course, $sections, $modinfo, $strsection, $strjumpto, $width=50, $cmid=0) {
  1941. global $CFG, $OUTPUT;
  1942. $section = -1;
  1943. $url = '';
  1944. $menu = array();
  1945. $doneheading = false;
  1946. $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
  1947. $menu[] = '<ul class="navmenulist"><li class="jumpto section"><span>'.$strjumpto.'</span><ul>';
  1948. foreach ($modinfo->cms as $mod) {
  1949. if (!$mod->has_view()) {
  1950. // Don't show modules which you can't link to!
  1951. continue;
  1952. }
  1953. if ($mod->sectionnum > $course->numsections) { /// Don't show excess hidden sections
  1954. break;
  1955. }
  1956. if (!$mod->uservisible) { // do not icnlude empty sections at all
  1957. continue;
  1958. }
  1959. if ($mod->sectionnum >= 0 and $section != $mod->sectionnum) {
  1960. $thissection = $sections[$mod->sectionnum];
  1961. if ($thissection->visible or !$course->hiddensections or
  1962. has_capability('moodle/course:viewhiddensections', $coursecontext)) {
  1963. $thissection->summary = strip_tags(format_string($thissection->summary,true));
  1964. if (!$doneheading) {
  1965. $menu[] = '</ul></li>';
  1966. }
  1967. if ($course->format == 'weeks' or empty($thissection->summary)) {
  1968. $item = $strsection ." ". $mod->sectionnum;
  1969. } else {
  1970. if (strlen($thissection->summary) < ($width-3)) {
  1971. $item = $thissection->summary;
  1972. } else {
  1973. $item = substr($thissection->summary, 0, $width).'...';
  1974. }
  1975. }
  1976. $menu[] = '<li class="section"><span>'.$item.'</span>';
  1977. $menu[] = '<ul>';
  1978. $doneheading = true;
  1979. $section = $mod->sectionnum;
  1980. } else {
  1981. // no activities from this hidden section shown
  1982. continue;
  1983. }
  1984. }
  1985. $url = $mod->modname .'/view.php?id='. $mod->id;
  1986. $mod->name = strip_tags(format_string($mod->name ,true));
  1987. if (strlen($mod->name) > ($width+5)) {
  1988. $mod->name = substr($mod->name, 0, $width).'...';
  1989. }
  1990. if (!$mod->visible) {
  1991. $mod->name = '('.$mod->name.')';
  1992. }
  1993. $class = 'activity '.$mod->modname;
  1994. $class .= ($cmid == $mod->id) ? ' selected' : '';
  1995. $menu[] = '<li class="'.$class.'">'.
  1996. '<img src="'.$OUTPUT->pix_url('icon', $mod->modname) . '" alt="" />'.
  1997. '<a href="'.$CFG->wwwroot.'/mod/'.$url.'">'.$mod->name.'</a></li>';
  1998. }
  1999. if ($doneheading) {
  2000. $menu[] = '</ul></li>';
  2001. }
  2002. $menu[] = '</ul></li></ul>';
  2003. return implode("\n", $menu);
  2004. }
  2005. /**
  2006. * Prints a grade menu (as part of an existing form) with help
  2007. * Showing all possible numerical grades and scales
  2008. *
  2009. * @todo Finish documenting this function
  2010. * @todo Deprecate: this is only used in a few contrib modules
  2011. *
  2012. * @global object
  2013. * @param int $courseid The course ID
  2014. * @param string $name
  2015. * @param string $current
  2016. * @param boolean $includenograde Include those with no grades
  2017. * @param boolean $return If set to true returns rather than echo's
  2018. * @return string|bool Depending on value of $return
  2019. */
  2020. function print_grade_menu($courseid, $name, $current, $includenograde=true, $return=false) {
  2021. global $CFG, $OUTPUT;
  2022. $output = '';
  2023. $strscale = get_string('scale');
  2024. $strscales = get_string('scales');
  2025. $scales = get_scales_menu($courseid);
  2026. foreach ($scales as $i => $scalename) {
  2027. $grades[-$i] = $strscale .': '. $scalename;
  2028. }
  2029. if ($includenograde) {
  2030. $grades[0] = get_string('nograde');
  2031. }
  2032. for ($i=100; $i>=1; $i--) {
  2033. $grades[$i] = $i;
  2034. }
  2035. $output .= html_writer::select($grades, $name, $current, false);
  2036. $linkobject = '<span class="helplink"><img class="iconhelp" alt="'.$strscales.'" src="'.$OUTPUT->pix_url('help') . '" /></span>';
  2037. $link = new moodle_url('/course/scales.php', array('id'=>$courseid, 'list'=>1));
  2038. $action = new popup_action('click', $link, 'ratingscales', array('height' => 400, 'width' => 500));
  2039. $output .= $OUTPUT->action_link($link, $linkobject, $action, array('title'=>$strscales));
  2040. if ($return) {
  2041. return $output;
  2042. } else {
  2043. echo $output;
  2044. }
  2045. }
  2046. /**
  2047. * Print an error to STDOUT and exit with a non-zero code. For commandline scripts.
  2048. * Default errorcode is 1.
  2049. *
  2050. * Very useful for perl-like error-handling:
  2051. *
  2052. * do_somethting() or mdie("Something went wrong");
  2053. *
  2054. * @param string $msg Error message
  2055. * @param integer $errorcode Error code to emit
  2056. */
  2057. function mdie($msg='', $errorcode=1) {
  2058. trigger_error($msg);
  2059. exit($errorcode);
  2060. }
  2061. /**
  2062. * Print a message and exit.
  2063. *
  2064. * @param string $message The message to print in the notice
  2065. * @param string $link The link to use for the continue button
  2066. * @param object $course A course object
  2067. * @return void This function simply exits
  2068. */
  2069. function notice ($message, $link='', $course=NULL) {
  2070. global $CFG, $SITE, $COURSE, $PAGE, $OUTPUT;
  2071. $message = clean_text($message); // In case nasties are in here
  2072. if (CLI_SCRIPT) {
  2073. echo("!!$message!!\n");
  2074. exit(1); // no success
  2075. }
  2076. if (!$PAGE->headerprinted) {
  2077. //header not yet printed
  2078. $PAGE->set_title(get_string('notice'));
  2079. echo $OUTPUT->header();
  2080. } else {
  2081. echo $OUTPUT->container_end_all(false);
  2082. }
  2083. echo $OUTPUT->box($message, 'generalbox', 'notice');
  2084. echo $OUTPUT->continue_button($link);
  2085. echo $OUTPUT->footer();
  2086. exit(1); // general error code
  2087. }
  2088. /**
  2089. * Redirects the user to another page, after printing a notice
  2090. *
  2091. * This function calls the OUTPUT redirect method, echo's the output
  2092. * and then dies to ensure nothing else happens.
  2093. *
  2094. * <strong>Good practice:</strong> You should call this method before starting page
  2095. * output by using any of the OUTPUT methods.
  2096. *
  2097. * @param moodle_url|string $url A moodle_url to redirect to. Strings are not to be trusted!
  2098. * @param string $message The message to display to the user
  2099. * @param int $delay The delay before redirecting
  2100. * @return void - does not return!
  2101. */
  2102. function redirect($url, $message='', $delay=-1) {
  2103. global $OUTPUT, $PAGE, $SESSION, $CFG;
  2104. if (CLI_SCRIPT or AJAX_SCRIPT) {
  2105. // this is wrong - developers should not use redirect in these scripts,
  2106. // but it should not be very likely
  2107. throw new moodle_exception('redirecterrordetected', 'error');
  2108. }
  2109. // prevent debug errors - make sure context is properly initialised
  2110. if ($PAGE) {
  2111. $PAGE->set_context(null);
  2112. }
  2113. if ($url instanceof moodle_url) {
  2114. $url = $url->out(false);
  2115. }
  2116. if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) {
  2117. $url = $SESSION->sid_process_url($url);
  2118. }
  2119. $debugdisableredirect = false;
  2120. do {
  2121. if (defined('DEBUGGING_PRINTED')) {
  2122. // some debugging already printed, no need to look more
  2123. $debugdisableredirect = true;
  2124. break;
  2125. }
  2126. if (empty($CFG->debugdisplay) or empty($CFG->debug)) {
  2127. // no errors should be displayed
  2128. break;
  2129. }
  2130. if (!function_exists('error_get_last') or !$lasterror = error_get_last()) {
  2131. break;
  2132. }
  2133. if (!($lasterror['type'] & $CFG->debug)) {
  2134. //last error not interesting
  2135. break;
  2136. }
  2137. // watch out here, @hidden() errors are returned from error_get_last() too
  2138. if (headers_sent()) {
  2139. //we already started printing something - that means errors likely printed
  2140. $debugdisableredirect = true;
  2141. break;
  2142. }
  2143. if (ob_get_level() and ob_get_contents()) {
  2144. // there is something waiting to be printed, hopefully it is the errors,
  2145. // but it might be some error hidden by @ too - such as the timezone mess from setup.php
  2146. $debugdisableredirect = true;
  2147. break;
  2148. }
  2149. } while (false);
  2150. if (!empty($message)) {
  2151. if ($delay === -1 || !is_numeric($delay)) {
  2152. $delay = 3;
  2153. }
  2154. $message = clean_text($message);
  2155. } else {
  2156. $message = get_string('pageshouldredirect');
  2157. $delay = 0;
  2158. // We are going to try to use a HTTP redirect, so we need a full URL.
  2159. if (!preg_match('|^[a-z]+:|', $url)) {
  2160. // Get host name http://www.wherever.com
  2161. $hostpart = preg_replace('|^(.*?[^:/])/.*$|', '$1', $CFG->wwwroot);
  2162. if (preg_match('|^/|', $url)) {
  2163. // URLs beginning with / are relative to web server root so we just add them in
  2164. $url = $hostpart.$url;
  2165. } else {
  2166. // URLs not beginning with / are relative to path of current script, so add that on.
  2167. $url = $hostpart.preg_replace('|\?.*$|','',me()).'/../'.$url;
  2168. }
  2169. // Replace all ..s
  2170. while (true) {
  2171. $newurl = preg_replace('|/(?!\.\.)[^/]*/\.\./|', '/', $url);
  2172. if ($newurl == $url) {
  2173. break;
  2174. }
  2175. $url = $newurl;
  2176. }
  2177. }
  2178. }
  2179. if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
  2180. if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
  2181. $perf = get_performance_info();
  2182. error_log("PERF: " . $perf['txt']);
  2183. }
  2184. }
  2185. $encodedurl = preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&amp;", $url);
  2186. $encodedurl = preg_replace('/^.*href="([^"]*)".*$/', "\\1", clean_text('<a href="'.$encodedurl.'" />'));
  2187. if ($delay == 0 && !$debugdisableredirect && !headers_sent()) {
  2188. // workaround for IIS bug http://support.microsoft.com/kb/q176113/
  2189. if (session_id()) {
  2190. session_get_instance()->write_close();
  2191. }
  2192. //302 might not work for POST requests, 303 is ignored by obsolete clients.
  2193. @header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other');
  2194. @header('Location: '.$url);
  2195. echo bootstrap_renderer::plain_redirect_message($encodedurl);
  2196. exit;
  2197. }
  2198. // Include a redirect message, even with a HTTP redirect, because that is recommended practice.
  2199. $PAGE->set_pagelayout('redirect'); // No header and footer needed
  2200. $CFG->docroot = false; // to prevent the link to moodle docs from being displayed on redirect page.
  2201. echo $OUTPUT->redirect_message($encodedurl, $message, $delay, $debugdisableredirect);
  2202. exit;
  2203. }
  2204. /**
  2205. * Given an email address, this function will return an obfuscated version of it
  2206. *
  2207. * @param string $email The email address to obfuscate
  2208. * @return string The obfuscated email address
  2209. */
  2210. function obfuscate_email($email) {
  2211. $i = 0;
  2212. $length = strlen($email);
  2213. $obfuscated = '';
  2214. while ($i < $length) {
  2215. if (rand(0,2) && $email{$i}!='@') { //MDL-20619 some browsers have problems unobfuscating @
  2216. $obfuscated.='%'.dechex(ord($email{$i}));
  2217. } else {
  2218. $obfuscated.=$email{$i};
  2219. }
  2220. $i++;
  2221. }
  2222. return $obfuscated;
  2223. }
  2224. /**
  2225. * This function takes some text and replaces about half of the characters
  2226. * with HTML entity equivalents. Return string is obviously longer.
  2227. *
  2228. * @param string $plaintext The text to be obfuscated
  2229. * @return string The obfuscated text
  2230. */
  2231. function obfuscate_text($plaintext) {
  2232. $i=0;
  2233. $length = strlen($plaintext);
  2234. $obfuscated='';
  2235. $prev_obfuscated = false;
  2236. while ($i < $length) {
  2237. $c = ord($plaintext{$i});
  2238. $numerical = ($c >= ord('0')) && ($c <= ord('9'));
  2239. if ($prev_obfuscated and $numerical ) {
  2240. $obfuscated.='&#'.ord($plaintext{$i}).';';
  2241. } else if (rand(0,2)) {
  2242. $obfuscated.='&#'.ord($plaintext{$i}).';';
  2243. $prev_obfuscated = true;
  2244. } else {
  2245. $obfuscated.=$plaintext{$i};
  2246. $prev_obfuscated = false;
  2247. }
  2248. $i++;
  2249. }
  2250. return $obfuscated;
  2251. }
  2252. /**
  2253. * This function uses the {@link obfuscate_email()} and {@link obfuscate_text()}
  2254. * to generate a fully obfuscated email link, ready to use.
  2255. *
  2256. * @param string $email The email address to display
  2257. * @param string $label The text to displayed as hyperlink to $email
  2258. * @param boolean $dimmed If true then use css class 'dimmed' for hyperlink
  2259. * @return string The obfuscated mailto link
  2260. */
  2261. function obfuscate_mailto($email, $label='', $dimmed=false) {
  2262. if (empty($label)) {
  2263. $label = $email;
  2264. }
  2265. if ($dimmed) {
  2266. $title = get_string('emaildisable');
  2267. $dimmed = ' class="dimmed"';
  2268. } else {
  2269. $title = '';
  2270. $dimmed = '';
  2271. }
  2272. return sprintf("<a href=\"%s:%s\" $dimmed title=\"$title\">%s</a>",
  2273. obfuscate_text('mailto'), obfuscate_email($email),
  2274. obfuscate_text($label));
  2275. }
  2276. /**
  2277. * This function is used to rebuild the <nolink> tag because some formats (PLAIN and WIKI)
  2278. * will transform it to html entities
  2279. *
  2280. * @param string $text Text to search for nolink tag in
  2281. * @return string
  2282. */
  2283. function rebuildnolinktag($text) {
  2284. $text = preg_replace('/&lt;(\/*nolink)&gt;/i','<$1>',$text);
  2285. return $text;
  2286. }
  2287. /**
  2288. * Prints a maintenance message from $CFG->maintenance_message or default if empty
  2289. * @return void
  2290. */
  2291. function print_maintenance_message() {
  2292. global $CFG, $SITE, $PAGE, $OUTPUT;
  2293. $PAGE->set_pagetype('maintenance-message');
  2294. $PAGE->set_pagelayout('maintenance');
  2295. $PAGE->set_title(strip_tags($SITE->fullname));
  2296. $PAGE->set_heading($SITE->fullname);
  2297. echo $OUTPUT->header();
  2298. echo $OUTPUT->heading(get_string('sitemaintenance', 'admin'));
  2299. if (isset($CFG->maintenance_message) and !html_is_blank($CFG->maintenance_message)) {
  2300. echo $OUTPUT->box_start('maintenance_message generalbox boxwidthwide boxaligncenter');
  2301. echo $CFG->maintenance_message;
  2302. echo $OUTPUT->box_end();
  2303. }
  2304. echo $OUTPUT->footer();
  2305. die;
  2306. }
  2307. /**
  2308. * Adjust the list of allowed tags based on $CFG->allowobjectembed and user roles (admin)
  2309. *
  2310. * @global object
  2311. * @global string
  2312. * @return void
  2313. */
  2314. function adjust_allowed_tags() {
  2315. global $CFG, $ALLOWED_TAGS;
  2316. if (!empty($CFG->allowobjectembed)) {
  2317. $ALLOWED_TAGS .= '<embed><object>';
  2318. }
  2319. }
  2320. /**
  2321. * A class for tabs, Some code to print tabs
  2322. *
  2323. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2324. * @package moodlecore
  2325. */
  2326. class tabobject {
  2327. /**
  2328. * @var string
  2329. */
  2330. var $id;
  2331. var $link;
  2332. var $text;
  2333. /**
  2334. * @var bool
  2335. */
  2336. var $linkedwhenselected;
  2337. /**
  2338. * A constructor just because I like constructors
  2339. *
  2340. * @param string $id
  2341. * @param string $link
  2342. * @param string $text
  2343. * @param string $title
  2344. * @param bool $linkedwhenselected
  2345. */
  2346. function tabobject ($id, $link='', $text='', $title='', $linkedwhenselected=false) {
  2347. $this->id = $id;
  2348. $this->link = $link;
  2349. $this->text = $text;
  2350. $this->title = $title ? $title : $text;
  2351. $this->linkedwhenselected = $linkedwhenselected;
  2352. }
  2353. }
  2354. /**
  2355. * Returns a string containing a nested list, suitable for formatting into tabs with CSS.
  2356. *
  2357. * @global object
  2358. * @param array $tabrows An array of rows where each row is an array of tab objects
  2359. * @param string $selected The id of the selected tab (whatever row it's on)
  2360. * @param array $inactive An array of ids of inactive tabs that are not selectable.
  2361. * @param array $activated An array of ids of other tabs that are currently activated
  2362. * @param bool $return If true output is returned rather then echo'd
  2363. **/
  2364. function print_tabs($tabrows, $selected=NULL, $inactive=NULL, $activated=NULL, $return=false) {
  2365. global $CFG;
  2366. /// $inactive must be an array
  2367. if (!is_array($inactive)) {
  2368. $inactive = array();
  2369. }
  2370. /// $activated must be an array
  2371. if (!is_array($activated)) {
  2372. $activated = array();
  2373. }
  2374. /// Convert the tab rows into a tree that's easier to process
  2375. if (!$tree = convert_tabrows_to_tree($tabrows, $selected, $inactive, $activated)) {
  2376. return false;
  2377. }
  2378. /// Print out the current tree of tabs (this function is recursive)
  2379. $output = convert_tree_to_html($tree);
  2380. $output = "\n\n".'<div class="tabtree">'.$output.'</div><div class="clearer"> </div>'."\n\n";
  2381. /// We're done!
  2382. if ($return) {
  2383. return $output;
  2384. }
  2385. echo $output;
  2386. }
  2387. /**
  2388. * Converts a nested array tree into HTML ul:li [recursive]
  2389. *
  2390. * @param array $tree A tree array to convert
  2391. * @param int $row Used in identifying the iteration level and in ul classes
  2392. * @return string HTML structure
  2393. */
  2394. function convert_tree_to_html($tree, $row=0) {
  2395. $str = "\n".'<ul class="tabrow'.$row.'">'."\n";
  2396. $first = true;
  2397. $count = count($tree);
  2398. foreach ($tree as $tab) {
  2399. $count--; // countdown to zero
  2400. $liclass = '';
  2401. if ($first && ($count == 0)) { // Just one in the row
  2402. $liclass = 'first last';
  2403. $first = false;
  2404. } else if ($first) {
  2405. $liclass = 'first';
  2406. $first = false;
  2407. } else if ($count == 0) {
  2408. $liclass = 'last';
  2409. }
  2410. if ((empty($tab->subtree)) && (!empty($tab->selected))) {
  2411. $liclass .= (empty($liclass)) ? 'onerow' : ' onerow';
  2412. }
  2413. if ($tab->inactive || $tab->active || $tab->selected) {
  2414. if ($tab->selected) {
  2415. $liclass .= (empty($liclass)) ? 'here selected' : ' here selected';
  2416. } else if ($tab->active) {
  2417. $liclass .= (empty($liclass)) ? 'here active' : ' here active';
  2418. }
  2419. }
  2420. $str .= (!empty($liclass)) ? '<li class="'.$liclass.'">' : '<li>';
  2421. if ($tab->inactive || $tab->active || ($tab->selected && !$tab->linkedwhenselected)) {
  2422. // The a tag is used for styling
  2423. $str .= '<a class="nolink"><span>'.$tab->text.'</span></a>';
  2424. } else {
  2425. $str .= '<a href="'.$tab->link.'" title="'.$tab->title.'"><span>'.$tab->text.'</span></a>';
  2426. }
  2427. if (!empty($tab->subtree)) {
  2428. $str .= convert_tree_to_html($tab->subtree, $row+1);
  2429. } else if ($tab->selected) {
  2430. $str .= '<div class="tabrow'.($row+1).' empty">&nbsp;</div>'."\n";
  2431. }
  2432. $str .= ' </li>'."\n";
  2433. }
  2434. $str .= '</ul>'."\n";
  2435. return $str;
  2436. }
  2437. /**
  2438. * Convert nested tabrows to a nested array
  2439. *
  2440. * @param array $tabrows A [nested] array of tab row objects
  2441. * @param string $selected The tabrow to select (by id)
  2442. * @param array $inactive An array of tabrow id's to make inactive
  2443. * @param array $activated An array of tabrow id's to make active
  2444. * @return array The nested array
  2445. */
  2446. function convert_tabrows_to_tree($tabrows, $selected, $inactive, $activated) {
  2447. /// Work backwards through the rows (bottom to top) collecting the tree as we go.
  2448. $tabrows = array_reverse($tabrows);
  2449. $subtree = array();
  2450. foreach ($tabrows as $row) {
  2451. $tree = array();
  2452. foreach ($row as $tab) {
  2453. $tab->inactive = in_array((string)$tab->id, $inactive);
  2454. $tab->active = in_array((string)$tab->id, $activated);
  2455. $tab->selected = (string)$tab->id == $selected;
  2456. if ($tab->active || $tab->selected) {
  2457. if ($subtree) {
  2458. $tab->subtree = $subtree;
  2459. }
  2460. }
  2461. $tree[] = $tab;
  2462. }
  2463. $subtree = $tree;
  2464. }
  2465. return $subtree;
  2466. }
  2467. /**
  2468. * Returns the Moodle Docs URL in the users language
  2469. *
  2470. * @global object
  2471. * @param string $path the end of the URL.
  2472. * @return string The MoodleDocs URL in the user's language. for example {@link http://docs.moodle.org/en/ http://docs.moodle.org/en/$path}
  2473. */
  2474. function get_docs_url($path) {
  2475. global $CFG;
  2476. // Check that $CFG->release has been set up, during installation it won't be.
  2477. if (empty($CFG->release)) {
  2478. // It's not there yet so look at version.php
  2479. include($CFG->dirroot.'/version.php');
  2480. } else {
  2481. // We can use $CFG->release and avoid having to include version.php
  2482. $release = $CFG->release;
  2483. }
  2484. // Attempt to match the branch from the release
  2485. if (preg_match('/^(.)\.(.)/', $release, $matches)) {
  2486. // We should ALWAYS get here
  2487. $branch = $matches[1].$matches[2];
  2488. } else {
  2489. // We should never get here but in case we do lets set $branch to .
  2490. // the smart one's will know that this is the current directory
  2491. // and the smarter ones will know that there is some smart matching
  2492. // that will ensure people end up at the latest version of the docs.
  2493. $branch = '.';
  2494. }
  2495. if (!empty($CFG->docroot)) {
  2496. return $CFG->docroot . '/' . $branch . '/' . current_language() . '/' . $path;
  2497. } else {
  2498. return 'http://docs.moodle.org/'. $branch . '/en/' . $path;
  2499. }
  2500. }
  2501. /**
  2502. * Standard Debugging Function
  2503. *
  2504. * Returns true if the current site debugging settings are equal or above specified level.
  2505. * If passed a parameter it will emit a debugging notice similar to trigger_error(). The
  2506. * routing of notices is controlled by $CFG->debugdisplay
  2507. * eg use like this:
  2508. *
  2509. * 1) debugging('a normal debug notice');
  2510. * 2) debugging('something really picky', DEBUG_ALL);
  2511. * 3) debugging('annoying debug message only for developers', DEBUG_DEVELOPER);
  2512. * 4) if (debugging()) { perform extra debugging operations (do not use print or echo) }
  2513. *
  2514. * In code blocks controlled by debugging() (such as example 4)
  2515. * any output should be routed via debugging() itself, or the lower-level
  2516. * trigger_error() or error_log(). Using echo or print will break XHTML
  2517. * JS and HTTP headers.
  2518. *
  2519. * It is also possible to define NO_DEBUG_DISPLAY which redirects the message to error_log.
  2520. *
  2521. * @uses DEBUG_NORMAL
  2522. * @param string $message a message to print
  2523. * @param int $level the level at which this debugging statement should show
  2524. * @param array $backtrace use different backtrace
  2525. * @return bool
  2526. */
  2527. function debugging($message = '', $level = DEBUG_NORMAL, $backtrace = null) {
  2528. global $CFG, $USER, $UNITTEST;
  2529. $forcedebug = false;
  2530. if (!empty($CFG->debugusers)) {
  2531. $debugusers = explode(',', $CFG->debugusers);
  2532. $forcedebug = in_array($USER->id, $debugusers);
  2533. }
  2534. if (!$forcedebug and (empty($CFG->debug) || $CFG->debug < $level)) {
  2535. return false;
  2536. }
  2537. if (!isset($CFG->debugdisplay)) {
  2538. $CFG->debugdisplay = ini_get_bool('display_errors');
  2539. }
  2540. if ($message) {
  2541. if (!$backtrace) {
  2542. $backtrace = debug_backtrace();
  2543. }
  2544. $from = format_backtrace($backtrace, CLI_SCRIPT);
  2545. if (!empty($UNITTEST->running)) {
  2546. // When the unit tests are running, any call to trigger_error
  2547. // is intercepted by the test framework and reported as an exception.
  2548. // Therefore, we cannot use trigger_error during unit tests.
  2549. // At the same time I do not think we should just discard those messages,
  2550. // so displaying them on-screen seems like the only option. (MDL-20398)
  2551. echo '<div class="notifytiny">' . $message . $from . '</div>';
  2552. } else if (NO_DEBUG_DISPLAY) {
  2553. // script does not want any errors or debugging in output,
  2554. // we send the info to error log instead
  2555. error_log('Debugging: ' . $message . $from);
  2556. } else if ($forcedebug or $CFG->debugdisplay) {
  2557. if (!defined('DEBUGGING_PRINTED')) {
  2558. define('DEBUGGING_PRINTED', 1); // indicates we have printed something
  2559. }
  2560. if (CLI_SCRIPT) {
  2561. echo "++ $message ++\n$from";
  2562. } else {
  2563. echo '<div class="notifytiny">' . $message . $from . '</div>';
  2564. }
  2565. } else {
  2566. trigger_error($message . $from, E_USER_NOTICE);
  2567. }
  2568. }
  2569. return true;
  2570. }
  2571. /**
  2572. * Outputs a HTML comment to the browser. This is used for those hard-to-debug
  2573. * pages that use bits from many different files in very confusing ways (e.g. blocks).
  2574. *
  2575. * <code>print_location_comment(__FILE__, __LINE__);</code>
  2576. *
  2577. * @param string $file
  2578. * @param integer $line
  2579. * @param boolean $return Whether to return or print the comment
  2580. * @return string|void Void unless true given as third parameter
  2581. */
  2582. function print_location_comment($file, $line, $return = false)
  2583. {
  2584. if ($return) {
  2585. return "<!-- $file at line $line -->\n";
  2586. } else {
  2587. echo "<!-- $file at line $line -->\n";
  2588. }
  2589. }
  2590. /**
  2591. * @return boolean true if the current language is right-to-left (Hebrew, Arabic etc)
  2592. */
  2593. function right_to_left() {
  2594. return (get_string('thisdirection', 'langconfig') === 'rtl');
  2595. }
  2596. /**
  2597. * Returns swapped left<=>right if in RTL environment.
  2598. * part of RTL support
  2599. *
  2600. * @param string $align align to check
  2601. * @return string
  2602. */
  2603. function fix_align_rtl($align) {
  2604. if (!right_to_left()) {
  2605. return $align;
  2606. }
  2607. if ($align=='left') { return 'right'; }
  2608. if ($align=='right') { return 'left'; }
  2609. return $align;
  2610. }
  2611. /**
  2612. * Returns true if the page is displayed in a popup window.
  2613. * Gets the information from the URL parameter inpopup.
  2614. *
  2615. * @todo Use a central function to create the popup calls all over Moodle and
  2616. * In the moment only works with resources and probably questions.
  2617. *
  2618. * @return boolean
  2619. */
  2620. function is_in_popup() {
  2621. $inpopup = optional_param('inpopup', '', PARAM_BOOL);
  2622. return ($inpopup);
  2623. }
  2624. /**
  2625. * To use this class.
  2626. * - construct
  2627. * - call create (or use the 3rd param to the constructor)
  2628. * - call update or update_full() or update() repeatedly
  2629. *
  2630. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2631. * @package moodlecore
  2632. */
  2633. class progress_bar {
  2634. /** @var string html id */
  2635. private $html_id;
  2636. /** @var int total width */
  2637. private $width;
  2638. /** @var int last percentage printed */
  2639. private $percent = 0;
  2640. /** @var int time when last printed */
  2641. private $lastupdate = 0;
  2642. /** @var int when did we start printing this */
  2643. private $time_start = 0;
  2644. /**
  2645. * Constructor
  2646. *
  2647. * @param string $html_id
  2648. * @param int $width
  2649. * @param bool $autostart Default to false
  2650. * @return void, prints JS code if $autostart true
  2651. */
  2652. public function __construct($html_id = '', $width = 500, $autostart = false) {
  2653. if (!empty($html_id)) {
  2654. $this->html_id = $html_id;
  2655. } else {
  2656. $this->html_id = 'pbar_'.uniqid();
  2657. }
  2658. $this->width = $width;
  2659. if ($autostart){
  2660. $this->create();
  2661. }
  2662. }
  2663. /**
  2664. * Create a new progress bar, this function will output html.
  2665. *
  2666. * @return void Echo's output
  2667. */
  2668. public function create() {
  2669. $this->time_start = microtime(true);
  2670. if (CLI_SCRIPT) {
  2671. return; // temporary solution for cli scripts
  2672. }
  2673. $htmlcode = <<<EOT
  2674. <div style="text-align:center;width:{$this->width}px;clear:both;padding:0;margin:0 auto;">
  2675. <h2 id="status_{$this->html_id}" style="text-align: center;margin:0 auto"></h2>
  2676. <p id="time_{$this->html_id}"></p>
  2677. <div id="bar_{$this->html_id}" style="border-style:solid;border-width:1px;width:500px;height:50px;">
  2678. <div id="progress_{$this->html_id}"
  2679. style="text-align:center;background:#FFCC66;width:4px;border:1px
  2680. solid gray;height:38px; padding-top:10px;">&nbsp;<span id="pt_{$this->html_id}"></span>
  2681. </div>
  2682. </div>
  2683. </div>
  2684. EOT;
  2685. flush();
  2686. echo $htmlcode;
  2687. flush();
  2688. }
  2689. /**
  2690. * Update the progress bar
  2691. *
  2692. * @param int $percent from 1-100
  2693. * @param string $msg
  2694. * @return void Echo's output
  2695. */
  2696. private function _update($percent, $msg) {
  2697. if (empty($this->time_start)){
  2698. $this->time_start = microtime(true);
  2699. }
  2700. if (CLI_SCRIPT) {
  2701. return; // temporary solution for cli scripts
  2702. }
  2703. $es = $this->estimate($percent);
  2704. if ($es === null) {
  2705. // always do the first and last updates
  2706. $es = "?";
  2707. } else if ($es == 0) {
  2708. // always do the last updates
  2709. } else if ($this->lastupdate + 20 < time()) {
  2710. // we must update otherwise browser would time out
  2711. } else if (round($this->percent, 2) === round($percent, 2)) {
  2712. // no significant change, no need to update anything
  2713. return;
  2714. }
  2715. $this->percent = $percent;
  2716. $this->lastupdate = microtime(true);
  2717. $w = ($this->percent/100) * $this->width;
  2718. echo html_writer::script(js_writer::function_call('update_progress_bar', array($this->html_id, $w, $this->percent, $msg, $es)));
  2719. flush();
  2720. }
  2721. /**
  2722. * Estimate how much time it is going to take.
  2723. *
  2724. * @param int $curtime the time call this function
  2725. * @param int $percent from 1-100
  2726. * @return mixed Null (unknown), or int
  2727. */
  2728. private function estimate($pt) {
  2729. if ($this->lastupdate == 0) {
  2730. return null;
  2731. }
  2732. if ($pt < 0.00001) {
  2733. return null; // we do not know yet how long it will take
  2734. }
  2735. if ($pt > 99.99999) {
  2736. return 0; // nearly done, right?
  2737. }
  2738. $consumed = microtime(true) - $this->time_start;
  2739. if ($consumed < 0.001) {
  2740. return null;
  2741. }
  2742. return (100 - $pt) * ($consumed / $pt);
  2743. }
  2744. /**
  2745. * Update progress bar according percent
  2746. *
  2747. * @param int $percent from 1-100
  2748. * @param string $msg the message needed to be shown
  2749. */
  2750. public function update_full($percent, $msg) {
  2751. $percent = max(min($percent, 100), 0);
  2752. $this->_update($percent, $msg);
  2753. }
  2754. /**
  2755. * Update progress bar according the number of tasks
  2756. *
  2757. * @param int $cur current task number
  2758. * @param int $total total task number
  2759. * @param string $msg message
  2760. */
  2761. public function update($cur, $total, $msg) {
  2762. $percent = ($cur / $total) * 100;
  2763. $this->update_full($percent, $msg);
  2764. }
  2765. /**
  2766. * Restart the progress bar.
  2767. */
  2768. public function restart() {
  2769. $this->percent = 0;
  2770. $this->lastupdate = 0;
  2771. $this->time_start = 0;
  2772. }
  2773. }
  2774. /**
  2775. * Use this class from long operations where you want to output occasional information about
  2776. * what is going on, but don't know if, or in what format, the output should be.
  2777. *
  2778. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2779. * @package moodlecore
  2780. */
  2781. abstract class progress_trace {
  2782. /**
  2783. * Ouput an progress message in whatever format.
  2784. * @param string $message the message to output.
  2785. * @param integer $depth indent depth for this message.
  2786. */
  2787. abstract public function output($message, $depth = 0);
  2788. /**
  2789. * Called when the processing is finished.
  2790. */
  2791. public function finished() {
  2792. }
  2793. }
  2794. /**
  2795. * This subclass of progress_trace does not ouput anything.
  2796. *
  2797. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2798. * @package moodlecore
  2799. */
  2800. class null_progress_trace extends progress_trace {
  2801. /**
  2802. * Does Nothing
  2803. *
  2804. * @param string $message
  2805. * @param int $depth
  2806. * @return void Does Nothing
  2807. */
  2808. public function output($message, $depth = 0) {
  2809. }
  2810. }
  2811. /**
  2812. * This subclass of progress_trace outputs to plain text.
  2813. *
  2814. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2815. * @package moodlecore
  2816. */
  2817. class text_progress_trace extends progress_trace {
  2818. /**
  2819. * Output the trace message
  2820. *
  2821. * @param string $message
  2822. * @param int $depth
  2823. * @return void Output is echo'd
  2824. */
  2825. public function output($message, $depth = 0) {
  2826. echo str_repeat(' ', $depth), $message, "\n";
  2827. flush();
  2828. }
  2829. }
  2830. /**
  2831. * This subclass of progress_trace outputs as HTML.
  2832. *
  2833. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2834. * @package moodlecore
  2835. */
  2836. class html_progress_trace extends progress_trace {
  2837. /**
  2838. * Output the trace message
  2839. *
  2840. * @param string $message
  2841. * @param int $depth
  2842. * @return void Output is echo'd
  2843. */
  2844. public function output($message, $depth = 0) {
  2845. echo '<p>', str_repeat('&#160;&#160;', $depth), htmlspecialchars($message), "</p>\n";
  2846. flush();
  2847. }
  2848. }
  2849. /**
  2850. * HTML List Progress Tree
  2851. *
  2852. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2853. * @package moodlecore
  2854. */
  2855. class html_list_progress_trace extends progress_trace {
  2856. /** @var int */
  2857. protected $currentdepth = -1;
  2858. /**
  2859. * Echo out the list
  2860. *
  2861. * @param string $message The message to display
  2862. * @param int $depth
  2863. * @return void Output is echoed
  2864. */
  2865. public function output($message, $depth = 0) {
  2866. $samedepth = true;
  2867. while ($this->currentdepth > $depth) {
  2868. echo "</li>\n</ul>\n";
  2869. $this->currentdepth -= 1;
  2870. if ($this->currentdepth == $depth) {
  2871. echo '<li>';
  2872. }
  2873. $samedepth = false;
  2874. }
  2875. while ($this->currentdepth < $depth) {
  2876. echo "<ul>\n<li>";
  2877. $this->currentdepth += 1;
  2878. $samedepth = false;
  2879. }
  2880. if ($samedepth) {
  2881. echo "</li>\n<li>";
  2882. }
  2883. echo htmlspecialchars($message);
  2884. flush();
  2885. }
  2886. /**
  2887. * Called when the processing is finished.
  2888. */
  2889. public function finished() {
  2890. while ($this->currentdepth >= 0) {
  2891. echo "</li>\n</ul>\n";
  2892. $this->currentdepth -= 1;
  2893. }
  2894. }
  2895. }
  2896. /**
  2897. * Returns a localized sentence in the current language summarizing the current password policy
  2898. *
  2899. * @todo this should be handled by a function/method in the language pack library once we have a support for it
  2900. * @uses $CFG
  2901. * @return string
  2902. */
  2903. function print_password_policy() {
  2904. global $CFG;
  2905. $message = '';
  2906. if (!empty($CFG->passwordpolicy)) {
  2907. $messages = array();
  2908. $messages[] = get_string('informminpasswordlength', 'auth', $CFG->minpasswordlength);
  2909. if (!empty($CFG->minpassworddigits)) {
  2910. $messages[] = get_string('informminpassworddigits', 'auth', $CFG->minpassworddigits);
  2911. }
  2912. if (!empty($CFG->minpasswordlower)) {
  2913. $messages[] = get_string('informminpasswordlower', 'auth', $CFG->minpasswordlower);
  2914. }
  2915. if (!empty($CFG->minpasswordupper)) {
  2916. $messages[] = get_string('informminpasswordupper', 'auth', $CFG->minpasswordupper);
  2917. }
  2918. if (!empty($CFG->minpasswordnonalphanum)) {
  2919. $messages[] = get_string('informminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum);
  2920. }
  2921. $messages = join(', ', $messages); // this is ugly but we do not have anything better yet...
  2922. $message = get_string('informpasswordpolicy', 'auth', $messages);
  2923. }
  2924. return $message;
  2925. }