MDL-16438 standardized component names
[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
7cf1c7bd 69
70/**
71 * Allowed tags - string of html tags that can be tested against for safe html tags
72 * @global string $ALLOWED_TAGS
449611af 73 * @name $ALLOWED_TAGS
7cf1c7bd 74 */
5ea4af22 75global $ALLOWED_TAGS;
39dda0fc 76$ALLOWED_TAGS =
cf34d0ea 77'<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 78
037dcbb6 79/**
80 * Allowed protocols - array of protocols that are safe to use in links and so on
81 * @global string $ALLOWED_PROTOCOLS
449611af 82 * @name $ALLOWED_PROTOCOLS
037dcbb6 83 */
f941df22 84$ALLOWED_PROTOCOLS = array('http', 'https', 'ftp', 'news', 'mailto', 'rtsp', 'teamspeak', 'gopher', 'mms',
f697a421 85 'color', 'callto', 'cursor', 'text-align', 'font-size', 'font-weight', 'font-style', 'font-family',
62097c91 86 'border', 'margin', 'padding', 'background', 'background-color', 'text-decoration'); // CSS as well to get through kses
037dcbb6 87
88
0095d5cd 89/// Functions
90
7cf1c7bd 91/**
92 * Add quotes to HTML characters
93 *
94 * Returns $var with HTML characters (like "<", ">", etc.) properly quoted.
95 * This function is very similar to {@link p()}
96 *
449611af 97 * @todo Remove obsolete param $obsolete if not used anywhere
98 *
7cf1c7bd 99 * @param string $var the string potentially containing HTML characters
b4cf9371 100 * @param boolean $obsolete no longer used.
7cf1c7bd 101 * @return string
102 */
b4cf9371 103function s($var, $obsolete = false) {
d4a42ff4 104
63e554d0 105 if ($var == '0') { // for integer 0, boolean false, string '0'
106 return '0';
3662bce5 107 }
d4a42ff4 108
0c34c7eb 109 return preg_replace("/&amp;(#\d+);/i", "&$1;", htmlspecialchars($var));
f9903ed0 110}
111
7cf1c7bd 112/**
113 * Add quotes to HTML characters
114 *
d48b00b4 115 * Prints $var with HTML characters (like "<", ">", etc.) properly quoted.
449611af 116 * This function simply calls {@link s()}
117 * @see s()
118 *
119 * @todo Remove obsolete param $obsolete if not used anywhere
7cf1c7bd 120 *
121 * @param string $var the string potentially containing HTML characters
b4cf9371 122 * @param boolean $obsolete no longer used.
7cf1c7bd 123 * @return string
124 */
b4cf9371 125function p($var, $obsolete = false) {
126 echo s($var, $obsolete);
f9903ed0 127}
128
0d1cd0ea 129/**
130 * Does proper javascript quoting.
449611af 131 *
5ce73257 132 * Do not use addslashes anymore, because it does not work when magic_quotes_sybase is enabled.
133 *
449611af 134 * @param mixed $var String, Array, or Object to add slashes to
0d1cd0ea 135 * @return mixed quoted result
136 */
137function addslashes_js($var) {
138 if (is_string($var)) {
139 $var = str_replace('\\', '\\\\', $var);
140 $var = str_replace(array('\'', '"', "\n", "\r", "\0"), array('\\\'', '\\"', '\\n', '\\r', '\\0'), $var);
4702be4e 141 $var = str_replace('</', '<\/', $var); // XHTML compliance
0d1cd0ea 142 } else if (is_array($var)) {
143 $var = array_map('addslashes_js', $var);
144 } else if (is_object($var)) {
145 $a = get_object_vars($var);
146 foreach ($a as $key=>$value) {
147 $a[$key] = addslashes_js($value);
148 }
149 $var = (object)$a;
150 }
151 return $var;
152}
7cf1c7bd 153
7cf1c7bd 154/**
155 * Remove query string from url
156 *
157 * Takes in a URL and returns it without the querystring portion
158 *
159 * @param string $url the url which may have a query string attached
449611af 160 * @return string The remaining URL
7cf1c7bd 161 */
162 function strip_querystring($url) {
f9903ed0 163
b9b8ab69 164 if ($commapos = strpos($url, '?')) {
165 return substr($url, 0, $commapos);
166 } else {
167 return $url;
168 }
f9903ed0 169}
170
7cf1c7bd 171/**
c8135a35 172 * Returns the URL of the HTTP_REFERER, less the querystring portion if required
449611af 173 *
174 * @uses $_SERVER
9ea04325 175 * @param boolean $stripquery if true, also removes the query part of the url.
449611af 176 * @return string The resulting referer or emtpy string
7cf1c7bd 177 */
c8135a35 178function get_referer($stripquery=true) {
d90ffc1f 179 if (isset($_SERVER['HTTP_REFERER'])) {
c8135a35 180 if ($stripquery) {
181 return strip_querystring($_SERVER['HTTP_REFERER']);
182 } else {
183 return $_SERVER['HTTP_REFERER'];
184 }
d90ffc1f 185 } else {
5ce73257 186 return '';
d90ffc1f 187 }
f9903ed0 188}
189
c1d57101 190
7cf1c7bd 191/**
192 * Returns the name of the current script, WITH the querystring portion.
449611af 193 *
194 * This function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
7cf1c7bd 195 * return different things depending on a lot of things like your OS, Web
196 * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.)
d48b00b4 197 * <b>NOTE:</b> This function returns false if the global variables needed are not set.
198 *
449611af 199 * @global string
200 * @return mixed String, or false if the global variables needed are not set
7cf1c7bd 201 */
b03fc392 202function me() {
203 global $ME;
204 return $ME;
f9903ed0 205}
206
7cf1c7bd 207/**
449611af 208 * Returns the name of the current script, WITH the full URL.
209 *
210 * This function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
211 * return different things depending on a lot of things like your OS, Web
212 * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.
213 * <b>NOTE:</b> This function returns false if the global variables needed are not set.
214 *
d48b00b4 215 * Like {@link me()} but returns a full URL
7cf1c7bd 216 * @see me()
449611af 217 *
218 * @global string
219 * @return mixed String, or false if the global variables needed are not set
7cf1c7bd 220 */
f9903ed0 221function qualified_me() {
11e7b506 222 global $FULLME;
223 return $FULLME;
f9903ed0 224}
225
360e503e 226/**
227 * Class for creating and manipulating urls.
84e3d2cc 228 *
449611af 229 * It can be used in moodle pages where config.php has been included without any further includes.
230 *
231 * It is useful for manipulating urls with long lists of params.
232 * One situation where it will be useful is a page which links to itself to perfrom various actions
233 * and / or to process form data. A moodle_url object :
234 * can be created for a page to refer to itself with all the proper get params being passed from page call to
235 * page call and methods can be used to output a url including all the params, optionally adding and overriding
236 * params and can also be used to
237 * - output the url without any get params
238 * - and output the params as hidden fields to be output within a form
239 *
240 * One important usage note is that data passed to methods out, out_action, get_query_string and
241 * hidden_params_out affect what is returned by the function and do not change the data stored in the object.
242 * This is to help with typical usage of these objects where one object is used to output urls
243 * in many places in a page.
244 *
245 * @link http://docs.moodle.org/en/Development:lib/weblib.php_moodle_url See short write up here
246 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
247 * @package moodlecore
360e503e 248 */
249class moodle_url {
449611af 250 /**
251 * @var string
252 * @access protected
253 */
7ceb61d8 254 protected $scheme = ''; // e.g. http
255 protected $host = '';
256 protected $port = '';
257 protected $user = '';
258 protected $pass = '';
259 protected $path = '';
260 protected $fragment = '';
449611af 261 /**
262 * @var array
263 * @access protected
264 */
7ceb61d8 265 protected $params = array(); // Associative array of query string params
84e3d2cc 266
360e503e 267 /**
449611af 268 * Pass no arguments to create a url that refers to this page.
269 * Use empty string to create empty url.
84e3d2cc 270 *
449611af 271 * @global string
75781f87 272 * @param mixed $url a number of different forms are accespted:
273 * null - create a URL that is the same as the URL used to load this page, but with no query string
274 * '' - and empty URL
275 * string - a URL, will be parsed into it's bits, including query string
276 * array - as returned from the PHP function parse_url
277 * moodle_url - make a copy of another moodle_url
7ceb61d8 278 * @param array $params these params override anything in the query string
279 * where params have the same name.
360e503e 280 */
7ceb61d8 281 public function __construct($url = null, $params = array()) {
75781f87 282 if ($url === '') {
283 // Leave URL blank.
284 } else if (is_a($url, 'moodle_url')) {
285 $this->scheme = $url->scheme;
286 $this->host = $url->host;
287 $this->port = $url->port;
288 $this->user = $url->user;
289 $this->pass = $url->pass;
ad52c04f 290 $this->path = $url->path;
75781f87 291 $this->fragment = $url->fragment;
292 $this->params = $url->params;
293 } else {
7ceb61d8 294 if ($url === null) {
75781f87 295 global $ME;
11e7b506 296 $url = $ME;
360e503e 297 }
75781f87 298 if (is_string($url)) {
299 $url = parse_url($url);
300 }
301 $parts = $url;
7ceb61d8 302 if ($parts === FALSE) {
75781f87 303 throw new moodle_exception('invalidurl');
360e503e 304 }
7ceb61d8 305 if (isset($parts['query'])) {
24a905f9 306 parse_str(str_replace('&amp;', '&', $parts['query']), $this->params);
360e503e 307 }
308 unset($parts['query']);
7ceb61d8 309 foreach ($parts as $key => $value) {
360e503e 310 $this->$key = $value;
311 }
312 }
75781f87 313 $this->params($params);
84e3d2cc 314 }
7ceb61d8 315
360e503e 316 /**
449611af 317 * Add an array of params to the params for this page.
318 *
319 * The added params override existing ones if they have the same name.
360e503e 320 *
7ceb61d8 321 * @param array $params Defaults to null. If null then return value of param 'name'.
449611af 322 * @return array Array of Params for url.
360e503e 323 */
7ceb61d8 324 public function params($params = null) {
325 if (!is_null($params)) {
c1f41c59 326 return $this->params = $params + $this->params;
327 } else {
328 return $this->params;
329 }
360e503e 330 }
84e3d2cc 331
360e503e 332 /**
449611af 333 * Remove all params if no arguments passed.
334 * Remove selected params if arguments are passed.
335 *
336 * Can be called as either remove_params('param1', 'param2')
75781f87 337 * or remove_params(array('param1', 'param2')).
360e503e 338 *
75781f87 339 * @param mixed $params either an array of param names, or a string param name,
340 * @param string $params,... any number of additional param names.
360e503e 341 */
49127522 342 public function remove_params($params = NULL) {
75781f87 343 if (empty($params)) {
360e503e 344 $this->params = array();
75781f87 345 return;
346 }
347 if (!is_array($params)) {
348 $params = func_get_args();
349 }
350 foreach ($params as $param) {
351 if (isset($this->params[$param])) {
352 unset($this->params[$param]);
353 }
360e503e 354 }
355 }
356
357 /**
449611af 358 * Add a param to the params for this page.
359 *
360 * The added param overrides existing one if theyhave the same name.
360e503e 361 *
362 * @param string $paramname name
449611af 363 * @param string $param Param value. Defaults to null. If null then return value of param 'name'
364 * @return void|string If $param was null then the value of $paramname was returned
cf615522 365 * (null is returned if that param does not exist).
360e503e 366 */
7ceb61d8 367 public function param($paramname, $param = null) {
368 if (!is_null($param)) {
c1f41c59 369 $this->params = array($paramname => $param) + $this->params;
cf615522 370 } else if (array_key_exists($paramname, $this->params)) {
5762b36e 371 return $this->params[$paramname];
cf615522 372 } else {
373 return null;
c1f41c59 374 }
360e503e 375 }
376
7ceb61d8 377 /**
378 * Get the params as as a query string.
449611af 379 *
7ceb61d8 380 * @param array $overrideparams params to add to the output params, these
381 * override existing ones with the same name.
382 * @return string query string that can be added to a url.
383 */
384 public function get_query_string($overrideparams = array()) {
360e503e 385 $arr = array();
386 $params = $overrideparams + $this->params;
7ceb61d8 387 foreach ($params as $key => $val) {
360e503e 388 $arr[] = urlencode($key)."=".urlencode($val);
389 }
dc1f7683 390 return implode($arr, "&amp;");
360e503e 391 }
7ceb61d8 392
360e503e 393 /**
394 * Outputs params as hidden form elements.
fcdb06c4 395 *
449611af 396 * @param array $exclude params to ignore
fae60ee3 397 * @param integer $indent indentation
f44e4d14 398 * @param array $overrideparams params to add to the output params, these
7ceb61d8 399 * override existing ones with the same name.
360e503e 400 * @return string html for form elements.
401 */
7ceb61d8 402 public function hidden_params_out($exclude = array(), $indent = 0, $overrideparams=array()) {
360e503e 403 $tabindent = str_repeat("\t", $indent);
404 $str = '';
f44e4d14 405 $params = $overrideparams + $this->params;
7ceb61d8 406 foreach ($params as $key => $val) {
fcdb06c4 407 if (FALSE === array_search($key, $exclude)) {
83bc64db 408 $val = s($val);
fcdb06c4 409 $str.= "$tabindent<input type=\"hidden\" name=\"$key\" value=\"$val\" />\n";
410 }
360e503e 411 }
412 return $str;
413 }
7ceb61d8 414
360e503e 415 /**
416 * Output url
84e3d2cc 417 *
7130fb21 418 * @param boolean $omitquerystring whether to output page params as a query string in the url.
360e503e 419 * @param array $overrideparams params to add to the output url, these override existing ones with the same name.
449611af 420 * @return string Resulting URL
360e503e 421 */
7130fb21 422 public function out($omitquerystring = false, $overrideparams = array()) {
360e503e 423 $uri = $this->scheme ? $this->scheme.':'.((strtolower($this->scheme) == 'mailto') ? '':'//'): '';
424 $uri .= $this->user ? $this->user.($this->pass? ':'.$this->pass:'').'@':'';
425 $uri .= $this->host ? $this->host : '';
426 $uri .= $this->port ? ':'.$this->port : '';
427 $uri .= $this->path ? $this->path : '';
7130fb21 428 if (!$omitquerystring) {
429 $querystring = $this->get_query_string($overrideparams);
430 if ($querystring) {
431 $uri .= '?' . $querystring;
432 }
360e503e 433 }
434 $uri .= $this->fragment ? '#'.$this->fragment : '';
84e3d2cc 435 return $uri;
360e503e 436 }
7ceb61d8 437
360e503e 438 /**
439 * Output action url with sesskey
84e3d2cc 440 *
449611af 441 * Adds sesskey and overriderparams then calls {@link out()}
442 * @see out()
443 *
444 * @param array $overrideparams Allows you to override params
360e503e 445 * @return string url
446 */
7ceb61d8 447 public function out_action($overrideparams = array()) {
360e503e 448 $overrideparams = array('sesskey'=> sesskey()) + $overrideparams;
dc1f7683 449 return $this->out(false, $overrideparams);
360e503e 450 }
451}
452
7cf1c7bd 453/**
454 * Determine if there is data waiting to be processed from a form
455 *
456 * Used on most forms in Moodle to check for data
457 * Returns the data as an object, if it's found.
458 * This object can be used in foreach loops without
459 * casting because it's cast to (array) automatically
772e78be 460 *
9c0f063b 461 * Checks that submitted POST data exists and returns it as object.
d48b00b4 462 *
449611af 463 * @uses $_POST
9c0f063b 464 * @return mixed false or object
7cf1c7bd 465 */
294ce987 466function data_submitted() {
d48b00b4 467
607809b3 468 if (empty($_POST)) {
36b4f985 469 return false;
470 } else {
294ce987 471 return (object)$_POST;
36b4f985 472 }
473}
474
7cf1c7bd 475/**
d48b00b4 476 * Given some normal text this function will break up any
477 * long words to a given size by inserting the given character
478 *
6aaa17c7 479 * It's multibyte savvy and doesn't change anything inside html tags.
480 *
7cf1c7bd 481 * @param string $string the string to be modified
89dcb99d 482 * @param int $maxsize maximum length of the string to be returned
7cf1c7bd 483 * @param string $cutchar the string used to represent word breaks
484 * @return string
485 */
4a5644e5 486function break_up_long_words($string, $maxsize=20, $cutchar=' ') {
a2b3f884 487
6aaa17c7 488/// Loading the textlib singleton instance. We are going to need it.
489 $textlib = textlib_get_instance();
8f7dc7f1 490
6aaa17c7 491/// First of all, save all the tags inside the text to skip them
492 $tags = array();
493 filter_save_tags($string,$tags);
5b07d990 494
6aaa17c7 495/// Process the string adding the cut when necessary
4a5644e5 496 $output = '';
810944af 497 $length = $textlib->strlen($string);
4a5644e5 498 $wordlength = 0;
499
500 for ($i=0; $i<$length; $i++) {
810944af 501 $char = $textlib->substr($string, $i, 1);
6aaa17c7 502 if ($char == ' ' or $char == "\t" or $char == "\n" or $char == "\r" or $char == "<" or $char == ">") {
4a5644e5 503 $wordlength = 0;
504 } else {
505 $wordlength++;
506 if ($wordlength > $maxsize) {
507 $output .= $cutchar;
508 $wordlength = 0;
509 }
510 }
511 $output .= $char;
512 }
6aaa17c7 513
514/// Finally load the tags back again
515 if (!empty($tags)) {
516 $output = str_replace(array_keys($tags), $tags, $output);
517 }
518
4a5644e5 519 return $output;
520}
521
7cf1c7bd 522/**
fc5e9c40 523 * This function will print a button/link/etc. form element
524 * that will work on both Javascript and non-javascript browsers.
d48b00b4 525 *
449611af 526 * Relies on the Javascript function openpopup in javascript.php
fc5e9c40 527 * All parameters default to null, only $type and $url are mandatory.
528 *
7cf1c7bd 529 * $url must be relative to home page eg /mod/survey/stuff.php
449611af 530 *
531 * @global object
532 * @param string $type Can be 'button' or 'link'
bed9cec8 533 * @param string $url Web link. Either relative to $CFG->wwwroot, or a full URL.
73f7ad71 534 * @param string $name Name to be assigned to the popup window (this is used by
a5fe6177 535 * client-side scripts to "talk" to the popup window)
7cf1c7bd 536 * @param string $linkname Text to be displayed as web link
89dcb99d 537 * @param int $height Height to assign to popup window
538 * @param int $width Height to assign to popup window
7cf1c7bd 539 * @param string $title Text to be displayed as popup page title
540 * @param string $options List of additional options for popup window
449611af 541 * @param bool $return If true, return as a string, otherwise print
fc5e9c40 542 * @param string $id id added to the element
543 * @param string $class class added to the element
7cf1c7bd 544 * @return string
7cf1c7bd 545 */
fc5e9c40 546function element_to_popup_window ($type=null, $url=null, $name=null, $linkname=null,
c5659019 547 $height=400, $width=500, $title=null,
fc5e9c40 548 $options=null, $return=false, $id=null, $class=null) {
f9903ed0 549
c5659019 550 if (is_null($url)) {
b867e602 551 debugging('You must give the url to display in the popup. URL is missing - can\'t create popup window.', DEBUG_DEVELOPER);
2416a6f7 552 }
ff80e012 553
fc5e9c40 554 global $CFG;
f9903ed0 555
2416a6f7 556 if ($options == 'none') { // 'none' is legacy, should be removed in v2.0
c5659019 557 $options = null;
fc5e9c40 558 }
559
560 // add some sane default options for popup windows
c5659019 561 if (!$options) {
562 $options = 'menubar=0,location=0,scrollbars,resizable';
2416a6f7 563 }
c5659019 564 if ($width) {
565 $options .= ',width='. $width;
2416a6f7 566 }
c5659019 567 if ($height) {
568 $options .= ',height='. $height;
2416a6f7 569 }
c5659019 570 if ($id) {
571 $id = ' id="'.$id.'" ';
2416a6f7 572 }
c5659019 573 if ($class) {
574 $class = ' class="'.$class.'" ';
2416a6f7 575 }
b867e602 576 if ($name) {
4ba6b3e3 577 $_name = $name;
578 if (($name = preg_replace("/\s/", '_', $name)) != $_name) {
579 debugging('The $name of a popup window shouldn\'t contain spaces - string modified. '. $_name .' changed to '. $name, DEBUG_DEVELOPER);
580 }
b867e602 581 } else {
a5fe6177 582 $name = 'popup';
2416a6f7 583 }
73f7ad71 584
a5fe6177 585 // get some default string, using the localized version of legacy defaults
32ba2f0d 586 if (is_null($linkname) || $linkname === '') {
5df984eb 587 $linkname = get_string('clickhere');
2416a6f7 588 }
c5659019 589 if (!$title) {
5df984eb 590 $title = get_string('popupwindowname');
2416a6f7 591 }
fc5e9c40 592
c5659019 593 $fullscreen = 0; // must be passed to openpopup
fc5e9c40 594 $element = '';
595
596 switch ($type) {
bed9cec8 597 case 'button':
fc5e9c40 598 $element = '<input type="button" name="'. $name .'" title="'. $title .'" value="'. $linkname .'" '. $id . $class .
599 "onclick=\"return openpopup('$url', '$name', '$options', $fullscreen);\" />\n";
600 break;
bed9cec8 601 case 'link':
602 // Add wwwroot only if the URL does not already start with http:// or https://
603 if (!preg_match('|https?://|', $url)) {
604 $url = $CFG->wwwroot . $url;
fc5e9c40 605 }
bed9cec8 606 $element = '<a title="'. s(strip_tags($title)) .'" href="'. $url .'" '.
fc5e9c40 607 "onclick=\"this.target='$name'; return openpopup('$url', '$name', '$options', $fullscreen);\">$linkname</a>";
608 break;
609 default :
e49ef64a 610 print_error('cannotcreatepopupwin');
fc5e9c40 611 break;
c80b7585 612 }
613
1f2eec7b 614 if ($return) {
fc5e9c40 615 return $element;
1f2eec7b 616 } else {
fc5e9c40 617 echo $element;
1f2eec7b 618 }
f9903ed0 619}
620
7cf1c7bd 621/**
fc5e9c40 622 * Creates and displays (or returns) a link to a popup window, using element_to_popup_window function.
d48b00b4 623 *
449611af 624 * Simply calls {@link element_to_popup_window()} with type set to 'link'
fc5e9c40 625 * @see element_to_popup_window()
449611af 626 *
627 * @param string $url Web link. Either relative to $CFG->wwwroot, or a full URL.
628 * @param string $name Name to be assigned to the popup window (this is used by
629 * client-side scripts to "talk" to the popup window)
630 * @param string $linkname Text to be displayed as web link
631 * @param int $height Height to assign to popup window
632 * @param int $width Height to assign to popup window
633 * @param string $title Text to be displayed as popup page title
634 * @param string $options List of additional options for popup window
635 * @param bool $return If true, return as a string, otherwise print
636 * @param string $id id added to the element
637 * @param string $class class added to the element
638 * @return string html code to display a link to a popup window.
7cf1c7bd 639 */
fc5e9c40 640function link_to_popup_window ($url, $name=null, $linkname=null,
641 $height=400, $width=500, $title=null,
642 $options=null, $return=false) {
52f1b496 643
fc5e9c40 644 return element_to_popup_window('link', $url, $name, $linkname, $height, $width, $title, $options, $return, null, null);
645}
0f7d4e5e 646
fc5e9c40 647/**
648 * Creates and displays (or returns) a buttons to a popup window, using element_to_popup_window function.
649 *
449611af 650 * Simply calls {@link element_to_popup_window()} with type set to 'button'
fc5e9c40 651 * @see element_to_popup_window()
449611af 652 *
653 * @param string $url Web link. Either relative to $CFG->wwwroot, or a full URL.
654 * @param string $name Name to be assigned to the popup window (this is used by
655 * client-side scripts to "talk" to the popup window)
656 * @param string $linkname Text to be displayed as web link
657 * @param int $height Height to assign to popup window
658 * @param int $width Height to assign to popup window
659 * @param string $title Text to be displayed as popup page title
660 * @param string $options List of additional options for popup window
661 * @param bool $return If true, return as a string, otherwise print
662 * @param string $id id added to the element
663 * @param string $class class added to the element
664 * @return string html code to display a link to a popup window.
fc5e9c40 665 */
666function button_to_popup_window ($url, $name=null, $linkname=null,
667 $height=400, $width=500, $title=null, $options=null, $return=false,
668 $id=null, $class=null) {
52f1b496 669
fc5e9c40 670 return element_to_popup_window('button', $url, $name, $linkname, $height, $width, $title, $options, $return, $id, $class);
52f1b496 671}
672
673
7cf1c7bd 674/**
675 * Prints a simple button to close a window
449611af 676 *
677 * @global object
678 * @param string $name Name of the window to close
829eabfe 679 * @param boolean $return whether this function should return a string or output it.
680 * @param boolean $reloadopener if true, clicking the button will also reload
681 * the page that opend this popup window.
449611af 682 * @return string|void if $return is true, void otherwise
7cf1c7bd 683 */
829eabfe 684function close_window_button($name='closewindow', $return=false, $reloadopener = false) {
38d5ab86 685 global $CFG;
686
829eabfe 687 $js = 'self.close();';
688 if ($reloadopener) {
689 $js = 'window.opener.location.reload(1);' . $js;
690 }
691
eaeaf964 692 $output = '';
693
4079b3d7 694 $output .= '<div class="closewindow">' . "\n";
450522e3 695 $output .= '<form action="#"><div>';
829eabfe 696 $output .= '<input type="button" onclick="' . $js . '" value="'.get_string($name).'" />';
38d5ab86 697 $output .= '</div></form>';
4079b3d7 698 $output .= '</div>' . "\n";
eaeaf964 699
700 if ($return) {
701 return $output;
702 } else {
703 echo $output;
704 }
f9903ed0 705}
706
08396bb2 707/*
b166403f 708 * Try and close the current window using JavaScript, either immediately, or after a delay.
449611af 709 *
710 * Echo's out the resulting XHTML & javascript
711 *
712 * @global object
713 * @global object
b166403f 714 * @param integer $delay a delay in seconds before closing the window. Default 0.
715 * @param boolean $reloadopener if true, we will see if this window was a pop-up, and try
716 * to reload the parent window before this one closes.
08396bb2 717 */
b166403f 718function close_window($delay = 0, $reloadopener = false) {
c13a5e71 719 global $THEME, $PAGE;
08396bb2 720
c13a5e71 721 if (!$PAGE->headerprinted) {
b166403f 722 print_header(get_string('closewindow'));
723 } else {
724 print_container_end_all(false, $THEME->open_header_containers);
725 }
726
727 if ($reloadopener) {
728 $function = 'close_window_reloading_opener';
729 } else {
730 $function = 'close_window';
731 }
732 echo '<p class="centerpara">' . get_string('windowclosing') . '</p>';
cf615522 733
734 $PAGE->requires->js_function_call($function)->after_delay($delay);
b166403f 735
736 print_footer('empty');
737 exit;
738}
08396bb2 739
d48b00b4 740/**
75afe0e7 741 * Given an array of values, output the HTML for a select element with those options.
449611af 742 *
75afe0e7 743 * Normally, you only need to use the first few parameters.
d48b00b4 744 *
75afe0e7 745 * @param array $options The options to offer. An array of the form
746 * $options[{value}] = {text displayed for that option};
747 * @param string $name the name of this form control, as in &lt;select name="..." ...
748 * @param string $selected the option to select initially, default none.
749 * @param string $nothing The label for the 'nothing is selected' option. Defaults to get_string('choose').
750 * Set this to '' if you don't want a 'nothing is selected' option.
449611af 751 * @param string $script if not '', then this is added to the &lt;select> element as an onchange handler.
75afe0e7 752 * @param string $nothingvalue The value corresponding to the $nothing option. Defaults to 0.
753 * @param boolean $return if false (the default) the the output is printed directly, If true, the
754 * generated HTML is returned as a string.
755 * @param boolean $disabled if true, the select is generated in a disabled state. Default, false.
756 * @param int $tabindex if give, sets the tabindex attribute on the &lt;select> element. Default none.
757 * @param string $id value to use for the id attribute of the &lt;select> element. If none is given,
758 * then a suitable one is constructed.
1ea118e4 759 * @param mixed $listbox if false, display as a dropdown menu. If true, display as a list box.
760 * By default, the list box will have a number of rows equal to min(10, count($options)), but if
761 * $listbox is an integer, that number is used for size instead.
d7074a5d 762 * @param boolean $multiple if true, enable multiple selections, else only 1 item can be selected. Used
763 * when $listbox display is enabled
764 * @param string $class value to use for the class attribute of the &lt;select> element. If none is given,
765 * then a suitable one is constructed.
449611af 766 * @return string|void If $return=true returns string, else echo's and returns void
d48b00b4 767 */
c06c8492 768function choose_from_menu ($options, $name, $selected='', $nothing='choose', $script='',
1ea118e4 769 $nothingvalue='0', $return=false, $disabled=false, $tabindex=0,
d7074a5d 770 $id='', $listbox=false, $multiple=false, $class='') {
ab9f24ad 771
b0ccd3fb 772 if ($nothing == 'choose') {
773 $nothing = get_string('choose') .'...';
618b22c5 774 }
775
48e535bc 776 $attributes = ($script) ? 'onchange="'. $script .'"' : '';
777 if ($disabled) {
778 $attributes .= ' disabled="disabled"';
f9903ed0 779 }
9c9f7d77 780
25e5dbf9 781 if ($tabindex) {
782 $attributes .= ' tabindex="'.$tabindex.'"';
783 }
784
7850588a 785 if ($id ==='') {
a1c91f9a 786 $id = 'menu'.$name;
787 // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading
788 $id = str_replace('[', '', $id);
789 $id = str_replace(']', '', $id);
7850588a 790 }
791
d7074a5d 792 if ($class ==='') {
caec4b6f 793 $class = 'menu'.$name;
d7074a5d 794 // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading
795 $class = str_replace('[', '', $class);
796 $class = str_replace(']', '', $class);
797 }
caec4b6f 798 $class = 'select ' . $class; /// Add 'select' selector always
d7074a5d 799
1ea118e4 800 if ($listbox) {
801 if (is_integer($listbox)) {
802 $size = $listbox;
803 } else {
804 $numchoices = count($options);
805 if ($nothing) {
806 $numchoices += 1;
807 }
808 $size = min(10, $numchoices);
809 }
810 $attributes .= ' size="' . $size . '"';
811 if ($multiple) {
812 $attributes .= ' multiple="multiple"';
813 }
814 }
815
d7074a5d 816 $output = '<select id="'. $id .'" class="'. $class .'" name="'. $name .'" '. $attributes .'>' . "\n";
bda8d43a 817 if ($nothing) {
7850588a 818 $output .= ' <option value="'. s($nothingvalue) .'"'. "\n";
cec0a0fc 819 if ($nothingvalue === $selected) {
b0ccd3fb 820 $output .= ' selected="selected"';
bda8d43a 821 }
b0ccd3fb 822 $output .= '>'. $nothing .'</option>' . "\n";
873960de 823 }
1ea118e4 824
607809b3 825 if (!empty($options)) {
826 foreach ($options as $value => $label) {
7850588a 827 $output .= ' <option value="'. s($value) .'"';
1ea118e4 828 if ((string)$value == (string)$selected ||
829 (is_array($selected) && in_array($value, $selected))) {
b0ccd3fb 830 $output .= ' selected="selected"';
607809b3 831 }
b0ccd3fb 832 if ($label === '') {
833 $output .= '>'. $value .'</option>' . "\n";
a20c1090 834 } else {
b0ccd3fb 835 $output .= '>'. $label .'</option>' . "\n";
607809b3 836 }
f9903ed0 837 }
838 }
b0ccd3fb 839 $output .= '</select>' . "\n";
08056730 840
841 if ($return) {
842 return $output;
843 } else {
844 echo $output;
845 }
ab9f24ad 846}
f9903ed0 847
73d4db84 848/**
849 * Choose value 0 or 1 from a menu with options 'No' and 'Yes'.
850 * Other options like choose_from_menu.
449611af 851 *
852 * Calls {@link choose_from_menu()} with preset arguments
853 * @see choose_from_menu()
854 *
855 * @param string $name the name of this form control, as in &lt;select name="..." ...
856 * @param string $selected the option to select initially, default none.
857 * @param string $script if not '', then this is added to the &lt;select> element as an onchange handler.
858 * @param boolean $return Whether this function should return a string or output it (defaults to false)
9ea04325 859 * @param boolean $disabled (defaults to false)
860 * @param int $tabindex
449611af 861 * @return string|void If $return=true returns string, else echo's and returns void
73d4db84 862 */
863function choose_from_menu_yesno($name, $selected, $script = '',
864 $return = false, $disabled = false, $tabindex = 0) {
865 return choose_from_menu(array(get_string('no'), get_string('yes')), $name,
866 $selected, '', $script, '0', $return, $disabled, $tabindex);
867}
868
14040797 869/**
870 * Just like choose_from_menu, but takes a nested array (2 levels) and makes a dropdown menu
871 * including option headings with the first level.
449611af 872 *
873 * This function is very similar to {@link choose_from_menu_yesno()}
874 * and {@link choose_from_menu()}
875 *
876 * @todo Add datatype handling to make sure $options is an array
877 *
878 * @param array $options An array of objects to choose from
879 * @param string $name The XHTML field name
880 * @param string $selected The value to select by default
881 * @param string $nothing The label for the 'nothing is selected' option.
882 * Defaults to get_string('choose').
883 * @param string $script If not '', then this is added to the &lt;select> element
884 * as an onchange handler.
885 * @param string $nothingvalue The value for the first `nothing` option if $nothing is set
886 * @param bool $return Whether this function should return a string or output
887 * it (defaults to false)
888 * @param bool $disabled Is the field disabled by default
889 * @param int|string $tabindex Override the tabindex attribute [numeric]
890 * @return string|void If $return=true returns string, else echo's and returns void
14040797 891 */
892function choose_from_menu_nested($options,$name,$selected='',$nothing='choose',$script = '',
893 $nothingvalue=0,$return=false,$disabled=false,$tabindex=0) {
894
895 if ($nothing == 'choose') {
896 $nothing = get_string('choose') .'...';
897 }
898
899 $attributes = ($script) ? 'onchange="'. $script .'"' : '';
900 if ($disabled) {
901 $attributes .= ' disabled="disabled"';
902 }
903
904 if ($tabindex) {
905 $attributes .= ' tabindex="'.$tabindex.'"';
906 }
907
908 $output = '<select id="menu'.$name.'" name="'. $name .'" '. $attributes .'>' . "\n";
909 if ($nothing) {
910 $output .= ' <option value="'. $nothingvalue .'"'. "\n";
911 if ($nothingvalue === $selected) {
912 $output .= ' selected="selected"';
913 }
914 $output .= '>'. $nothing .'</option>' . "\n";
915 }
916 if (!empty($options)) {
917 foreach ($options as $section => $values) {
84e3d2cc 918
dacb47c0 919 $output .= ' <optgroup label="'. s(format_string($section)) .'">'."\n";
14040797 920 foreach ($values as $value => $label) {
dacb47c0 921 $output .= ' <option value="'. format_string($value) .'"';
f332bd02 922 if ((string)$value == (string)$selected) {
14040797 923 $output .= ' selected="selected"';
924 }
925 if ($label === '') {
926 $output .= '>'. $value .'</option>' . "\n";
927 } else {
928 $output .= '>'. $label .'</option>' . "\n";
929 }
930 }
931 $output .= ' </optgroup>'."\n";
932 }
933 }
934 $output .= '</select>' . "\n";
935
936 if ($return) {
937 return $output;
938 } else {
939 echo $output;
940 }
941}
942
943
531a3cb2 944/**
945 * Given an array of values, creates a group of radio buttons to be part of a form
c06c8492 946 *
449611af 947 * @staticvar int $idcounter
531a3cb2 948 * @param array $options An array of value-label pairs for the radio group (values as keys)
949 * @param string $name Name of the radiogroup (unique in the form)
950 * @param string $checked The value that is already checked
449611af 951 * @param bool $return Whether this function should return a string or output
952 * it (defaults to false)
953 * @return string|void If $return=true returns string, else echo's and returns void
531a3cb2 954 */
eaeaf964 955function choose_from_radio ($options, $name, $checked='', $return=false) {
531a3cb2 956
20cbef63 957 static $idcounter = 0;
958
531a3cb2 959 if (!$name) {
960 $name = 'unnamed';
961 }
962
963 $output = '<span class="radiogroup '.$name."\">\n";
964
965 if (!empty($options)) {
966 $currentradio = 0;
967 foreach ($options as $value => $label) {
20cbef63 968 $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter);
969 $output .= ' <span class="radioelement '.$name.' rb'.$currentradio."\">";
970 $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="radio" value="'.$value.'"';
531a3cb2 971 if ($value == $checked) {
972 $output .= ' checked="checked"';
973 }
974 if ($label === '') {
20cbef63 975 $output .= ' /> <label for="'.$htmlid.'">'. $value .'</label></span>' . "\n";
531a3cb2 976 } else {
20cbef63 977 $output .= ' /> <label for="'.$htmlid.'">'. $label .'</label></span>' . "\n";
531a3cb2 978 }
979 $currentradio = ($currentradio + 1) % 2;
980 }
981 }
982
983 $output .= '</span>' . "\n";
984
eaeaf964 985 if ($return) {
986 return $output;
987 } else {
988 echo $output;
989 }
531a3cb2 990}
991
449611af 992/**
993 * Display an standard html checkbox with an optional label
2481037f 994 *
449611af 995 * @staticvar int $idcounter
996 * @param string $name The name of the checkbox
997 * @param string $value The valus that the checkbox will pass when checked
998 * @param bool $checked The flag to tell the checkbox initial state
999 * @param string $label The label to be showed near the checkbox
1000 * @param string $alt The info to be inserted in the alt tag
1001 * @param string $script If not '', then this is added to the checkbox element
1002 * as an onchange handler.
1003 * @param bool $return Whether this function should return a string or output
1004 * it (defaults to false)
1005 * @return string|void If $return=true returns string, else echo's and returns void
2481037f 1006 */
d0d272e7 1007function print_checkbox ($name, $value, $checked = true, $label = '', $alt = '', $script='',$return=false) {
2481037f 1008
20cbef63 1009 static $idcounter = 0;
1010
2481037f 1011 if (!$name) {
1012 $name = 'unnamed';
1013 }
1014
27f79fda 1015 if ($alt) {
1016 $alt = strip_tags($alt);
1017 } else {
2481037f 1018 $alt = 'checkbox';
1019 }
1020
1021 if ($checked) {
1022 $strchecked = ' checked="checked"';
f89f924e 1023 } else {
1024 $strchecked = '';
2481037f 1025 }
1026
20cbef63 1027 $htmlid = 'auto-cb'.sprintf('%04d', ++$idcounter);
2481037f 1028 $output = '<span class="checkbox '.$name."\">";
fbe31d22 1029 $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="checkbox" value="'.$value.'" alt="'.$alt.'"'.$strchecked.' '.((!empty($script)) ? ' onclick="'.$script.'" ' : '').' />';
20cbef63 1030 if(!empty($label)) {
1031 $output .= ' <label for="'.$htmlid.'">'.$label.'</label>';
1032 }
2481037f 1033 $output .= '</span>'."\n";
1034
d0d272e7 1035 if (empty($return)) {
1036 echo $output;
1037 } else {
1038 return $output;
1039 }
2481037f 1040
1041}
1042
449611af 1043/**
1044 * Display an standard html text field with an optional label
d0d272e7 1045 *
449611af 1046 * @param string $name The name of the text field
1047 * @param string $value The value of the text field
1048 * @param string $alt The info to be inserted in the alt tag
1049 * @param int $size Sets the size attribute of the field. Defaults to 50
1050 * @param int $maxlength Sets the maxlength attribute of the field. Not set by default
1051 * @param bool $return Whether this function should return a string or output
1052 * it (defaults to false)
1053 * @return string|void If $return=true returns string, else echo's and returns void
d0d272e7 1054 */
eaeaf964 1055function print_textfield ($name, $value, $alt = '',$size=50,$maxlength=0, $return=false) {
d0d272e7 1056
1057 static $idcounter = 0;
1058
1059 if (empty($name)) {
1060 $name = 'unnamed';
1061 }
1062
1063 if (empty($alt)) {
1064 $alt = 'textfield';
1065 }
1066
1067 if (!empty($maxlength)) {
1068 $maxlength = ' maxlength="'.$maxlength.'" ';
1069 }
1070
ce432524 1071 $htmlid = 'auto-tf'.sprintf('%04d', ++$idcounter);
d0d272e7 1072 $output = '<span class="textfield '.$name."\">";
1073 $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="text" value="'.$value.'" size="'.$size.'" '.$maxlength.' alt="'.$alt.'" />';
c06c8492 1074
d0d272e7 1075 $output .= '</span>'."\n";
1076
1077 if (empty($return)) {
1078 echo $output;
1079 } else {
1080 return $output;
1081 }
1082
1083}
1084
1085
d48b00b4 1086/**
449611af 1087 * Implements a complete little form with a dropdown menu.
1088 *
1089 * When JavaScript is on selecting an option from the dropdown automatically
1090 * submits the form (while avoiding the usual acessibility problems with this appoach).
1091 * With JavaScript off, a 'Go' button is printed.
d48b00b4 1092 *
449611af 1093 * @todo Finish documenting this function
1094 *
1095 * @global object
1096 * @global object
fa583f5f 1097 * @param string $baseurl The target URL up to the point of the variable that changes
1098 * @param array $options A list of value-label pairs for the popup list
1099 * @param string $formid id for the control. Must be unique on the page. Used in the HTML.
1100 * @param string $selected The option that is initially selected
89dcb99d 1101 * @param string $nothing The label for the "no choice" option
1102 * @param string $help The name of a help page if help is required
1103 * @param string $helptext The name of the label for the help button
fa583f5f 1104 * @param boolean $return Indicates whether the function should return the HTML
89dcb99d 1105 * as a string or echo it directly to the page being rendered
772e78be 1106 * @param string $targetwindow The name of the target page to open the linked page in.
1d75edd0 1107 * @param string $selectlabel Text to place in a [label] element - preferred for accessibility.
fa583f5f 1108 * @param array $optionsextra an array with the same keys as $options. The values are added within the corresponding <option ...> tag.
1109 * @param string $submitvalue Optional label for the 'Go' button. Defaults to get_string('go').
1110 * @param boolean $disabled If true, the menu will be displayed disabled.
ad70c470 1111 * @param boolean $showbutton If true, the button will always be shown even if JavaScript is available
449611af 1112 * @return string|void If $return=true returns string, else echo's and returns void
89dcb99d 1113 */
fa583f5f 1114function popup_form($baseurl, $options, $formid, $selected='', $nothing='choose', $help='', $helptext='', $return=false,
ad70c470 1115 $targetwindow='self', $selectlabel='', $optionsextra=NULL, $submitvalue='', $disabled=false, $showbutton=false) {
eee5d9bb 1116 global $CFG, $SESSION;
b0542a1e 1117 static $go, $choose; /// Locally cached, in case there's lots on a page
87180677 1118
6f9f3b69 1119 if (empty($options)) {
1120 return '';
1121 }
0d0baabf 1122
fa583f5f 1123 if (empty($submitvalue)){
1124 if (!isset($go)) {
1125 $go = get_string('go');
1126 $submitvalue=$go;
1127 }
037dcbb6 1128 }
b0ccd3fb 1129 if ($nothing == 'choose') {
037dcbb6 1130 if (!isset($choose)) {
1131 $choose = get_string('choose');
1132 }
1133 $nothing = $choose.'...';
618b22c5 1134 }
fa583f5f 1135 if ($disabled) {
3fb00221 1136 $disabled = ' disabled="disabled"';
fa583f5f 1137 } else {
1138 $disabled = '';
1139 }
618b22c5 1140
9d3c6ee5 1141 // changed reference to document.getElementById('id_abc') instead of document.abc
1142 // MDL-7861
4f24b3e3 1143 $output = '<form action="'.$CFG->wwwroot.'/course/jumpto.php"'.
fa738731 1144 ' method="get" '.
1145 $CFG->frametarget.
ee5567d1 1146 ' id="'.$formid.'"'.
c4d951e1 1147 ' class="popupform">';
4f24b3e3 1148 if ($help) {
1149 $button = helpbutton($help, $helptext, 'moodle', true, false, '', true);
1150 } else {
1151 $button = '';
1152 }
037dcbb6 1153
fcf9577a 1154 if ($selectlabel) {
1155 $selectlabel = '<label for="'.$formid.'_jump">'.$selectlabel.'</label>';
1156 }
1157
ad70c470 1158 if ($showbutton) {
1159 // Using the no-JavaScript version
1160 $javascript = '';
1161 } else if (check_browser_version('MSIE') || (check_browser_version('Opera') && !check_browser_operating_system("Linux"))) {
1162 //IE and Opera fire the onchange when ever you move into a dropdown list with the keyboard.
1163 //onfocus will call a function inside dropdown.js. It fixes this IE/Opera behavior.
1164 //Note: There is a bug on Opera+Linux with the javascript code (first mouse selection is inactive),
1165 //so we do not fix the Opera behavior on Linux
1166 $javascript = ' onfocus="initSelect(\''.$formid.'\','.$targetwindow.')"';
1167 } else {
1168 //Other browser
1169 $javascript = ' onchange="'.$targetwindow.
1170 '.location=document.getElementById(\''.$formid.
1171 '\').jump.options[document.getElementById(\''.
1172 $formid.'\').jump.selectedIndex].value;"';
795a08ad 1173 }
ad70c470 1174
3fb00221 1175 $output .= '<div style="white-space:nowrap">'.$selectlabel.$button.'<select id="'.$formid.'_jump" name="jump"'.$javascript.$disabled.'>'."\n";
73f7ad71 1176
b0ccd3fb 1177 if ($nothing != '') {
ffadf71b 1178 $selectlabeloption = '';
1179 if ($selected=='') {
1180 $selectlabeloption = ' selected="selected"';
1181 }
1182 foreach ($options as $value => $label) { //if one of the options is the empty value, don't make this the default
1183 if ($value == '') {
1184 $selected = '';
1185 }
1186 }
1187 $output .= " <option value=\"javascript:void(0)\"$selectlabeloption>$nothing</option>\n";
f9903ed0 1188 }
1189
72b4e283 1190 $inoptgroup = false;
89bfeee0 1191
f9903ed0 1192 foreach ($options as $value => $label) {
772e78be 1193
89bfeee0 1194 if ($label == '--') { /// we are ending previous optgroup
1195 /// Check to see if we already have a valid open optgroup
1196 /// XHTML demands that there be at least 1 option within an optgroup
1197 if ($inoptgroup and (count($optgr) > 1) ) {
1198 $output .= implode('', $optgr);
1199 $output .= ' </optgroup>';
1200 }
1201 $optgr = array();
1202 $inoptgroup = false;
1203 continue;
1204 } else if (substr($label,0,2) == '--') { /// we are starting a new optgroup
772e78be 1205
2a003a90 1206 /// Check to see if we already have a valid open optgroup
1207 /// XHTML demands that there be at least 1 option within an optgroup
1208 if ($inoptgroup and (count($optgr) > 1) ) {
1209 $output .= implode('', $optgr);
72b4e283 1210 $output .= ' </optgroup>';
72b4e283 1211 }
2a003a90 1212
1213 unset($optgr);
1214 $optgr = array();
1215
dacb47c0 1216 $optgr[] = ' <optgroup label="'. s(format_string(substr($label,2))) .'">'; // Plain labels
772e78be 1217
2a003a90 1218 $inoptgroup = true; /// everything following will be in an optgroup
3326450b 1219 continue;
772e78be 1220
d897cae4 1221 } else {
fd78420b 1222 if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()]))
1223 {
fa583f5f 1224 $url = $SESSION->sid_process_url( $baseurl . $value );
fd78420b 1225 } else
1226 {
fa583f5f 1227 $url=$baseurl . $value;
fd78420b 1228 }
1229 $optstr = ' <option value="' . $url . '"';
772e78be 1230
d897cae4 1231 if ($value == $selected) {
2a003a90 1232 $optstr .= ' selected="selected"';
1233 }
772e78be 1234
b6b354e3 1235 if (!empty($optionsextra[$value])) {
1236 $optstr .= ' '.$optionsextra[$value];
1237 }
1238
2a003a90 1239 if ($label) {
1240 $optstr .= '>'. $label .'</option>' . "\n";
1241 } else {
1242 $optstr .= '>'. $value .'</option>' . "\n";
1243 }
772e78be 1244
2a003a90 1245 if ($inoptgroup) {
1246 $optgr[] = $optstr;
1247 } else {
1248 $output .= $optstr;
d897cae4 1249 }
f9903ed0 1250 }
772e78be 1251
f9903ed0 1252 }
2a003a90 1253
1254 /// catch the final group if not closed
1255 if ($inoptgroup and count($optgr) > 1) {
1256 $output .= implode('', $optgr);
72b4e283 1257 $output .= ' </optgroup>';
1258 }
2a003a90 1259
b0ccd3fb 1260 $output .= '</select>';
3435f39b 1261 $output .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
ad70c470 1262 if (!$showbutton) {
1263 $output .= '<div id="noscript'.$formid.'" style="display: inline;">';
1264 }
fa583f5f 1265 $output .= '<input type="submit" value="'.$submitvalue.'" '.$disabled.' /></div>';
ad70c470 1266 if (!$showbutton) {
1267 $output .= '<script type="text/javascript">'.
1268 "\n//<![CDATA[\n".
1269 'document.getElementById("noscript'.$formid.'").style.display = "none";'.
1270 "\n//]]>\n".'</script>';
ad70c470 1271 }
9cef0c27 1272 $output .= '</div></form>';
d897cae4 1273
1274 if ($return) {
4f24b3e3 1275 return $output;
d897cae4 1276 } else {
4f24b3e3 1277 echo $output;
d897cae4 1278 }
f9903ed0 1279}
1280
1281
d48b00b4 1282/**
1283 * Validates an email to make sure it makes sense.
1284 *
1285 * @param string $address The email address to validate.
1286 * @return boolean
1287 */
89dcb99d 1288function validate_email($address) {
d48b00b4 1289
78fbaeae 1290 return (ereg('^[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+'.
1291 '(\.[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+)*'.
f9903ed0 1292 '@'.
1293 '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.
1294 '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$',
1295 $address));
1296}
1297
690f358b 1298/**
1299 * Extracts file argument either from file parameter or PATH_INFO
11e7b506 1300 * Note: $scriptname parameter is not needed anymore
690f358b 1301 *
449611af 1302 * @global string
1303 * @uses $_SERVER
1304 * @uses PARAM_PATH
690f358b 1305 * @return string file path (only safe characters)
1306 */
11e7b506 1307function get_file_argument() {
1308 global $SCRIPT;
690f358b 1309
690f358b 1310 $relativepath = optional_param('file', FALSE, PARAM_PATH);
1311
1312 // then try extract file from PATH_INFO (slasharguments method)
11e7b506 1313 if ($relativepath === false and isset($_SERVER['PATH_INFO']) and $_SERVER['PATH_INFO'] !== '') {
690f358b 1314 // check that PATH_INFO works == must not contain the script name
11e7b506 1315 if (strpos($_SERVER['PATH_INFO'], $SCRIPT) === false) {
1316 $relativepath = clean_param(urldecode($_SERVER['PATH_INFO']), PARAM_PATH);
690f358b 1317 }
1318 }
1319
11e7b506 1320 // note: we are not using any other way because they are not compatible with unicode file names ;-)
690f358b 1321
1322 return $relativepath;
1323}
1324
d48b00b4 1325/**
89dcb99d 1326 * Just returns an array of text formats suitable for a popup menu
d48b00b4 1327 *
89dcb99d 1328 * @uses FORMAT_MOODLE
1329 * @uses FORMAT_HTML
1330 * @uses FORMAT_PLAIN
89dcb99d 1331 * @uses FORMAT_MARKDOWN
1332 * @return array
d48b00b4 1333 */
0095d5cd 1334function format_text_menu() {
d48b00b4 1335
b0ccd3fb 1336 return array (FORMAT_MOODLE => get_string('formattext'),
1337 FORMAT_HTML => get_string('formathtml'),
1338 FORMAT_PLAIN => get_string('formatplain'),
b0ccd3fb 1339 FORMAT_MARKDOWN => get_string('formatmarkdown'));
0095d5cd 1340}
1341
d48b00b4 1342/**
1343 * Given text in a variety of format codings, this function returns
772e78be 1344 * the text as safe HTML.
d48b00b4 1345 *
c5659019 1346 * This function should mainly be used for long strings like posts,
e8276c10 1347 * answers, glossary items etc. For short strings @see format_string().
1348 *
449611af 1349 * @todo Finish documenting this function
1350 *
1351 * @global object
1352 * @global object
1353 * @global object
1354 * @global object
89dcb99d 1355 * @uses FORMAT_MOODLE
1356 * @uses FORMAT_HTML
1357 * @uses FORMAT_PLAIN
1358 * @uses FORMAT_WIKI
1359 * @uses FORMAT_MARKDOWN
449611af 1360 * @uses CLI_SCRIPT
1361 * @staticvar array $croncache
89dcb99d 1362 * @param string $text The text to be formatted. This is raw text originally from user input.
772e78be 1363 * @param int $format Identifier of the text format to be used
449611af 1364 * [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN]
1365 * @param object $options ?
1366 * @param int $courseid The courseid to use, defaults to $COURSE->courseid
89dcb99d 1367 * @return string
d48b00b4 1368 */
7d8a3cb0 1369function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
e3e40b43 1370 global $CFG, $COURSE, $DB, $PAGE;
c4ae4fa1 1371
1cc54a45 1372 static $croncache = array();
795a08ad 1373
9e3f34d1 1374 $hashstr = '';
1cc54a45 1375
d53ca6ad 1376 if ($text === '') {
1377 return ''; // no need to do any filters and cleaning
1378 }
1379
cbc2b5df 1380 if (!isset($options->trusted)) {
1381 $options->trusted = false;
7d8a3cb0 1382 }
e7a47153 1383 if (!isset($options->noclean)) {
cbc2b5df 1384 if ($options->trusted and trusttext_active()) {
1385 // no cleaning if text trusted and noclean not specified
1386 $options->noclean=true;
1387 } else {
1388 $options->noclean=false;
1389 }
e7a47153 1390 }
a17c57b5 1391 if (!isset($options->nocache)) {
1392 $options->nocache=false;
1393 }
e7a47153 1394 if (!isset($options->smiley)) {
1395 $options->smiley=true;
1396 }
1397 if (!isset($options->filter)) {
1398 $options->filter=true;
1399 }
1400 if (!isset($options->para)) {
1401 $options->para=true;
1402 }
1403 if (!isset($options->newlines)) {
1404 $options->newlines=true;
f0aa2fed 1405 }
c4ae4fa1 1406 if (empty($courseid)) {
dcf6d93c 1407 $courseid = $COURSE->id;
c4ae4fa1 1408 }
a751a4e5 1409
ccc161f8 1410 if ($options->filter) {
1411 $filtermanager = filter_manager::instance();
1412 } else {
1413 $filtermanager = new null_filter_manager();
9e3f34d1 1414 }
e3e40b43 1415 $context = $PAGE->context;
ccc161f8 1416
a17c57b5 1417 if (!empty($CFG->cachetext) and empty($options->nocache)) {
ccc161f8 1418 $hashstr .= $text.'-'.$filtermanager->text_filtering_hash($context, $courseid).'-'.(int)$courseid.'-'.current_language().'-'.
cbc2b5df 1419 (int)$format.(int)$options->trusted.(int)$options->noclean.(int)$options->smiley.
ccc161f8 1420 (int)$options->filter.(int)$options->para.(int)$options->newlines;
1cc54a45 1421
9e3f34d1 1422 $time = time() - $CFG->cachetext;
795a08ad 1423 $md5key = md5($hashstr);
a91b910e 1424 if (CLI_SCRIPT) {
1cc54a45 1425 if (isset($croncache[$md5key])) {
1426 return $croncache[$md5key];
1427 }
1428 }
1429
f33e1ed4 1430 if ($oldcacheitem = $DB->get_record('cache_text', array('md5key'=>$md5key), '*', true)) {
a9743837 1431 if ($oldcacheitem->timemodified >= $time) {
a91b910e 1432 if (CLI_SCRIPT) {
1cc54a45 1433 if (count($croncache) > 150) {
5087c945 1434 reset($croncache);
1435 $key = key($croncache);
1436 unset($croncache[$key]);
1cc54a45 1437 }
1438 $croncache[$md5key] = $oldcacheitem->formattedtext;
1439 }
a9743837 1440 return $oldcacheitem->formattedtext;
1441 }
e7a47153 1442 }
1443 }
1444
0095d5cd 1445 switch ($format) {
73f8658c 1446 case FORMAT_HTML:
7d8a3cb0 1447 if ($options->smiley) {
e7a47153 1448 replace_smilies($text);
1449 }
7d8a3cb0 1450 if (!$options->noclean) {
5c6347ce 1451 $text = clean_text($text, FORMAT_HTML);
9d40806d 1452 }
ccc161f8 1453 $text = $filtermanager->filter_text($text, $context, $courseid);
73f8658c 1454 break;
1455
6901fa79 1456 case FORMAT_PLAIN:
5c6347ce 1457 $text = s($text); // cleans dangerous JS
ab892a4f 1458 $text = rebuildnolinktag($text);
b0ccd3fb 1459 $text = str_replace(' ', '&nbsp; ', $text);
6901fa79 1460 $text = nl2br($text);
6901fa79 1461 break;
1462
d342c763 1463 case FORMAT_WIKI:
6a6495ff 1464 // this format is deprecated
572fe9ab 1465 $text = '<p>NOTICE: Wiki-like formatting has been removed from Moodle. You should not be seeing
1466 this message as all texts should have been converted to Markdown format instead.
ce50cc70 1467 Please post a bug report to http://moodle.org/bugs with information about where you
e7a47153 1468 saw this message.</p>'.s($text);
d342c763 1469 break;
1470
e7cdcd18 1471 case FORMAT_MARKDOWN:
1472 $text = markdown_to_html($text);
7d8a3cb0 1473 if ($options->smiley) {
e7a47153 1474 replace_smilies($text);
1475 }
7d8a3cb0 1476 if (!$options->noclean) {
5c6347ce 1477 $text = clean_text($text, FORMAT_HTML);
9d40806d 1478 }
ccc161f8 1479 $text = $filtermanager->filter_text($text, $context, $courseid);
e7cdcd18 1480 break;
1481
73f8658c 1482 default: // FORMAT_MOODLE or anything else
b7a3d3b2 1483 $text = text_to_html($text, $options->smiley, $options->para, $options->newlines);
7d8a3cb0 1484 if (!$options->noclean) {
5c6347ce 1485 $text = clean_text($text, FORMAT_HTML);
9d40806d 1486 }
ccc161f8 1487 $text = $filtermanager->filter_text($text, $context, $courseid);
0095d5cd 1488 break;
0095d5cd 1489 }
f0aa2fed 1490
ccc161f8 1491 // Warn people that we have removed this old mechanism, just in case they
1492 // were stupid enough to rely on it.
1493 if (isset($CFG->currenttextiscacheable)) {
1494 debugging('Once upon a time, Moodle had a truly evil use of global variables ' .
1495 'called $CFG->currenttextiscacheable. The good news is that this no ' .
1496 'longer exists. The bad news is that you seem to be using a filter that '.
1497 'relies on it. Please seek out and destroy that filter code.', DEBUG_DEVELOPER);
1498 }
1499
1500 if (empty($options->nocache) and !empty($CFG->cachetext)) {
a91b910e 1501 if (CLI_SCRIPT) {
1cc54a45 1502 // special static cron cache - no need to store it in db if its not already there
1503 if (count($croncache) > 150) {
5087c945 1504 reset($croncache);
1505 $key = key($croncache);
1506 unset($croncache[$key]);
1cc54a45 1507 }
1508 $croncache[$md5key] = $text;
1509 return $text;
1510 }
1511
f47f4659 1512 $newcacheitem = new object();
a9743837 1513 $newcacheitem->md5key = $md5key;
f33e1ed4 1514 $newcacheitem->formattedtext = $text;
a9743837 1515 $newcacheitem->timemodified = time();
1516 if ($oldcacheitem) { // See bug 4677 for discussion
1517 $newcacheitem->id = $oldcacheitem->id;
f6949ddb 1518 try {
1519 $DB->update_record('cache_text', $newcacheitem); // Update existing record in the cache table
1520 } catch (dml_exception $e) {
1521 // It's unlikely that the cron cache cleaner could have
1522 // deleted this entry in the meantime, as it allows
1523 // some extra time to cover these cases.
1524 }
a9743837 1525 } else {
f6949ddb 1526 try {
1527 $DB->insert_record('cache_text', $newcacheitem); // Insert a new record in the cache table
1528 } catch (dml_exception $e) {
1529 // Again, it's possible that another user has caused this
1530 // record to be created already in the time that it took
1531 // to traverse this function. That's OK too, as the
1532 // call above handles duplicate entries, and eventually
1533 // the cron cleaner will delete them.
1534 }
a9743837 1535 }
f0aa2fed 1536 }
1537
1538 return $text;
0095d5cd 1539}
1540
449611af 1541/**
1542 * Converts the text format from the value to the 'internal'
1543 * name or vice versa.
c06c8492 1544 *
449611af 1545 * $key can either be the value or the name and you get the other back.
1546 *
1547 * @uses FORMAT_MOODLE
1548 * @uses FORMAT_HTML
1549 * @uses FORMAT_PLAIN
1550 * @uses FORMAT_MARKDOWN
1551 * @param mixed $key int 0-4 or string one of 'moodle','html','plain','markdown'
1552 * @return mixed as above but the other way around!
4a28b5ca 1553 */
1554function text_format_name( $key ) {
1555 $lookup = array();
1556 $lookup[FORMAT_MOODLE] = 'moodle';
1557 $lookup[FORMAT_HTML] = 'html';
1558 $lookup[FORMAT_PLAIN] = 'plain';
1559 $lookup[FORMAT_MARKDOWN] = 'markdown';
1560 $value = "error";
1561 if (!is_numeric($key)) {
1562 $key = strtolower( $key );
1563 $value = array_search( $key, $lookup );
1564 }
1565 else {
1566 if (isset( $lookup[$key] )) {
1567 $value = $lookup[ $key ];
1568 }
1569 }
1570 return $value;
1571}
1572
109e3cb2 1573/**
1574 * Resets all data related to filters, called during upgrade or when filter settings change.
449611af 1575 *
1576 * @global object
1577 * @global object
109e3cb2 1578 * @return void
1579 */
1580function reset_text_filters_cache() {
8618fd2a 1581 global $CFG, $DB;
109e3cb2 1582
8618fd2a 1583 $DB->delete_records('cache_text');
109e3cb2 1584 $purifdir = $CFG->dataroot.'/cache/htmlpurifier';
1585 remove_dir($purifdir, true);
1586}
473d29eb 1587
449611af 1588/**
1589 * Given a simple string, this function returns the string
1590 * processed by enabled string filters if $CFG->filterall is enabled
e8276c10 1591 *
449611af 1592 * This function should be used to print short strings (non html) that
1593 * need filter processing e.g. activity titles, post subjects,
1594 * glossary concepts.
7b2c5e72 1595 *
449611af 1596 * @global object
1597 * @global object
1598 * @global object
1599 * @staticvar bool $strcache
1600 * @param string $string The string to be filtered.
1601 * @param boolean $striplinks To strip any link in the result text.
1602 Moodle 1.8 default changed from false to true! MDL-8713
1603 * @param int $courseid Current course as filters can, potentially, use it
1604 * @return string
7b2c5e72 1605 */
ccc161f8 1606function format_string($string, $striplinks=true, $courseid=NULL ) {
e3e40b43 1607 global $CFG, $COURSE, $PAGE;
38701b69 1608
2a3affe9 1609 //We'll use a in-memory cache here to speed up repeated strings
473d29eb 1610 static $strcache = false;
1611
38701b69 1612 if ($strcache === false or count($strcache) > 2000 ) { // this number might need some tuning to limit memory usage in cron
473d29eb 1613 $strcache = array();
1614 }
84e3d2cc 1615
38701b69 1616 //init course id
a97e0ccb 1617 if (empty($courseid)) {
38701b69 1618 $courseid = $COURSE->id;
1619 }
1620
2a3affe9 1621 //Calculate md5
38701b69 1622 $md5 = md5($string.'<+>'.$striplinks.'<+>'.$courseid.'<+>'.current_language());
2a3affe9 1623
1624 //Fetch from cache if possible
38701b69 1625 if (isset($strcache[$md5])) {
2a3affe9 1626 return $strcache[$md5];
1627 }
1628
dacb47c0 1629 // First replace all ampersands not followed by html entity code
6dbcacee 1630 // Regular expression moved to its own method for easier unit testing
1631 $string = replace_ampersands_not_followed_by_entity($string);
268ddd50 1632
14d20de1 1633 if (!empty($CFG->filterall) && $CFG->version >= 2009040600) { // Avoid errors during the upgrade to the new system.
e3e40b43 1634 $context = $PAGE->context;
ccc161f8 1635 $string = filter_manager::instance()->filter_string($string, $context, $courseid);
7b2c5e72 1636 }
84e3d2cc 1637
9fbed9c9 1638 // If the site requires it, strip ALL tags from this string
1639 if (!empty($CFG->formatstringstriptags)) {
1640 $string = strip_tags($string);
1641
408d5327 1642 } else {
1643 // Otherwise strip just links if that is required (default)
1644 if ($striplinks) { //strip links in string
31c2087d 1645 $string = strip_links($string);
408d5327 1646 }
1647 $string = clean_text($string);
3e6691ee 1648 }
1649
2a3affe9 1650 //Store to cache
1651 $strcache[$md5] = $string;
84e3d2cc 1652
7b2c5e72 1653 return $string;
1654}
1655
6dbcacee 1656/**
1657 * Given a string, performs a negative lookahead looking for any ampersand character
1658 * that is not followed by a proper HTML entity. If any is found, it is replaced
1659 * by &amp;. The string is then returned.
1660 *
1661 * @param string $string
1662 * @return string
1663 */
1664function replace_ampersands_not_followed_by_entity($string) {
1665 return preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&amp;", $string);
1666}
1667
31c2087d 1668/**
1669 * Given a string, replaces all <a>.*</a> by .* and returns the string.
1670 *
1671 * @param string $string
1672 * @return string
1673 */
1674function strip_links($string) {
1675 return preg_replace('/(<a\s[^>]+?>)(.+?)(<\/a>)/is','$2',$string);
1676}
1677
1678/**
1679 * This expression turns links into something nice in a text format. (Russell Jungwirth)
1680 *
1681 * @param string $string
1682 * @return string
1683 */
1684function wikify_links($string) {
1685 return preg_replace('~(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)~i','$3 [ $2 ]', $string);
1686}
1687
1688/**
1689 * Replaces non-standard HTML entities
1690 *
1691 * @param string $string
1692 * @return string
1693 */
1694function fix_non_standard_entities($string) {
1695 $text = preg_replace('/(&#[0-9]+)(;?)/', '$1;', $string);
1696 $text = preg_replace('/(&#x[0-9a-fA-F]+)(;?)/', '$1;', $text);
1697 return $text;
1698}
1699
d48b00b4 1700/**
1701 * Given text in a variety of format codings, this function returns
1702 * the text as plain text suitable for plain email.
d48b00b4 1703 *
89dcb99d 1704 * @uses FORMAT_MOODLE
1705 * @uses FORMAT_HTML
1706 * @uses FORMAT_PLAIN
1707 * @uses FORMAT_WIKI
1708 * @uses FORMAT_MARKDOWN
1709 * @param string $text The text to be formatted. This is raw text originally from user input.
772e78be 1710 * @param int $format Identifier of the text format to be used
449611af 1711 * [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN]
89dcb99d 1712 * @return string
d48b00b4 1713 */
d342c763 1714function format_text_email($text, $format) {
d342c763 1715
1716 switch ($format) {
1717
1718 case FORMAT_PLAIN:
1719 return $text;
1720 break;
1721
1722 case FORMAT_WIKI:
1723 $text = wiki_to_html($text);
31c2087d 1724 $text = wikify_links($text);
7c55a29b 1725 return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
d342c763 1726 break;
1727
6ff45b59 1728 case FORMAT_HTML:
1729 return html_to_text($text);
1730 break;
1731
e7cdcd18 1732 case FORMAT_MOODLE:
1733 case FORMAT_MARKDOWN:
67ccec43 1734 default:
31c2087d 1735 $text = wikify_links($text);
7c55a29b 1736 return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
d342c763 1737 break;
1738 }
1739}
0095d5cd 1740
d48b00b4 1741/**
1742 * Given some text in HTML format, this function will pass it
ccc161f8 1743 * through any filters that have been configured for this context.
d48b00b4 1744 *
449611af 1745 * @global object
1746 * @global object
1747 * @global object
89dcb99d 1748 * @param string $text The text to be passed through format filters
ccc161f8 1749 * @param int $courseid The current course.
1750 * @return string the filtered string.
d48b00b4 1751 */
c4ae4fa1 1752function filter_text($text, $courseid=NULL) {
e3e40b43 1753 global $CFG, $COURSE, $PAGE;
dcf6d93c 1754
1755 if (empty($courseid)) {
1756 $courseid = $COURSE->id; // (copied from format_text)
1757 }
e67b9e31 1758
e3e40b43 1759 $context = $PAGE->context;
9fbed9c9 1760
ccc161f8 1761 return filter_manager::instance()->filter_text($text, $context, $courseid);
cbdfb929 1762}
dc5c2bd9 1763/**
1764 * Formats activity intro text
449611af 1765 *
1766 * @global object
1767 * @uses CONTEXT_MODULE
dc5c2bd9 1768 * @param string $module name of module
1769 * @param object $activity instance of activity
1770 * @param int $cmid course module id
43b44d5e 1771 * @param bool $filter filter resulting html text
dc5c2bd9 1772 * @return text
1773 */
43b44d5e 1774function format_module_intro($module, $activity, $cmid, $filter=true) {
ac3668bf 1775 global $CFG;
1776 require_once("$CFG->libdir/filelib.php");
43b44d5e 1777 $options = (object)array('noclean'=>true, 'para'=>false, 'filter'=>false);
dc5c2bd9 1778 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7d2948bd 1779 $intro = file_rewrite_pluginfile_urls($activity->intro, 'pluginfile.php', $context->id, $module.'_intro', null);
ac3668bf 1780 return trim(format_text($intro, $activity->introformat, $options));
dc5c2bd9 1781}
cbdfb929 1782
7d8a3cb0 1783/**
cbc2b5df 1784 * Legacy function, used for cleaning of old forum and glossary text only.
449611af 1785 *
1786 * @global object
5ce73257 1787 * @param string $text text that may contain TRUSTTEXT marker
7d8a3cb0 1788 * @return text without any TRUSTTEXT marker
1789 */
1790function trusttext_strip($text) {
1791 global $CFG;
1792
1793 while (true) { //removing nested TRUSTTEXT
5ce73257 1794 $orig = $text;
cbc2b5df 1795 $text = str_replace('#####TRUSTTEXT#####', '', $text);
7d8a3cb0 1796 if (strcmp($orig, $text) === 0) {
1797 return $text;
1798 }
1799 }
1800}
1801
cbc2b5df 1802/**
1803 * Must be called before editing of all texts
1804 * with trust flag. Removes all XSS nasties
1805 * from texts stored in database if needed.
449611af 1806 *
cbc2b5df 1807 * @param object $object data object with xxx, xxxformat and xxxtrust fields
1808 * @param string $field name of text field
1809 * @param object $context active context
1810 * @return object updated $object
1811 */
1812function trusttext_pre_edit($object, $field, $context) {
1813 $trustfield = $field.'trust';
1814 $formatfield = $field.'format';
1815
1816 if (!$object->$trustfield or !trusttext_trusted($context)) {
1817 $object->$field = clean_text($object->$field, $object->$formatfield);
1818 }
1819
1820 return $object;
1821}
1822
1823/**
449611af 1824 * Is current user trusted to enter no dangerous XSS in this context?
1825 *
cbc2b5df 1826 * Please note the user must be in fact trusted everywhere on this server!!
449611af 1827 *
1828 * @param object $context
cbc2b5df 1829 * @return bool true if user trusted
1830 */
1831function trusttext_trusted($context) {
1832 return (trusttext_active() and has_capability('moodle/site:trustcontent', $context));
1833}
1834
1835/**
1836 * Is trusttext feature active?
449611af 1837 *
1838 * @global object
1839 * @param object $context
cbc2b5df 1840 * @return bool
1841 */
1842function trusttext_active() {
1843 global $CFG;
1844
1845 return !empty($CFG->enabletrusttext);
1846}
1847
d48b00b4 1848/**
1849 * Given raw text (eg typed in by a user), this function cleans it up
1850 * and removes any nasty tags that could mess up Moodle pages.
1851 *
89dcb99d 1852 * @uses FORMAT_MOODLE
1853 * @uses FORMAT_PLAIN
449611af 1854 * @global string
1855 * @global object
89dcb99d 1856 * @param string $text The text to be cleaned
772e78be 1857 * @param int $format Identifier of the text format to be used
449611af 1858 * [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN]
89dcb99d 1859 * @return string The cleaned up text
d48b00b4 1860 */
3da47524 1861function clean_text($text, $format=FORMAT_MOODLE) {
b7a3cf49 1862
85fbf884 1863 global $ALLOWED_TAGS, $CFG;
1864
e0ac8448 1865 if (empty($text) or is_numeric($text)) {
84e3d2cc 1866 return (string)$text;
e0ac8448 1867 }
3fe3851d 1868
ab9f24ad 1869 switch ($format) {
e7cdcd18 1870 case FORMAT_PLAIN:
8f660562 1871 case FORMAT_MARKDOWN:
e7cdcd18 1872 return $text;
1873
1874 default:
1875
e0ac8448 1876 if (!empty($CFG->enablehtmlpurifier)) {
1877 $text = purify_html($text);
1878 } else {
1879 /// Fix non standard entity notations
31c2087d 1880 $text = fix_non_standard_entities($text);
84e3d2cc 1881
e0ac8448 1882 /// Remove tags that are not allowed
1883 $text = strip_tags($text, $ALLOWED_TAGS);
84e3d2cc 1884
e0ac8448 1885 /// Clean up embedded scripts and , using kses
1886 $text = cleanAttributes($text);
a33c44c4 1887
1888 /// Again remove tags that are not allowed
1889 $text = strip_tags($text, $ALLOWED_TAGS);
1890
e0ac8448 1891 }
7789ffbf 1892
e0ac8448 1893 /// Remove potential script events - some extra protection for undiscovered bugs in our code
6dbcacee 1894 $text = preg_replace("~([^a-z])language([[:space:]]*)=~i", "$1Xlanguage=", $text);
1895 $text = preg_replace("~([^a-z])on([a-z]+)([[:space:]]*)=~i", "$1Xon$2=", $text);
6901fa79 1896
6901fa79 1897 return $text;
0095d5cd 1898 }
b7a3cf49 1899}
f9903ed0 1900
e0ac8448 1901/**
1902 * KSES replacement cleaning function - uses HTML Purifier.
449611af 1903 *
1904 * @global object
1905 * @param string $text The (X)HTML string to purify
e0ac8448 1906 */
1907function purify_html($text) {
1908 global $CFG;
1909
109e3cb2 1910 // this can not be done only once because we sometimes need to reset the cache
eb203ee4 1911 $cachedir = $CFG->dataroot.'/cache/htmlpurifier';
109e3cb2 1912 $status = check_dir_exists($cachedir, true, true);
1913
e0ac8448 1914 static $purifier = false;
eb203ee4 1915 static $config;
109e3cb2 1916 if ($purifier === false) {
eb203ee4 1917 require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.safe-includes.php';
e0ac8448 1918 $config = HTMLPurifier_Config::createDefault();
eb203ee4 1919 $config->set('Core', 'ConvertDocumentToFragment', true);
5adad310 1920 $config->set('Core', 'Encoding', 'UTF-8');
1921 $config->set('HTML', 'Doctype', 'XHTML 1.0 Transitional');
109e3cb2 1922 $config->set('Cache', 'SerializerPath', $cachedir);
e0ac8448 1923 $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));
3c24a391 1924 $config->set('Attr', 'AllowedFrameTargets', array('_blank'));
e0ac8448 1925 $purifier = new HTMLPurifier($config);
1926 }
1927 return $purifier->purify($text);
1928}
1929
d48b00b4 1930/**
89dcb99d 1931 * This function takes a string and examines it for HTML tags.
449611af 1932 *
d48b00b4 1933 * If tags are detected it passes the string to a helper function {@link cleanAttributes2()}
449611af 1934 * which checks for attributes and filters them for malicious content
d48b00b4 1935 *
1936 * @param string $str The string to be examined for html tags
1937 * @return string
1938 */
3bd7ffec 1939function cleanAttributes($str){
4e8f2e6b 1940 $result = preg_replace_callback(
1941 '%(<[^>]*(>|$)|>)%m', #search for html tags
1942 "cleanAttributes2",
3bd7ffec 1943 $str
67ccec43 1944 );
3bd7ffec 1945 return $result;
67ccec43 1946}
1947
d48b00b4 1948/**
1949 * This function takes a string with an html tag and strips out any unallowed
1950 * protocols e.g. javascript:
449611af 1951 *
d48b00b4 1952 * It calls ancillary functions in kses which are prefixed by kses
d48b00b4 1953 *
449611af 1954 * @global object
1955 * @global string
4e8f2e6b 1956 * @param array $htmlArray An array from {@link cleanAttributes()}, containing in its 1st
1957 * element the html to be cleared
d48b00b4 1958 * @return string
1959 */
4e8f2e6b 1960function cleanAttributes2($htmlArray){
3bd7ffec 1961
037dcbb6 1962 global $CFG, $ALLOWED_PROTOCOLS;
b0ccd3fb 1963 require_once($CFG->libdir .'/kses.php');
3bd7ffec 1964
4e8f2e6b 1965 $htmlTag = $htmlArray[1];
037dcbb6 1966 if (substr($htmlTag, 0, 1) != '<') {
3bd7ffec 1967 return '&gt;'; //a single character ">" detected
1968 }
037dcbb6 1969 if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $htmlTag, $matches)) {
67ccec43 1970 return ''; // It's seriously malformed
1971 }
3bd7ffec 1972 $slash = trim($matches[1]); //trailing xhtml slash
67ccec43 1973 $elem = $matches[2]; //the element name
3bd7ffec 1974 $attrlist = $matches[3]; // the list of attributes as a string
1975
037dcbb6 1976 $attrArray = kses_hair($attrlist, $ALLOWED_PROTOCOLS);
3bd7ffec 1977
67ccec43 1978 $attStr = '';
037dcbb6 1979 foreach ($attrArray as $arreach) {
29939bea 1980 $arreach['name'] = strtolower($arreach['name']);
1981 if ($arreach['name'] == 'style') {
1982 $value = $arreach['value'];
1983 while (true) {
1984 $prevvalue = $value;
1985 $value = kses_no_null($value);
1986 $value = preg_replace("/\/\*.*\*\//Us", '', $value);
1987 $value = kses_decode_entities($value);
1988 $value = preg_replace('/(&#[0-9]+)(;?)/', "\\1;", $value);
1989 $value = preg_replace('/(&#x[0-9a-fA-F]+)(;?)/', "\\1;", $value);
1990 if ($value === $prevvalue) {
1991 $arreach['value'] = $value;
1992 break;
1993 }
1994 }
1995 $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']);
1996 $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 1997 $arreach['value'] = preg_replace("/b\s*i\s*n\s*d\s*i\s*n\s*g/i", "Xbinding", $arreach['value']);
b8806ccc 1998 } else if ($arreach['name'] == 'href') {
ee7f231d 1999 //Adobe Acrobat Reader XSS protection
920337d1 2000 $arreach['value'] = preg_replace('/(\.(pdf|fdf|xfdf|xdp|xfd)[^#]*)#.*$/i', '$1', $arreach['value']);
29939bea 2001 }
049bd7db 2002 $attStr .= ' '.$arreach['name'].'="'.$arreach['value'].'"';
3bd7ffec 2003 }
713126cd 2004
3bd7ffec 2005 $xhtml_slash = '';
037dcbb6 2006 if (preg_match('%/\s*$%', $attrlist)) {
67ccec43 2007 $xhtml_slash = ' /';
3bd7ffec 2008 }
b0ccd3fb 2009 return '<'. $slash . $elem . $attStr . $xhtml_slash .'>';
3bd7ffec 2010}
2011
d48b00b4 2012/**
2013 * Replaces all known smileys in the text with image equivalents
2014 *
449611af 2015 * @global object
2016 * @staticvar array $e
2017 * @staticvar array $img
2018 * @staticvar array $emoticons
d48b00b4 2019 * @param string $text Passed by reference. The string to search for smily strings.
2020 * @return string
2021 */
5f350e8f 2022function replace_smilies(&$text) {
183a13c2 2023
2ea9027b 2024 global $CFG;
183a13c2 2025
2026 if (empty($CFG->emoticons)) { /// No emoticons defined, nothing to process here
2027 return;
2028 }
2029
5a144b3a 2030 $lang = current_language();
93c61c18 2031 $emoticonstring = $CFG->emoticons;
69081931 2032 static $e = array();
2033 static $img = array();
93c61c18 2034 static $emoticons = null;
2035
2036 if (is_null($emoticons)) {
2037 $emoticons = array();
2038 if ($emoticonstring) {
2039 $items = explode('{;}', $CFG->emoticons);
2040 foreach ($items as $item) {
2041 $item = explode('{:}', $item);
c5659019 2042 $emoticons[$item[0]] = $item[1];
93c61c18 2043 }
2044 }
2045 }
2046
5a144b3a 2047 if (empty($img[$lang])) { /// After the first time this is not run again
2048 $e[$lang] = array();
2049 $img[$lang] = array();
617778f2 2050 foreach ($emoticons as $emoticon => $image){
fbfc2675 2051 $alttext = get_string($image, 'pix');
4bfa414f 2052 $alttext = preg_replace('/^\[\[(.*)\]\]$/', '$1', $alttext); /// Clean alttext in case there isn't lang string for it.
5a144b3a 2053 $e[$lang][] = $emoticon;
2054 $img[$lang][] = '<img alt="'. $alttext .'" width="15" height="15" src="'. $CFG->pixpath .'/s/'. $image .'.gif" />';
617778f2 2055 }
c0f728ba 2056 }
b7a3cf49 2057
8dcd43f3 2058 // Exclude from transformations all the code inside <script> tags
2059 // Needed to solve Bug 1185. Thanks to jouse 2001 detecting it. :-)
2060 // Based on code from glossary fiter by Williams Castillo.
2061 // - Eloy
2062
2063 // Detect all the <script> zones to take out
2064 $excludes = array();
2065 preg_match_all('/<script language(.+?)<\/script>/is',$text,$list_of_excludes);
2066
2067 // Take out all the <script> zones from text
2068 foreach (array_unique($list_of_excludes[0]) as $key=>$value) {
2069 $excludes['<+'.$key.'+>'] = $value;
2070 }
2071 if ($excludes) {
2072 $text = str_replace($excludes,array_keys($excludes),$text);
2073 }
2074
fbfc2675 2075/// this is the meat of the code - this is run every time
5a144b3a 2076 $text = str_replace($e[$lang], $img[$lang], $text);
8dcd43f3 2077
2078 // Recover all the <script> zones to text
2079 if ($excludes) {
2080 $text = str_replace(array_keys($excludes),$excludes,$text);
2081 }
1a072208 2082}
0095d5cd 2083
740939ec 2084/**
2085 * This code is called from help.php to inject a list of smilies into the
2086 * emoticons help file.
2087 *
449611af 2088 * @global object
2089 * @global object
740939ec 2090 * @return string HTML for a list of smilies.
2091 */
cf615522 2092function get_emoticons_list_for_help_file() {
2093 global $CFG, $SESSION, $PAGE;
740939ec 2094 if (empty($CFG->emoticons)) {
2095 return '';
2096 }
2097
740939ec 2098 $items = explode('{;}', $CFG->emoticons);
2099 $output = '<ul id="emoticonlist">';
2100 foreach ($items as $item) {
2101 $item = explode('{:}', $item);
cf615522 2102 $output .= '<li><img src="' . $CFG->pixpath . '/s/' . $item[1] . '.gif" alt="' .
740939ec 2103 $item[0] . '" /><code>' . $item[0] . '</code></li>';
2104 }
2105 $output .= '</ul>';
2106 if (!empty($SESSION->inserttextform)) {
2107 $formname = $SESSION->inserttextform;
2108 $fieldname = $SESSION->inserttextfield;
2109 } else {
2110 $formname = 'theform';
2111 $fieldname = 'message';
2112 }
fa583f5f 2113
cf615522 2114 $PAGE->requires->yui_lib('event');
2115 $PAGE->requires->js_function_call('emoticons_help.init', array($formname, $fieldname, 'emoticonlist'));
740939ec 2116 return $output;
2117
2118}
2119
89dcb99d 2120/**
2121 * Given plain text, makes it into HTML as nicely as possible.
2122 * May contain HTML tags already
2123 *
449611af 2124 * @global object
89dcb99d 2125 * @param string $text The string to convert.
2126 * @param boolean $smiley Convert any smiley characters to smiley images?
2127 * @param boolean $para If true then the returned string will be wrapped in paragraph tags
2128 * @param boolean $newlines If true then lines newline breaks will be converted to HTML newline breaks.
2129 * @return string
2130 */
2131
b7a3d3b2 2132function text_to_html($text, $smiley=true, $para=true, $newlines=true) {
772e78be 2133///
f9903ed0 2134
27326a3e 2135 global $CFG;
2136
c1d57101 2137/// Remove any whitespace that may be between HTML tags
6dbcacee 2138 $text = preg_replace("~>([[:space:]]+)<~i", "><", $text);
7b3be1b1 2139
c1d57101 2140/// Remove any returns that precede or follow HTML tags
6dbcacee 2141 $text = preg_replace("~([\n\r])<~i", " <", $text);
2142 $text = preg_replace("~>([\n\r])~i", "> ", $text);
7b3be1b1 2143
5f350e8f 2144 convert_urls_into_links($text);
f9903ed0 2145
c1d57101 2146/// Make returns into HTML newlines.
b7a3d3b2 2147 if ($newlines) {
2148 $text = nl2br($text);
2149 }
f9903ed0 2150
c1d57101 2151/// Turn smileys into images.
d69cb7f4 2152 if ($smiley) {
5f350e8f 2153 replace_smilies($text);
d69cb7f4 2154 }
f9903ed0 2155
c1d57101 2156/// Wrap the whole thing in a paragraph tag if required
909f539d 2157 if ($para) {
b0ccd3fb 2158 return '<p>'.$text.'</p>';
909f539d 2159 } else {
2160 return $text;
2161 }
f9903ed0 2162}
2163
d48b00b4 2164/**
2165 * Given Markdown formatted text, make it into XHTML using external function
2166 *
449611af 2167 * @global object
89dcb99d 2168 * @param string $text The markdown formatted text to be converted.
2169 * @return string Converted text
d48b00b4 2170 */
e7cdcd18 2171function markdown_to_html($text) {
e7cdcd18 2172 global $CFG;
2173
b0ccd3fb 2174 require_once($CFG->libdir .'/markdown.php');
e7cdcd18 2175
2176 return Markdown($text);
2177}
2178
d48b00b4 2179/**
89dcb99d 2180 * Given HTML text, make it into plain text using external function
d48b00b4 2181 *
449611af 2182 * @global object
d48b00b4 2183 * @param string $html The text to be converted.
2184 * @return string
2185 */
6ff45b59 2186function html_to_text($html) {
89dcb99d 2187
428aaa29 2188 global $CFG;
6ff45b59 2189
b0ccd3fb 2190 require_once($CFG->libdir .'/html2text.php');
6ff45b59 2191
588acd06 2192 $h2t = new html2text($html);
2193 $result = $h2t->get_text();
07e9a300 2194
977b3d31 2195 return $result;
6ff45b59 2196}
2197
d48b00b4 2198/**
2199 * Given some text this function converts any URLs it finds into HTML links
2200 *
2201 * @param string $text Passed in by reference. The string to be searched for urls.
2202 */
5f350e8f 2203function convert_urls_into_links(&$text) {
5f350e8f 2204/// Make lone URLs into links. eg http://moodle.com/
6dbcacee 2205 $text = preg_replace("~([[:space:]]|^|\(|\[)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])~i",
31c2087d 2206 '$1<a href="$2://$3$4">$2://$3$4</a>', $text);
5f350e8f 2207
2208/// eg www.moodle.com
6dbcacee 2209 $text = preg_replace("~([[:space:]]|^|\(|\[)www\.([^[:space:]]*)([[:alnum:]#?/&=])~i",
31c2087d 2210 '$1<a href="http://www.$2$3">www.$2$3</a>', $text);
5f350e8f 2211}
2212
d48b00b4 2213/**
2214 * This function will highlight search words in a given string
449611af 2215 *
d48b00b4 2216 * It cares about HTML and will not ruin links. It's best to use
2217 * this function after performing any conversions to HTML.
d48b00b4 2218 *
9289e4c9 2219 * @param string $needle The search string. Syntax like "word1 +word2 -word3" is dealt with correctly.
2220 * @param string $haystack The string (HTML) within which to highlight the search terms.
2221 * @param boolean $matchcase whether to do case-sensitive. Default case-insensitive.
2222 * @param string $prefix the string to put before each search term found.
2223 * @param string $suffix the string to put after each search term found.
2224 * @return string The highlighted HTML.
d48b00b4 2225 */
9289e4c9 2226function highlight($needle, $haystack, $matchcase = false,
2227 $prefix = '<span class="highlight">', $suffix = '</span>') {
587c7040 2228
9289e4c9 2229/// Quick bail-out in trivial cases.
587c7040 2230 if (empty($needle) or empty($haystack)) {
69d51d3a 2231 return $haystack;
2232 }
2233
9289e4c9 2234/// Break up the search term into words, discard any -words and build a regexp.
2235 $words = preg_split('/ +/', trim($needle));
2236 foreach ($words as $index => $word) {
2237 if (strpos($word, '-') === 0) {
2238 unset($words[$index]);
2239 } else if (strpos($word, '+') === 0) {
2240 $words[$index] = '\b' . preg_quote(ltrim($word, '+'), '/') . '\b'; // Match only as a complete word.
2241 } else {
2242 $words[$index] = preg_quote($word, '/');
88438a58 2243 }
2244 }
9289e4c9 2245 $regexp = '/(' . implode('|', $words) . ')/u'; // u is do UTF-8 matching.
2246 if (!$matchcase) {
2247 $regexp .= 'i';
88438a58 2248 }
2249
9289e4c9 2250/// Another chance to bail-out if $search was only -words
2251 if (empty($words)) {
2252 return $haystack;
88438a58 2253 }
88438a58 2254
9289e4c9 2255/// Find all the HTML tags in the input, and store them in a placeholders array.
2256 $placeholders = array();
2257 $matches = array();
2258 preg_match_all('/<[^>]*>/', $haystack, $matches);
2259 foreach (array_unique($matches[0]) as $key => $htmltag) {
2260 $placeholders['<|' . $key . '|>'] = $htmltag;
2261 }
9ccdcd97 2262
9289e4c9 2263/// In $hastack, replace each HTML tag with the corresponding placeholder.
2264 $haystack = str_replace($placeholders, array_keys($placeholders), $haystack);
9ccdcd97 2265
9289e4c9 2266/// In the resulting string, Do the highlighting.
2267 $haystack = preg_replace($regexp, $prefix . '$1' . $suffix, $haystack);
9ccdcd97 2268
9289e4c9 2269/// Turn the placeholders back into HTML tags.
2270 $haystack = str_replace(array_keys($placeholders), $placeholders, $haystack);
88438a58 2271
f60e7cfe 2272 return $haystack;
88438a58 2273}
2274
d48b00b4 2275/**
2276 * This function will highlight instances of $needle in $haystack
449611af 2277 *
2278 * It's faster that the above function {@link highlight()} and doesn't care about
d48b00b4 2279 * HTML or anything.
2280 *
2281 * @param string $needle The string to search for
2282 * @param string $haystack The string to search for $needle in
449611af 2283 * @return string The highlighted HTML
d48b00b4 2284 */
88438a58 2285function highlightfast($needle, $haystack) {
5af78ed2 2286
587c7040 2287 if (empty($needle) or empty($haystack)) {
2288 return $haystack;
2289 }
2290
57f1b914 2291 $parts = explode(moodle_strtolower($needle), moodle_strtolower($haystack));
5af78ed2 2292
587c7040 2293 if (count($parts) === 1) {
2294 return $haystack;
2295 }
2296
5af78ed2 2297 $pos = 0;
2298
2299 foreach ($parts as $key => $part) {
2300 $parts[$key] = substr($haystack, $pos, strlen($part));
2301 $pos += strlen($part);
2302
b0ccd3fb 2303 $parts[$key] .= '<span class="highlight">'.substr($haystack, $pos, strlen($needle)).'</span>';
5af78ed2 2304 $pos += strlen($needle);
ab9f24ad 2305 }
5af78ed2 2306
587c7040 2307 return str_replace('<span class="highlight"></span>', '', join('', $parts));
5af78ed2 2308}
2309
2ab4e4b8 2310/**
2311 * Return a string containing 'lang', xml:lang and optionally 'dir' HTML attributes.
2312 * Internationalisation, for print_header and backup/restorelib.
449611af 2313 *
2314 * @param bool $dir Default false
2315 * @return string Attributes
2ab4e4b8 2316 */
2317function get_html_lang($dir = false) {
2318 $direction = '';
2319 if ($dir) {
2320 if (get_string('thisdirection') == 'rtl') {
2321 $direction = ' dir="rtl"';
2322 } else {
2323 $direction = ' dir="ltr"';
2324 }
2325 }
2326 //Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag.
2327 $language = str_replace('_', '-', str_replace('_utf8', '', current_language()));
0946fff4 2328 @header('Content-Language: '.$language);
84e3d2cc 2329 return ($direction.' lang="'.$language.'" xml:lang="'.$language.'"');
2ab4e4b8 2330}
2331
5c355019 2332/**
2333 * Return the markup for the destination of the 'Skip to main content' links.
449611af 2334 * Accessibility improvement for keyboard-only users.
2335 *
2336 * Used in course formats, /index.php and /course/index.php
2337 *
317d5ddc 2338 * @return string HTML element.
5c355019 2339 */
2340function skip_main_destination() {
2341 return '<span id="maincontent"></span>';
2342}
2343
f9903ed0 2344
9fa49e22 2345/// STANDARD WEB PAGE PARTS ///////////////////////////////////////////////////
2346
d48b00b4 2347/**
2348 * Print a standard header
2349 *
449611af 2350 * @global object
2351 * @global object
2352 * @global object
2353 * @global object
2354 * @global string Doesnt appear to be used here
2355 * @global string Doesnt appear to be used here
2356 * @global object
2357 * @global object
2358 * @uses $_SERVER
f1af7aaa 2359 * @param string $title Appears at the top of the window
2360 * @param string $heading Appears at the top of the page
449611af 2361 * @param string $navigation Array of $navlinks arrays (keys: name, link, type) for use as breadcrumbs links
f1af7aaa 2362 * @param string $focus Indicates form element to get cursor focus on load eg inputform.password
2363 * @param string $meta Meta tags to be added to the header
89dcb99d 2364 * @param boolean $cache Should this page be cacheable?
f1af7aaa 2365 * @param string $button HTML code for a button (usually for module editing)
2366 * @param string $menu HTML code for a popup menu
89dcb99d 2367 * @param boolean $usexml use XML for this page
f1af7aaa 2368 * @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc)
2369 * @param bool $return If true, return the visible elements of the header instead of echoing them.
449611af 2370 * @return string|void If return=true then string else void
d48b00b4 2371 */
36b6bcec 2372function print_header ($title='', $heading='', $navigation='', $focus='',
2373 $meta='', $cache=true, $button='&nbsp;', $menu='',
2374 $usexml=false, $bodytags='', $return=false) {
63f3cbbd 2375
c13a5e71 2376 global $USER, $CFG, $THEME, $SESSION, $ME, $SITE, $COURSE, $PAGE;
84e3d2cc 2377
432038e0 2378 if (gettype($navigation) == 'string' && strlen($navigation) != 0 && $navigation != 'home') {
364fffda 2379 debugging("print_header() was sent a string as 3rd ($navigation) parameter. "
8ebb324b 2380 . "This is deprecated in favour of an array built by build_navigation(). Please upgrade your code.", DEBUG_DEVELOPER);
364fffda 2381 }
2382
c13a5e71 2383 $PAGE->set_state(moodle_page::STATE_PRINTING_HEADER);
2384
6ba65fa0 2385 $heading = format_string($heading); // Fix for MDL-8582
84e3d2cc 2386
fbd0c5e3 2387 if (CLI_SCRIPT) {
258d5322 2388 $output = $heading."\n";
2f13f94c 2389 if ($return) {
2390 return $output;
2391 } else {
258d5322 2392 echo $output;
2f13f94c 2393 return;
2394 }
2395 }
2396
c3f55692 2397/// Add the required stylesheets
d74d4f20 2398 $stylesheetshtml = '';
2399 foreach ($CFG->stylesheets as $stylesheet) {
2400 $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
c3f55692 2401 }
d74d4f20 2402 $meta = $stylesheetshtml.$meta;
e89fb61e 2403
84e3d2cc 2404
79f533c3 2405/// Add the meta page from the themes if any were requested
2406
2407 $metapage = '';
2408
2409 if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) {
2410 ob_start();
2411 include_once($CFG->dirroot.'/theme/standard/meta.php');
2412 $metapage .= ob_get_contents();
2413 ob_end_clean();
2414 }
2415
2416 if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) {
36e8c122 2417 if (file_exists($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php')) {
2418 ob_start();
2419 include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php');
2420 $metapage .= ob_get_contents();
2421 ob_end_clean();
2422 }
79f533c3 2423 }
2424
2425 if (!isset($THEME->metainclude) || $THEME->metainclude) {
36e8c122 2426 if (file_exists($CFG->dirroot.'/theme/'.current_theme().'/meta.php')) {
2427 ob_start();
2428 include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php');
2429 $metapage .= ob_get_contents();
2430 ob_end_clean();
2431 }
79f533c3 2432 }
2433
2434 $meta = $meta."\n".$metapage;
cf615522 2435 $meta .= $PAGE->requires->get_head_code();
830c58b7 2436
c77f9f07 2437/// Set up some navigation variables
604c6341 2438
c77f9f07 2439 if (is_newnav($navigation)){
9d378732 2440 $home = false;
ac4feb59 2441 } else {
2442 if ($navigation == 'home') {
2443 $home = true;
2444 $navigation = '';
2445 } else {
2446 $home = false;
2447 }
9fa49e22 2448 }
2449
0d741155 2450/// This is another ugly hack to make navigation elements available to print_footer later
2451 $THEME->title = $title;
2452 $THEME->heading = $heading;
2453 $THEME->navigation = $navigation;
2454 $THEME->button = $button;
2455 $THEME->menu = $menu;
2507b2f5 2456 $navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : '';
0d741155 2457
b0ccd3fb 2458 if ($button == '') {
2459 $button = '&nbsp;';
9fa49e22 2460 }
2461
4fe2250a 2462 if (!empty($CFG->maintenance_enabled)) {
2463 $button = '<a href="'.$CFG->wwwroot.'/'.$CFG->admin.'/settings.php?section=maintenancemode">'.get_string('maintenancemode', 'admin').'</a> '.$button;
a2353e9b 2464 if(!empty($title)) {
2465 $title .= ' - ';
2466 }
2467 $title .= get_string('maintenancemode', 'admin');
2468 }
2469
9fa49e22 2470 if (!$menu and $navigation) {
8a33e371 2471 if (empty($CFG->loginhttps)) {
2472 $wwwroot = $CFG->wwwroot;
2473 } else {
2c3432e6 2474 $wwwroot = str_replace('http:','https:',$CFG->wwwroot);
8a33e371 2475 }
60f9e36e 2476 $menu = user_login_string($COURSE);
9fa49e22 2477 }
67ccec43 2478
b4bac9b6 2479 if (isset($SESSION->justloggedin)) {
2480 unset($SESSION->justloggedin);
2481 if (!empty($CFG->displayloginfailures)) {
8f8ed475 2482 if (!empty($USER->username) and $USER->username != 'guest') {
b4bac9b6 2483 if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) {
2484 $menu .= '&nbsp;<font size="1">';
2485 if (empty($count->accounts)) {
2486 $menu .= get_string('failedloginattempts', '', $count);
2487 } else {
2488 $menu .= get_string('failedloginattemptsall', '', $count);
2489 }
a2e4bf7f 2490 if (has_capability('coursereport/log:view', get_context_instance(CONTEXT_SYSTEM))) {
52af9a35 2491 $menu .= ' (<a href="'.$CFG->wwwroot.'/course/report/log/index.php'.
839f2456 2492 '?chooselog=1&amp;id=1&amp;modid=site_errors">'.get_string('logs').'</a>)';
b4bac9b6 2493 }
2494 $menu .= '</font>';
2495 }
2496 }
2497 }
2498 }
9fa49e22 2499
47037513 2500
bc1bbaf4 2501 $meta = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' .
c6f9234c 2502 "\n" . $meta . "\n";
03fe48e7 2503 if (!$usexml) {
bc1bbaf4 2504 @header('Content-Type: text/html; charset=utf-8');
03fe48e7 2505 }
bc1bbaf4 2506 @header('Content-Script-Type: text/javascript');
2507 @header('Content-Style-Type: text/css');
9fa49e22 2508
6575bfd4 2509 //Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag.
2ab4e4b8 2510 $direction = get_html_lang($dir=true);
ab9f24ad 2511
5debee2d 2512 if ($cache) { // Allow caching on "back" (but not on normal clicks)
2513 @header('Cache-Control: private, pre-check=0, post-check=0, max-age=0');
2514 @header('Pragma: no-cache');
772e78be 2515 @header('Expires: ');
5debee2d 2516 } else { // Do everything we can to always prevent clients and proxies caching
03fe48e7 2517 @header('Cache-Control: no-store, no-cache, must-revalidate');
2518 @header('Cache-Control: post-check=0, pre-check=0', false);
2519 @header('Pragma: no-cache');
5debee2d 2520 @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
2521 @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
03fe48e7 2522
5debee2d 2523 $meta .= "\n<meta http-equiv=\"pragma\" content=\"no-cache\" />";
2524 $meta .= "\n<meta http-equiv=\"expires\" content=\"0\" />";
66a51452 2525 }
5debee2d 2526 @header('Accept-Ranges: none');
66a51452 2527
b0b4ffe1 2528 $currentlanguage = current_language();
2529
5ce73257 2530 if (empty($usexml)) {
27f79fda 2531 $direction = ' xmlns="http://www.w3.org/1999/xhtml"'. $direction; // See debug_header
2532 } else {
8f0cd6ef 2533 $mathplayer = preg_match("/MathPlayer/i", $_SERVER['HTTP_USER_AGENT']);
2534 if(!$mathplayer) {
2535 header('Content-Type: application/xhtml+xml');
2536 }
b0ccd3fb 2537 echo '<?xml version="1.0" ?>'."\n";
66a51452 2538 if (!empty($CFG->xml_stylesheets)) {
b0ccd3fb 2539 $stylesheets = explode(';', $CFG->xml_stylesheets);
66a51452 2540 foreach ($stylesheets as $stylesheet) {
b0ccd3fb 2541 echo '<?xml-stylesheet type="text/xsl" href="'. $CFG->wwwroot .'/'. $stylesheet .'" ?>' . "\n";
66a51452 2542 }
2543 }
b0ccd3fb 2544 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1';
e4576482 2545 if (!empty($CFG->xml_doctype_extra)) {
b0ccd3fb 2546 echo ' plus '. $CFG->xml_doctype_extra;
e4576482 2547 }
b0ccd3fb 2548 echo '//' . strtoupper($currentlanguage) . '" "'. $CFG->xml_dtd .'">'."\n";
8f0cd6ef 2549 $direction = " xmlns=\"http://www.w3.org/1999/xhtml\"
2550 xmlns:math=\"http://www.w3.org/1998/Math/MathML\"
8f0cd6ef 2551 xmlns:xlink=\"http://www.w3.org/1999/xlink\"
2552 $direction";
2553 if($mathplayer) {
2554 $meta .= '<object id="mathplayer" classid="clsid:32F66A20-7614-11D4-BD11-00104BD3F987">' . "\n";
b0ccd3fb 2555 $meta .= '<!--comment required to prevent this becoming an empty tag-->'."\n";
2556 $meta .= '</object>'."\n";
8f0cd6ef 2557 $meta .= '<?import namespace="math" implementation="#mathplayer" ?>' . "\n";
2558 }
9fa49e22 2559 }
2560
7bb6d80f 2561 // Clean up the title
2562
268ddd50 2563 $title = format_string($title); // fix for MDL-8582
2eea2cce 2564 $title = str_replace('"', '&quot;', $title);
2565
7bb6d80f 2566 // Create class and id for this page
d529807a 2567 $pageid = $PAGE->pagetype;
753debd2 2568 $pageclass = $PAGE->bodyclasses;
7bb6d80f 2569 $bodytags .= ' class="'.$pageclass.'" id="'.$pageid.'"';
772e78be 2570
6f8a7d39 2571 ob_start();
ea35ab87 2572 include($CFG->header);
6f8a7d39 2573 $output = ob_get_contents();
2574 ob_end_clean();
b26be9a9 2575
d795bfdb 2576 // container debugging info
2577 $THEME->open_header_containers = open_containers();
2578
317d5ddc 2579 // Skip to main content, see skip_main_destination().
2580 if ($pageid=='course-view' or $pageid=='site-index' or $pageid=='course-index') {
5c355019 2581 $skiplink = '<a class="skip" href="#maincontent">'.get_string('tocontent', 'access').'</a>';
d0b8e40d 2582 if (! preg_match('/(.*<div[^>]+id="page"[^>]*>)(.*)/s', $output, $matches)) {
5c355019 2583 preg_match('/(.*<body.*?>)(.*)/s', $output, $matches);
2584 }
2585 $output = $matches[1]."\n". $skiplink .$matches[2];
317d5ddc 2586 }
5c355019 2587
8af1b1ba 2588 $output = force_strict_header($output);
27f79fda 2589
cdf39255 2590 if (!empty($CFG->messaging)) {
36b6bcec 2591 $output .= message_popup_window();
2592 }
2593
ca70075a 2594 // Add in any extra JavaScript libraries that occurred during the header
cf615522 2595 $output .= $PAGE->requires->get_top_of_body_code();
ca70075a 2596
c13a5e71 2597 $PAGE->set_state(moodle_page::STATE_IN_BODY);
2598
36b6bcec 2599 if ($return) {
2600 return $output;
2601 } else {
2602 echo $output;
cdf39255 2603 }
9fa49e22 2604}
2605
27f79fda 2606/**
2607 * Debugging aid: serve page as 'application/xhtml+xml' where possible,
2608 * and substitute the XHTML strict document type.
2609 * Note, requires the 'xmlns' fix in function print_header above.
449611af 2610 * See: {@link http://tracker.moodle.org/browse/MDL-7883}
2611 *
2612 * @global object
2613 * @uses $_SERVER
2614 * @param string The HTML to apply the strict header to
2615 * @return string The HTML with strict header
27f79fda 2616 */
8af1b1ba 2617function force_strict_header($output) {
27f79fda 2618 global $CFG;
2619 $strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
2620 $xsl = '/lib/xhtml.xsl';
2621
8cf6cb90 2622 if (!headers_sent() && !empty($CFG->xmlstrictheaders)) { // With xml strict headers, the browser will barf
27f79fda 2623 $ctype = 'Content-Type: ';
2624 $prolog= "<?xml version='1.0' encoding='utf-8'?>\n";
2625
5ce73257 2626 if (isset($_SERVER['HTTP_ACCEPT'])
27f79fda 2627 && false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml')) {
2628 //|| false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') //Safari "Entity 'copy' not defined".
2629 // Firefox et al.
2630 $ctype .= 'application/xhtml+xml';
2631 $prolog .= "<!--\n DEBUG: $ctype \n-->\n";
2632
2633 } else if (file_exists($CFG->dirroot.$xsl)
2634 && preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
2635 // XSL hack for IE 5+ on Windows.
2636 //$www_xsl = preg_replace('/(http:\/\/.+?\/).*/', '', $CFG->wwwroot) .$xsl;
2637 $www_xsl = $CFG->wwwroot .$xsl;
2638 $ctype .= 'application/xml';
2639 $prolog .= "<?xml-stylesheet type='text/xsl' href='$www_xsl'?>\n";
2640 $prolog .= "<!--\n DEBUG: $ctype \n-->\n";
2641
2642 } else {
2643 //ELSE: Mac/IE, old/non-XML browsers.
2644 $ctype .= 'text/html';
2645 $prolog = '';
2646 }
2647 @header($ctype.'; charset=utf-8');
2648 $output = $prolog . $output;
2649
2650 // Test parser error-handling.
2651 if (isset($_GET['error'])) {
2652 $output .= "__ TEST: XML well-formed error < __\n";
2653 }
2654 }
8af1b1ba 2655
84dec541 2656 $output = preg_replace('/(<!DOCTYPE.+?>)/s', $strict, $output); // Always change the DOCTYPE to Strict 1.0
8af1b1ba 2657
27f79fda 2658 return $output;
2659}
2660
2661
2662
d48b00b4 2663/**
2664 * This version of print_header is simpler because the course name does not have to be
2665 * provided explicitly in the strings. It can be used on the site page as in courses
2666 * Eventually all print_header could be replaced by print_header_simple
2667 *
449611af 2668 * @global object
2669 * @global object
2670 * @uses SITEID
89dcb99d 2671 * @param string $title Appears at the top of the window
2672 * @param string $heading Appears at the top of the page
2673 * @param string $navigation Premade navigation string (for use as breadcrumbs links)
2674 * @param string $focus Indicates form element to get cursor focus on load eg inputform.password
2675 * @param string $meta Meta tags to be added to the header
2676 * @param boolean $cache Should this page be cacheable?
2677 * @param string $button HTML code for a button (usually for module editing)
2678 * @param string $menu HTML code for a popup menu
2679 * @param boolean $usexml use XML for this page
2680 * @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc)
36b6bcec 2681 * @param bool $return If true, return the visible elements of the header instead of echoing them.
449611af 2682 * @return string|void If $return=true the return string else nothing
d48b00b4 2683 */
b0ccd3fb 2684function print_header_simple($title='', $heading='', $navigation='', $focus='', $meta='',
36b6bcec 2685 $cache=true, $button='&nbsp;', $menu='', $usexml=false, $bodytags='', $return=false) {
90fcc576 2686
5ce73257 2687 global $COURSE, $CFG;
90fcc576 2688
5dc1e0be 2689 // if we have no navigation specified, build it
2690 if( empty($navigation) ){
2691 $navigation = build_navigation('');
2692 }
2693
84e3d2cc 2694 // If old style nav prepend course short name otherwise leave $navigation object alone
ac4feb59 2695 if (!is_newnav($navigation)) {
7243c7c8 2696 if ($COURSE->id != SITEID) {
2697 $shortname = '<a href="'.$CFG->wwwroot.'/course/view.php?id='. $COURSE->id .'">'. $COURSE->shortname .'</a> ->';
2698 $navigation = $shortname.' '.$navigation;
2699 }
ac4feb59 2700 }
2701
2702 $output = print_header($COURSE->shortname .': '. $title, $COURSE->fullname .' '. $heading, $navigation, $focus, $meta,
da4124be 2703 $cache, $button, $menu, $usexml, $bodytags, true);
36b6bcec 2704
2705 if ($return) {
2706 return $output;
2707 } else {
2708 echo $output;
2709 }
90fcc576 2710}
629b5885 2711
2712
d48b00b4 2713/**
2714 * Can provide a course object to make the footer contain a link to
2715 * to the course home page, otherwise the link will go to the site home
449611af 2716 *
2717 * @global object
2718 * @global object
2719 * @global object
2720 * @global object Apparently not used in this function
2721 * @global string
2722 * @global object
d795bfdb 2723 * @param mixed $course course object, used for course link button or
2724 * 'none' means no user link, only docs link
2725 * 'empty' means nothing printed in footer
2726 * 'home' special frontpage footer
2727 * @param object $usercourse course used in user link
2728 * @param boolean $return output as string
2729 * @return mixed string or void
d48b00b4 2730 */
469b6501 2731function print_footer($course=NULL, $usercourse=NULL, $return=false) {
c13a5e71 2732 global $USER, $CFG, $THEME, $COURSE, $SITE, $PAGE;
9fa49e22 2733
1ae083e4 2734 if (defined('ADMIN_EXT_HEADER_PRINTED') and !defined('ADMIN_EXT_FOOTER_PRINTED')) {
635773c6 2735 admin_externalpage_print_footer();
2736 return;
1ae083e4 2737 }
2738
c13a5e71 2739 $PAGE->set_state(moodle_page::STATE_PRINTING_FOOTER);
2740
d795bfdb 2741/// Course links or special footer
9fa49e22 2742 if ($course) {
d795bfdb 2743 if ($course === 'empty') {
2744 // special hack - sometimes we do not want even the docs link in footer
2745 $output = '';
2746 if (!empty($THEME->open_header_containers)) {
2747 for ($i=0; $i<$THEME->open_header_containers; $i++) {
2748 $output .= print_container_end_all(); // containers opened from header
2749 }
2750 } else {
2751 //1.8 theme compatibility
2752 $output .= "\n</div>"; // content div
2753 }
5085a59a 2754 $output .= "\n</div>\n" . $PAGE->requires->get_end_code() . "</body>\n</html>"; // close page div started in header
d795bfdb 2755 if ($return) {
2756 return $output;
2757 } else {
2758 echo $output;
2759 return;
2760 }
2761
2762 } else if ($course === 'none') { // Don't print any links etc
b3a2b9dd 2763 $homelink = '';
2764 $loggedinas = '';
0d741155 2765 $home = false;
d795bfdb 2766
2767 } else if ($course === 'home') { // special case for site home page - please do not remove
c23b0ea1 2768 $course = $SITE;
a77ac3dc 2769 $homelink = '<div class="sitelink">'.
8cd94820 2770 '<a title="Moodle '. $CFG->release .'" href="http://moodle.org/">'.
9ace5094 2771 '<img style="width:100px;height:30px" src="'.$CFG->wwwroot.'/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
0d741155 2772 $home = true;
d795bfdb 2773
9ace5094 2774 } else if ($course === 'upgrade') {
2775 $home = false;
2776 $loggedinas = '';
2777 $homelink = '<div class="sitelink">'.
2778 '<a title="Moodle '. $CFG->target_release .'" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">'.
2779 '<img style="width:100px;height:30px" src="'.$CFG->wwwroot.'/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
2780
9fa49e22 2781 } else {
fa738731 2782 $homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot.
6ba65fa0 2783 '/course/view.php?id='.$course->id.'">'.format_string($course->shortname).'</a></div>';
0d741155 2784 $home = false;
9fa49e22 2785 }
d795bfdb 2786
9fa49e22 2787 } else {
c23b0ea1 2788 $course = $SITE; // Set course as site course by default
fa738731 2789 $homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot.'/">'.get_string('home').'</a></div>';
0d741155 2790 $home = false;
9fa49e22 2791 }
2792
0d741155 2793/// Set up some other navigation links (passed from print_header by ugly hack)
2507b2f5 2794 $menu = isset($THEME->menu) ? str_replace('navmenu', 'navmenufooter', $THEME->menu) : '';
2795 $title = isset($THEME->title) ? $THEME->title : '';
2796 $button = isset($THEME->button) ? $THEME->button : '';
2797 $heading = isset($THEME->heading) ? $THEME->heading : '';
2798 $navigation = isset($THEME->navigation) ? $THEME->navigation : '';
2799 $navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : '';
0d741155 2800
f940ee41 2801
b3a2b9dd 2802/// Set the user link if necessary
2803 if (!$usercourse and is_object($course)) {
1f2eec7b 2804 $usercourse = $course;
2805 }
2806
b3a2b9dd 2807 if (!isset($loggedinas)) {
2808 $loggedinas = user_login_string($usercourse, $USER);
2809 }
2810
0d741155 2811 if ($loggedinas == $menu) {
9e0d1983 2812 $menu = '';
0d741155 2813 }
2814
d795bfdb 2815/// there should be exactly the same number of open containers as after the header
2816 if ($THEME->open_header_containers != open_containers()) {
2817 debugging('Unexpected number of open containers: '.open_containers().', expecting '.$THEME->open_header_containers, DEBUG_DEVELOPER);
0ad439bb 2818 }
2819
b8cea9b2 2820/// Provide some performance info if required
c2fd9e95 2821 $performanceinfo = '';
afd2b299 2822 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
c2fd9e95 2823 $perf = get_performance_info();
cf1348ca 2824 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
853df85e 2825 error_log("PERF: " . $perf['txt']);
2826 }
ea82d6b6 2827 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
c2fd9e95 2828 $performanceinfo = $perf['html'];
2829 }
572fe9ab 2830 }
b8cea9b2 2831
b3a2b9dd 2832/// Include the actual footer file
a282d0ff 2833
469b6501 2834 ob_start();
ea35ab87 2835 include($CFG->footer);
469b6501 2836 $output = ob_get_contents();
2837 ob_end_clean();
a9a9bdba 2838
cf615522 2839 // Put the end of page <script> tags just inside </body> to maintain validity.
2840 $output = str_replace('</body>', $PAGE->requires->get_end_code() . '</body>', $output);
2841
c13a5e71 2842 $PAGE->set_state(moodle_page::STATE_DONE);
2843
469b6501 2844 if ($return) {
2845 return $output;
2846 } else {
2847 echo $output;
2848 }
a282d0ff 2849}
2850
c3f55692 2851/**
2852 * Returns the name of the current theme
2853 *
449611af 2854 * @global object
2855 * @global object
2856 * @global object
2857 * @global object
2858 * @global string
c3f55692 2859 * @return string
2860 */
2861function current_theme() {
11e7b506 2862 global $CFG, $USER, $SESSION, $COURSE, $SCRIPT;
8f8210b3 2863
2864 if (empty($CFG->themeorder)) {
2865 $themeorder = array('page', 'course', 'category', 'session', 'user', 'site');
2866 } else {
2867 $themeorder = $CFG->themeorder;
2868 }
2869
7507f325 2870 if (isloggedin() and isset($CFG->mnet_localhost_id) and $USER->mnethostid != $CFG->mnet_localhost_id) {
1673e134 2871 require_once($CFG->dirroot.'/mnet/peer.php');
2872 $mnet_peer = new mnet_peer();
2873 $mnet_peer->set_id($USER->mnethostid);
2874 }
2875
8f8210b3 2876 $theme = '';
2877 foreach ($themeorder as $themetype) {
2878
2879 if (!empty($theme)) continue;
84e3d2cc 2880
8f8210b3 2881 switch ($themetype) {
2882 case 'page': // Page theme is for special page-only themes set by code
2883 if (!empty($CFG->pagetheme)) {
2884 $theme = $CFG->pagetheme;
2885 }
2886 break;
2887 case 'course':
2888 if (!empty($CFG->allowcoursethemes) and !empty($COURSE->theme)) {
2889 $theme = $COURSE->theme;
2890 }
2891 break;
2892 case 'category':
2893 if (!empty($CFG->allowcategorythemes)) {
2894 /// Nasty hack to check if we're in a category page
11e7b506 2895 if ($SCRIPT == '/course/category.php') {
8f8210b3 2896 global $id;
2897 if (!empty($id)) {
2898 $theme = current_category_theme($id);
2899 }
2900 /// Otherwise check if we're in a course that has a category theme set
2901 } else if (!empty($COURSE->category)) {
2902 $theme = current_category_theme($COURSE->category);
2903 }
2904 }
2905 break;
2906 case 'session':
2907 if (!empty($SESSION->theme)) {
2908 $theme = $SESSION->theme;
2909 }
2910 break;
2911 case 'user':
2912 if (!empty($CFG->allowuserthemes) and !empty($USER->theme)) {
178bcb75 2913 if (isloggedin() and $USER->mnethostid != $CFG->mnet_localhost_id && $mnet_peer->force_theme == 1 && $mnet_peer->theme != '') {
1673e134 2914 $theme = $mnet_peer->theme;
2915 } else {
2916 $theme = $USER->theme;
2917 }
8f8210b3 2918 }
2919 break;
2920 case 'site':
7507f325 2921 if (isloggedin() and isset($CFG->mnet_localhost_id) and $USER->mnethostid != $CFG->mnet_localhost_id && $mnet_peer->force_theme == 1 && $mnet_peer->theme != '') {
1673e134 2922 $theme = $mnet_peer->theme;
2923 } else {
2924 $theme = $CFG->theme;
2925 }
8f8210b3 2926 break;
2927 default:
2928 /// do nothing
2929 }
2930 }
c3f55692 2931
8f8210b3 2932/// A final check in case 'site' was not included in $CFG->themeorder
2933 if (empty($theme)) {
2934 $theme = $CFG->theme;
2935 }
c3f55692 2936
8f8210b3 2937 return $theme;
2938}
c3f55692 2939
8f8210b3 2940/**
2941 * Retrieves the category theme if one exists, otherwise checks the parent categories.
2942 * Recursive function.
2943 *
449611af 2944 * @global object
2945 * @global object
8f8210b3 2946 * @param integer $categoryid id of the category to check
2947 * @return string theme name
2948 */
2949function current_category_theme($categoryid=0) {
f33e1ed4 2950 global $COURSE, $DB;
84e3d2cc 2951
8f8210b3 2952/// Use the COURSE global if the categoryid not set
2953 if (empty($categoryid)) {
2954 if (!empty($COURSE->category)) {
2955 $categoryid = $COURSE->category;
2956 } else {
2957 return false;
2958 }
2959 }
84e3d2cc 2960
8f8210b3 2961/// Retrieve the current category
f33e1ed4 2962 if ($category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
84e3d2cc 2963
8f8210b3 2964 /// Return the category theme if it exists
2965 if (!empty($category->theme)) {
2966 return $category->theme;
c3f55692 2967
8f8210b3 2968 /// Otherwise try the parent category if one exists
2969 } else if (!empty($category->parent)) {
2970 return current_category_theme($category->parent);
2971 }
c3f55692 2972
8f8210b3 2973/// Return false if we can't find the category record
c3f55692 2974 } else {
8f8210b3 2975 return false;
c3f55692 2976 }
2977}
2978
d48b00b4 2979/**
2980 * This function is called by stylesheets to set up the header
2981 * approriately as well as the current path
2982 *
449611af 2983 * @global object
2984 * @global object
2985 * @uses PARAM_SAFEDIR
2986 * @param int $lastmodified Always gets set to now
2987 * @param int $lifetime The max-age header setting (seconds) defaults to 300
2988 * @param string $themename The name of the theme to use (optional) defaults to current theme
2989 * @param string $forceconfig Force a particular theme config (optional)
2990 * @param string $lang Load styles for the specified language (optional)
d48b00b4 2991 */
f31701c4 2992function style_sheet_setup($lastmodified=0, $lifetime=300, $themename='', $forceconfig='', $lang='') {
6535be85 2993
9c4e6e21 2994 global $CFG, $THEME;
ab9f24ad 2995
a2e2bf64 2996 // Fix for IE6 caching - we don't want the filemtime('styles.php'), instead use now.
2997 $lastmodified = time();
2998
b0ccd3fb 2999 header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastmodified) . ' GMT');
3000 header('Expires: ' . gmdate("D, d M Y H:i:s", time() + $lifetime) . ' GMT');
a2e2bf64 3001 header('Cache-Control: max-age='. $lifetime);
b0ccd3fb 3002 header('Pragma: ');
c13a5e71 3003 header('Content-type: text/css'); // Correct MIME type
6535be85 3004
909ec807 3005 $DEFAULT_SHEET_LIST = array('styles_layout', 'styles_fonts', 'styles_color');
9c4e6e21 3006
3007 if (empty($themename)) {
c13a5e71 3008 $themename = current_theme(); // So we have something. Normally not needed.
ab25ce31 3009 } else {
3010 $themename = clean_param($themename, PARAM_SAFEDIR);
9c4e6e21 3011 }
3012
c13a5e71 3013 theme_setup($themename);
3014
3015 if (!empty($forceconfig)) { // Page wants to use the config from this theme instead
9c4e6e21 3016 unset($THEME);
a44091bf 3017 include($CFG->themedir.'/'.$forceconfig.'/'.'config.php');
9c4e6e21 3018 }
3019
3020/// If this is the standard theme calling us, then find out what sheets we need
9c4e6e21 3021 if ($themename == 'standard') {
3022 if (!isset($THEME->standardsheets) or $THEME->standardsheets === true) { // Use all the sheets we have
3023 $THEME->sheets = $DEFAULT_SHEET_LIST;
c13a5e71 3024 } else if (empty($THEME->standardsheets)) { // We can stop right now!
9c4e6e21 3025 echo "/***** Nothing required from this stylesheet by main theme *****/\n\n";
3026 exit;
c13a5e71 3027 } else { // Use the provided subset only
9c4e6e21 3028 $THEME->sheets = $THEME->standardsheets;
3029 }
3030
3031/// If we are a parent theme, then check for parent definitions
9c4e6e21 3032 } else if (!empty($THEME->parent) && $themename == $THEME->parent) {
3033 if (!isset($THEME->parentsheets) or $THEME->parentsheets === true) { // Use all the sheets we have
3034 $THEME->sheets = $DEFAULT_SHEET_LIST;
3035 } else if (empty($THEME->parentsheets)) { // We can stop right now!
3036 echo "/***** Nothing required from this stylesheet by main theme *****/\n\n";
3037 exit;
3038 } else { // Use the provided subset only
3039 $THEME->sheets = $THEME->parentsheets;
3040 }
3041 }
3042
3043/// Work out the last modified date for this theme
9c4e6e21 3044 foreach ($THEME->sheets as $sheet) {
a44091bf 3045 if (file_exists($CFG->themedir.'/'.$themename.'/'.$sheet.'.css')) {
3046 $sheetmodified = filemtime($CFG->themedir.'/'.$themename.'/'.$sheet.'.css');
9c4e6e21 3047 if ($sheetmodified > $lastmodified) {
3048 $lastmodified = $sheetmodified;
3049 }
3050 }
6535be85 3051 }
3052
6ba172fb 3053/// Get a list of all the files we want to include
3054 $files = array();
3055
3056 foreach ($THEME->sheets as $sheet) {
3057 $files[] = array($CFG->themedir, $themename.'/'.$sheet.'.css');
3058 }
3059
3060 if ($themename == 'standard') { // Add any standard styles included in any modules
3061 if (!empty($THEME->modsheets)) { // Search for styles.php within activity modules
17da2e6f 3062 $mods = get_plugin_list('mod');
3063 foreach ($mods as $mod => $moddir) {
3064 if (file_exists($moddir.'/styles.php')) {
73735edf 3065 $files[] = array($moddir, 'styles.php');
08396bb2 3066 }
3067 }
3068 }
a2b3f884 3069
6ba172fb 3070 if (!empty($THEME->blocksheets)) { // Search for styles.php within block modules
17da2e6f 3071 $mods = get_plugin_list('blocks');
3072 foreach ($mods as $mod => $moddir) {
3073 if (file_exists($moddir.'/styles.php')) {
73735edf 3074 $files[] = array($moddir, 'styles.php');
08396bb2 3075 }
3076 }
3077 }
a2b3f884 3078
ae628043 3079 if (!isset($THEME->courseformatsheets) || $THEME->courseformatsheets) { // Search for styles.php in course formats
17da2e6f 3080 $mods = get_plugin_list('format');
3081 foreach ($mods as $mod => $moddir) {
3082 if (file_exists($moddir.'/styles.php')) {
73735edf 3083 $files[] = array($moddir, 'styles.php');
ae628043 3084 }
3085 }
3086 }
3087
1273d7dd 3088 if (!isset($THEME->gradereportsheets) || $THEME->gradereportsheets) { // Search for styles.php in grade reports
17da2e6f 3089 $reports = get_plugin_list('gradereport');
3090 foreach ($reports as $report => $reportdir) {
3091 if (file_exists($reportdir.'/styles.php')) {
73735edf 3092 $files[] = array($reportdir, 'styles.php');
1273d7dd 3093 }
3094 }
3095 }
3096
6ba172fb 3097 if (!empty($THEME->langsheets)) { // Search for styles.php within the current language
6ba172fb 3098 if (file_exists($CFG->dirroot.'/lang/'.$lang.'/styles.php')) {
73735edf 3099 $files[] = array($CFG->dirroot, 'lang/'.$lang.'/styles.php');
6ba172fb 3100 }
3101 }
08396bb2 3102 }
3103
6ba172fb 3104 if ($files) {
3105 /// Produce a list of all the files first
3106 echo '/**************************************'."\n";
3107 echo ' * THEME NAME: '.$themename."\n *\n";
3108 echo ' * Files included in this sheet:'."\n *\n";
3109 foreach ($files as $file) {
3110 echo ' * '.$file[1]."\n";
08396bb2 3111 }
6ba172fb 3112 echo ' **************************************/'."\n\n";
08396bb2 3113
6ba172fb 3114
16f0af59 3115 /// check if csscobstants is set
3116 if (!empty($THEME->cssconstants)) {
3117 require_once("$CFG->libdir/cssconstants.php");
3118 /// Actually collect all the files in order.
3119 $css = '';
3120 foreach ($files as $file) {
3121 $css .= '/***** '.$file[1].' start *****/'."\n\n";
3122 $css .= file_get_contents($file[0].'/'.$file[1]);
3123 $ccs .= '/***** '.$file[1].' end *****/'."\n\n";
3124 }
3125 /// replace css_constants with their values
3126 echo replace_cssconstants($css);
3127 } else {
3128 /// Actually output all the files in order.
d0c92410 3129 if (empty($CFG->CSSEdit) && empty($THEME->CSSEdit)) {
3130 foreach ($files as $file) {
3131 echo '/***** '.$file[1].' start *****/'."\n\n";
3132 @include_once($file[0].'/'.$file[1]);
3133 echo '/***** '.$file[1].' end *****/'."\n\n";
3134 }
3135 } else {
3136 foreach ($files as $file) {
3137 echo '/* @group '.$file[1].' */'."\n\n";
3138 if (strstr($file[1], '.css') !== FALSE) {
3139 echo '@import url("'.$CFG->themewww.'/'.$file[1].'");'."\n\n";
3140 } else {
3141 @include_once($file[0].'/'.$file[1]);
3142 }
3143 echo '/* @end */'."\n\n";
3144 }
16f0af59 3145 }
6ba172fb 3146 }
9c4e6e21 3147 }
3148
a44091bf 3149 return $CFG->themewww.'/'.$themename; // Only to help old themes (1.4 and earlier)
6535be85 3150}
3151
449611af 3152/**
3153 * Sets up the global variables related to theme
3154 *
3155 * @global object
3156 * @global object
3157 * @global object Apparently not used here
3158 * @global object Apparently not used here
3159 * @global object
3160 * @global object
3161 * @param string $theme The theme to use defaults to current theme
3162 * @param array $params An array of parameters to use
3163 */
9c4e6e21 3164function theme_setup($theme = '', $params=NULL) {
d74d4f20 3165/// Sets up global variables related to themes
3166
c13a5e71 3167 global $CFG, $THEME, $SESSION, $USER, $HTTPSPAGEREQUIRED, $PAGE;
d74d4f20 3168
32462b2c 3169/// Do not mess with THEME if header already printed - this would break all the extra stuff in global $THEME from print_header()!!
c13a5e71 3170 if ($PAGE->headerprinted) {
32462b2c 3171 return;
3172 }
3173
d74d4f20 3174 if (empty($theme)) {
3175 $theme = current_theme();
3176 }
9c4e6e21 3177
09ad59dc 3178/// If the theme doesn't exist for some reason then revert to standardwhite
a44091bf 3179 if (!file_exists($CFG->themedir .'/'. $theme .'/config.php')) {
09ad59dc 3180 $CFG->theme = $theme = 'standardwhite';
3181 }
3182
55b734fb 3183/// Load up the theme config
3184 $THEME = NULL; // Just to be sure
a44091bf 3185 include($CFG->themedir .'/'. $theme .'/config.php'); // Main config for current theme
a978bdde 3186 $THEME->name = $theme;
3187 $THEME->dir = $CFG->themedir .'/'. $theme;