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