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