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