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