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