PageRenderTime 64ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/weblib.php

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