| [ Index ] |
SchoolsICT Limited Hosted PHP Cross Reference of Moodle 1.9 |
[ Index ] [ Classes ] [ Functions ] [ Variables ] [ Constants ] [ Statistics ] | ||
[Summary view] [Print] [Text view]
1 <?php // $Id$ 2 3 /////////////////////////////////////////////////////////////////////////// 4 // // 5 // NOTICE OF COPYRIGHT // 6 // // 7 // Moodle - Modular Object-Oriented Dynamic Learning Environment // 8 // http://moodle.com // 9 // // 10 // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com // 11 // // 12 // This program is free software; you can redistribute it and/or modify // 13 // it under the terms of the GNU General Public License as published by // 14 // the Free Software Foundation; either version 2 of the License, or // 15 // (at your option) any later version. // 16 // // 17 // This program is distributed in the hope that it will be useful, // 18 // but WITHOUT ANY WARRANTY; without even the implied warranty of // 19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // 20 // GNU General Public License for more details: // 21 // // 22 // http://www.gnu.org/copyleft/gpl.html // 23 // // 24 /////////////////////////////////////////////////////////////////////////// 25 26 /** 27 * Library of functions for web output 28 * 29 * Library of all general-purpose Moodle PHP functions and constants 30 * that produce HTML output 31 * 32 * Other main libraries: 33 * - datalib.php - functions that access the database. 34 * - moodlelib.php - general-purpose Moodle functions. 35 * @author Martin Dougiamas 36 * @version $Id$ 37 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 38 * @package moodlecore 39 */ 40 41 /// We are going to uses filterlib functions here 42 require_once("$CFG->libdir/filterlib.php"); 43 44 require_once("$CFG->libdir/ajax/ajaxlib.php"); 45 46 /// Constants 47 48 /// Define text formatting types ... eventually we can add Wiki, BBcode etc 49 50 /** 51 * Does all sorts of transformations and filtering 52 */ 53 define('FORMAT_MOODLE', '0'); // Does all sorts of transformations and filtering 54 55 /** 56 * Plain HTML (with some tags stripped) 57 */ 58 define('FORMAT_HTML', '1'); // Plain HTML (with some tags stripped) 59 60 /** 61 * Plain text (even tags are printed in full) 62 */ 63 define('FORMAT_PLAIN', '2'); // Plain text (even tags are printed in full) 64 65 /** 66 * Wiki-formatted text 67 * Deprecated: left here just to note that '3' is not used (at the moment) 68 * and to catch any latent wiki-like text (which generates an error) 69 */ 70 define('FORMAT_WIKI', '3'); // Wiki-formatted text 71 72 /** 73 * Markdown-formatted text http://daringfireball.net/projects/markdown/ 74 */ 75 define('FORMAT_MARKDOWN', '4'); // Markdown-formatted text http://daringfireball.net/projects/markdown/ 76 77 /** 78 * TRUSTTEXT marker - if present in text, text cleaning should be bypassed 79 */ 80 define('TRUSTTEXT', '#####TRUSTTEXT#####'); 81 82 83 /** 84 * Javascript related defines 85 */ 86 define('REQUIREJS_BEFOREHEADER', 0); 87 define('REQUIREJS_INHEADER', 1); 88 define('REQUIREJS_AFTERHEADER', 2); 89 90 /** 91 * Allowed tags - string of html tags that can be tested against for safe html tags 92 * @global string $ALLOWED_TAGS 93 */ 94 global $ALLOWED_TAGS; 95 $ALLOWED_TAGS = 96 '<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>'; 97 98 /** 99 * Allowed protocols - array of protocols that are safe to use in links and so on 100 * @global string $ALLOWED_PROTOCOLS 101 */ 102 $ALLOWED_PROTOCOLS = array('http', 'https', 'ftp', 'news', 'mailto', 'rtsp', 'teamspeak', 'gopher', 'mms', 103 'color', 'callto', 'cursor', 'text-align', 'font-size', 'font-weight', 'font-style', 'font-family', 104 'border', 'border-bottom', 'border-left', 'border-top', 'border-right', 'margin', 'margin-bottom', 'margin-left', 'margin-top', 'margin-right', 105 'padding', 'padding-bottom', 'padding-left', 'padding-top', 'padding-right', 'vertical-align', 106 'background', 'background-color', 'text-decoration'); // CSS as well to get through kses 107 108 109 /// Functions 110 111 /** 112 * Add quotes to HTML characters 113 * 114 * Returns $var with HTML characters (like "<", ">", etc.) properly quoted. 115 * This function is very similar to {@link p()} 116 * 117 * @param string $var the string potentially containing HTML characters 118 * @param boolean $strip to decide if we want to strip slashes or no. Default to false. 119 * true should be used to print data from forms and false for data from DB. 120 * @return string 121 */ 122 function s($var, $strip=false) { 123 124 if ($var === '0' or $var === false or $var === 0) { 125 return '0'; 126 } 127 128 if ($strip) { 129 return preg_replace("/&(#\d+);/i", "&$1;", htmlspecialchars(stripslashes_safe($var))); 130 } else { 131 return preg_replace("/&(#\d+);/i", "&$1;", htmlspecialchars($var)); 132 } 133 } 134 135 /** 136 * Add quotes to HTML characters 137 * 138 * Prints $var with HTML characters (like "<", ">", etc.) properly quoted. 139 * This function is very similar to {@link s()} 140 * 141 * @param string $var the string potentially containing HTML characters 142 * @param boolean $strip to decide if we want to strip slashes or no. Default to false. 143 * true should be used to print data from forms and false for data from DB. 144 * @return string 145 */ 146 function p($var, $strip=false) { 147 echo s($var, $strip); 148 } 149 150 /** 151 * Does proper javascript quoting. 152 * Do not use addslashes anymore, because it does not work when magic_quotes_sybase is enabled. 153 * 154 * @since 1.8 - 22/02/2007 155 * @param mixed value 156 * @return mixed quoted result 157 */ 158 function addslashes_js($var) { 159 if (is_string($var)) { 160 $var = str_replace('\\', '\\\\', $var); 161 $var = str_replace(array('\'', '"', "\n", "\r", "\0"), array('\\\'', '\\"', '\\n', '\\r', '\\0'), $var); 162 $var = str_replace('</', '<\/', $var); // XHTML compliance 163 } else if (is_array($var)) { 164 $var = array_map('addslashes_js', $var); 165 } else if (is_object($var)) { 166 $a = get_object_vars($var); 167 foreach ($a as $key=>$value) { 168 $a[$key] = addslashes_js($value); 169 } 170 $var = (object)$a; 171 } 172 return $var; 173 } 174 175 /** 176 * Remove query string from url 177 * 178 * Takes in a URL and returns it without the querystring portion 179 * 180 * @param string $url the url which may have a query string attached 181 * @return string 182 */ 183 function strip_querystring($url) { 184 185 if ($commapos = strpos($url, '?')) { 186 return substr($url, 0, $commapos); 187 } else { 188 return $url; 189 } 190 } 191 192 /** 193 * Returns the URL of the HTTP_REFERER, less the querystring portion if required 194 * @param boolean $stripquery if true, also removes the query part of the url. 195 * @return string 196 */ 197 function get_referer($stripquery=true) { 198 if (isset($_SERVER['HTTP_REFERER'])) { 199 if ($stripquery) { 200 return strip_querystring($_SERVER['HTTP_REFERER']); 201 } else { 202 return $_SERVER['HTTP_REFERER']; 203 } 204 } else { 205 return ''; 206 } 207 } 208 209 210 /** 211 * Returns the name of the current script, WITH the querystring portion. 212 * this function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME 213 * return different things depending on a lot of things like your OS, Web 214 * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.) 215 * <b>NOTE:</b> This function returns false if the global variables needed are not set. 216 * 217 * @return string 218 */ 219 function me() { 220 if (!empty($_SERVER['REQUEST_URI'])) { 221 $return = $_SERVER['REQUEST_URI']; 222 223 } else if (!empty($_SERVER['PHP_SELF'])) { 224 if (!empty($_SERVER['QUERY_STRING'])) { 225 $return = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING']; 226 } else { 227 $return = $_SERVER['PHP_SELF']; 228 } 229 230 } else if (!empty($_SERVER['SCRIPT_NAME'])) { 231 if (!empty($_SERVER['QUERY_STRING'])) { 232 $return = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING']; 233 } else { 234 $return = $_SERVER['SCRIPT_NAME']; 235 } 236 237 } else if (!empty($_SERVER['URL'])) { // May help IIS (not well tested) 238 if (!empty($_SERVER['QUERY_STRING'])) { 239 $return = $_SERVER['URL'] .'?'. $_SERVER['QUERY_STRING']; 240 } else { 241 $return = $_SERVER['URL']; 242 } 243 244 } else { 245 notify('Warning: Could not find any of these web server variables: $REQUEST_URI, $PHP_SELF, $SCRIPT_NAME or $URL'); 246 return false; 247 } 248 249 // sanitize the url a bit more, the encoding style may be different in vars above 250 $return = str_replace('"', '%22', $return); 251 $return = str_replace('\'', '%27', $return); 252 253 return $return; 254 } 255 256 /** 257 * Like {@link me()} but returns a full URL 258 * @see me() 259 * @return string 260 */ 261 function qualified_me() { 262 263 global $CFG; 264 265 if (!empty($CFG->wwwroot)) { 266 $url = parse_url($CFG->wwwroot); 267 } 268 269 if (!empty($url['host'])) { 270 $hostname = $url['host']; 271 } else if (!empty($_SERVER['SERVER_NAME'])) { 272 $hostname = $_SERVER['SERVER_NAME']; 273 } else if (!empty($_ENV['SERVER_NAME'])) { 274 $hostname = $_ENV['SERVER_NAME']; 275 } else if (!empty($_SERVER['HTTP_HOST'])) { 276 $hostname = $_SERVER['HTTP_HOST']; 277 } else if (!empty($_ENV['HTTP_HOST'])) { 278 $hostname = $_ENV['HTTP_HOST']; 279 } else { 280 notify('Warning: could not find the name of this server!'); 281 return false; 282 } 283 284 if (!empty($url['port'])) { 285 $hostname .= ':'.$url['port']; 286 } else if (!empty($_SERVER['SERVER_PORT'])) { 287 if ($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) { 288 $hostname .= ':'.$_SERVER['SERVER_PORT']; 289 } 290 } 291 292 // TODO, this does not work in the situation described in MDL-11061, but 293 // I don't know how to fix it. Possibly believe $CFG->wwwroot ahead of what 294 // the server reports. 295 if (isset($_SERVER['HTTPS'])) { 296 $protocol = ($_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://'; 297 } else if (isset($_SERVER['SERVER_PORT'])) { # Apache2 does not export $_SERVER['HTTPS'] 298 $protocol = ($_SERVER['SERVER_PORT'] == '443') ? 'https://' : 'http://'; 299 } else { 300 $protocol = 'http://'; 301 } 302 303 $url_prefix = $protocol.$hostname; 304 return $url_prefix . me(); 305 } 306 307 308 /** 309 * Class for creating and manipulating urls. 310 * 311 * See short write up here http://docs.moodle.org/dev/lib/weblib.php_moodle_url 312 */ 313 class moodle_url { 314 var $scheme = '';// e.g. http 315 var $host = ''; 316 var $port = ''; 317 var $user = ''; 318 var $pass = ''; 319 var $path = ''; 320 var $fragment = ''; 321 var $params = array(); //associative array of query string params 322 323 /** 324 * Pass no arguments to create a url that refers to this page. Use empty string to create empty url. 325 * 326 * @param string $url url default null means use this page url with no query string 327 * empty string means empty url. 328 * if you pass any other type of url it will be parsed into it's bits, including query string 329 * @param array $params these params override anything in the query string where params have the same name. 330 */ 331 function moodle_url($url = null, $params = array()){ 332 global $FULLME; 333 if ($url !== ''){ 334 if ($url === null){ 335 $url = strip_querystring($FULLME); 336 } 337 $parts = parse_url($url); 338 if ($parts === FALSE){ 339 error('invalidurl'); 340 } 341 if (isset($parts['query'])){ 342 parse_str(str_replace('&', '&', $parts['query']), $this->params); 343 } 344 unset($parts['query']); 345 foreach ($parts as $key => $value){ 346 $this->$key = $value; 347 } 348 $this->params($params); 349 } 350 } 351 352 /** 353 * Add an array of params to the params for this page. 354 * 355 * The added params override existing ones if they have the same name. 356 * 357 * @param array $params Defaults to null. If null then return value of param 'name'. 358 * @return array Array of Params for url. 359 */ 360 function params($params = null) { 361 if (!is_null($params)) { 362 return $this->params = $params + $this->params; 363 } else { 364 return $this->params; 365 } 366 } 367 368 /** 369 * Remove all params if no arguments passed. Or else remove param $arg1, $arg2, etc. 370 * 371 * @param string $arg1 372 * @param string $arg2 373 * @param string $arg3 374 */ 375 function remove_params(){ 376 if ($thisargs = func_get_args()){ 377 foreach ($thisargs as $arg){ 378 if (isset($this->params[$arg])){ 379 unset($this->params[$arg]); 380 } 381 } 382 } else { // no args 383 $this->params = array(); 384 } 385 } 386 387 /** 388 * Add a param to the params for this page. The added param overrides existing one if they 389 * have the same name. 390 * 391 * @param string $paramname name 392 * @param string $param value 393 */ 394 function param($paramname, $param){ 395 $this->params = array($paramname => $param) + $this->params; 396 } 397 398 399 function get_query_string($overrideparams = array()){ 400 $arr = array(); 401 $params = $overrideparams + $this->params; 402 foreach ($params as $key => $val){ 403 $arr[] = urlencode($key)."=".urlencode($val); 404 } 405 return implode($arr, "&"); 406 } 407 /** 408 * Outputs params as hidden form elements. 409 * 410 * @param array $exclude params to ignore 411 * @param integer $indent indentation 412 * @param array $overrideparams params to add to the output params, these 413 * override existing ones with the same name. 414 * @return string html for form elements. 415 */ 416 function hidden_params_out($exclude = array(), $indent = 0, $overrideparams=array()){ 417 $tabindent = str_repeat("\t", $indent); 418 $str = ''; 419 $params = $overrideparams + $this->params; 420 foreach ($params as $key => $val){ 421 if (FALSE === array_search($key, $exclude)) { 422 $val = s($val); 423 $str.= "$tabindent<input type=\"hidden\" name=\"$key\" value=\"$val\" />\n"; 424 } 425 } 426 return $str; 427 } 428 /** 429 * Output url 430 * 431 * @param boolean $noquerystring whether to output page params as a query string in the url. 432 * @param array $overrideparams params to add to the output url, these override existing ones with the same name. 433 * @return string url 434 */ 435 function out($noquerystring = false, $overrideparams = array()) { 436 $uri = $this->scheme ? $this->scheme.':'.((strtolower($this->scheme) == 'mailto') ? '':'//'): ''; 437 $uri .= $this->user ? $this->user.($this->pass? ':'.$this->pass:'').'@':''; 438 $uri .= $this->host ? $this->host : ''; 439 $uri .= $this->port ? ':'.$this->port : ''; 440 $uri .= $this->path ? $this->path : ''; 441 if (!$noquerystring){ 442 $uri .= (count($this->params)||count($overrideparams)) ? '?'.$this->get_query_string($overrideparams) : ''; 443 } 444 $uri .= $this->fragment ? '#'.$this->fragment : ''; 445 return $uri; 446 } 447 /** 448 * Output action url with sesskey 449 * 450 * @param boolean $noquerystring whether to output page params as a query string in the url. 451 * @return string url 452 */ 453 function out_action($overrideparams = array()) { 454 $overrideparams = array('sesskey'=> sesskey()) + $overrideparams; 455 return $this->out(false, $overrideparams); 456 } 457 } 458 459 /** 460 * Determine if there is data waiting to be processed from a form 461 * 462 * Used on most forms in Moodle to check for data 463 * Returns the data as an object, if it's found. 464 * This object can be used in foreach loops without 465 * casting because it's cast to (array) automatically 466 * 467 * Checks that submitted POST data exists and returns it as object. 468 * 469 * @param string $url not used anymore 470 * @return mixed false or object 471 */ 472 function data_submitted($url='') { 473 474 if (empty($_POST)) { 475 return false; 476 } else { 477 return (object)$_POST; 478 } 479 } 480 481 /** 482 * Moodle replacement for php stripslashes() function, 483 * works also for objects and arrays. 484 * 485 * The standard php stripslashes() removes ALL backslashes 486 * even from strings - so C:\temp becomes C:temp - this isn't good. 487 * This function should work as a fairly safe replacement 488 * to be called on quoted AND unquoted strings (to be sure) 489 * 490 * @param mixed something to remove unsafe slashes from 491 * @return mixed 492 */ 493 function stripslashes_safe($mixed) { 494 // there is no need to remove slashes from int, float and bool types 495 if (empty($mixed)) { 496 //nothing to do... 497 } else if (is_string($mixed)) { 498 if (ini_get_bool('magic_quotes_sybase')) { //only unescape single quotes 499 $mixed = str_replace("''", "'", $mixed); 500 } else { //the rest, simple and double quotes and backslashes 501 $mixed = str_replace("\\'", "'", $mixed); 502 $mixed = str_replace('\\"', '"', $mixed); 503 $mixed = str_replace('\\\\', '\\', $mixed); 504 } 505 } else if (is_array($mixed)) { 506 foreach ($mixed as $key => $value) { 507 $mixed[$key] = stripslashes_safe($value); 508 } 509 } else if (is_object($mixed)) { 510 $vars = get_object_vars($mixed); 511 foreach ($vars as $key => $value) { 512 $mixed->$key = stripslashes_safe($value); 513 } 514 } 515 516 return $mixed; 517 } 518 519 /** 520 * Recursive implementation of stripslashes() 521 * 522 * This function will allow you to strip the slashes from a variable. 523 * If the variable is an array or object, slashes will be stripped 524 * from the items (or properties) it contains, even if they are arrays 525 * or objects themselves. 526 * 527 * @param mixed the variable to remove slashes from 528 * @return mixed 529 */ 530 function stripslashes_recursive($var) { 531 if (is_object($var)) { 532 $new_var = new object(); 533 $properties = get_object_vars($var); 534 foreach($properties as $property => $value) { 535 $new_var->$property = stripslashes_recursive($value); 536 } 537 538 } else if(is_array($var)) { 539 $new_var = array(); 540 foreach($var as $property => $value) { 541 $new_var[$property] = stripslashes_recursive($value); 542 } 543 544 } else if(is_string($var)) { 545 $new_var = stripslashes($var); 546 547 } else { 548 $new_var = $var; 549 } 550 551 return $new_var; 552 } 553 554 /** 555 * Recursive implementation of addslashes() 556 * 557 * This function will allow you to add the slashes from a variable. 558 * If the variable is an array or object, slashes will be added 559 * to the items (or properties) it contains, even if they are arrays 560 * or objects themselves. 561 * 562 * @param mixed the variable to add slashes from 563 * @return mixed 564 */ 565 function addslashes_recursive($var) { 566 if (is_object($var)) { 567 $new_var = new object(); 568 $properties = get_object_vars($var); 569 foreach($properties as $property => $value) { 570 $new_var->$property = addslashes_recursive($value); 571 } 572 573 } else if (is_array($var)) { 574 $new_var = array(); 575 foreach($var as $property => $value) { 576 $new_var[$property] = addslashes_recursive($value); 577 } 578 579 } else if (is_string($var)) { 580 $new_var = addslashes($var); 581 582 } else { // nulls, integers, etc. 583 $new_var = $var; 584 } 585 586 return $new_var; 587 } 588 589 /** 590 * Given some normal text this function will break up any 591 * long words to a given size by inserting the given character 592 * 593 * It's multibyte savvy and doesn't change anything inside html tags. 594 * 595 * @param string $string the string to be modified 596 * @param int $maxsize maximum length of the string to be returned 597 * @param string $cutchar the string used to represent word breaks 598 * @return string 599 */ 600 function break_up_long_words($string, $maxsize=20, $cutchar=' ') { 601 602 /// Loading the textlib singleton instance. We are going to need it. 603 $textlib = textlib_get_instance(); 604 605 /// First of all, save all the tags inside the text to skip them 606 $tags = array(); 607 filter_save_tags($string,$tags); 608 609 /// Process the string adding the cut when necessary 610 $output = ''; 611 $length = $textlib->strlen($string); 612 $wordlength = 0; 613 614 for ($i=0; $i<$length; $i++) { 615 $char = $textlib->substr($string, $i, 1); 616 if ($char == ' ' or $char == "\t" or $char == "\n" or $char == "\r" or $char == "<" or $char == ">") { 617 $wordlength = 0; 618 } else { 619 $wordlength++; 620 if ($wordlength > $maxsize) { 621 $output .= $cutchar; 622 $wordlength = 0; 623 } 624 } 625 $output .= $char; 626 } 627 628 /// Finally load the tags back again 629 if (!empty($tags)) { 630 $output = str_replace(array_keys($tags), $tags, $output); 631 } 632 633 return $output; 634 } 635 636 /** 637 * This does a search and replace, ignoring case 638 * This function is only used for versions of PHP older than version 5 639 * which do not have a native version of this function. 640 * Taken from the PHP manual, by bradhuizenga @ softhome.net 641 * 642 * @param string $find the string to search for 643 * @param string $replace the string to replace $find with 644 * @param string $string the string to search through 645 * return string 646 */ 647 if (!function_exists('str_ireplace')) { /// Only exists in PHP 5 648 function str_ireplace($find, $replace, $string) { 649 650 if (!is_array($find)) { 651 $find = array($find); 652 } 653 654 if(!is_array($replace)) { 655 if (!is_array($find)) { 656 $replace = array($replace); 657 } else { 658 // this will duplicate the string into an array the size of $find 659 $c = count($find); 660 $rString = $replace; 661 unset($replace); 662 for ($i = 0; $i < $c; $i++) { 663 $replace[$i] = $rString; 664 } 665 } 666 } 667 668 foreach ($find as $fKey => $fItem) { 669 $between = explode(strtolower($fItem),strtolower($string)); 670 $pos = 0; 671 foreach($between as $bKey => $bItem) { 672 $between[$bKey] = substr($string,$pos,strlen($bItem)); 673 $pos += strlen($bItem) + strlen($fItem); 674 } 675 $string = implode($replace[$fKey],$between); 676 } 677 return ($string); 678 } 679 } 680 681 /** 682 * Locate the position of a string in another string 683 * 684 * This function is only used for versions of PHP older than version 5 685 * which do not have a native version of this function. 686 * Taken from the PHP manual, by dmarsh @ spscc.ctc.edu 687 * 688 * @param string $haystack The string to be searched 689 * @param string $needle The string to search for 690 * @param int $offset The position in $haystack where the search should begin. 691 */ 692 if (!function_exists('stripos')) { /// Only exists in PHP 5 693 function stripos($haystack, $needle, $offset=0) { 694 695 return strpos(strtoupper($haystack), strtoupper($needle), $offset); 696 } 697 } 698 699 /** 700 * This function will print a button/link/etc. form element 701 * that will work on both Javascript and non-javascript browsers. 702 * Relies on the Javascript function openpopup in javascript.php 703 * 704 * All parameters default to null, only $type and $url are mandatory. 705 * 706 * $url must be relative to home page eg /mod/survey/stuff.php 707 * @param string $url Web link relative to home page 708 * @param string $name Name to be assigned to the popup window (this is used by 709 * client-side scripts to "talk" to the popup window) 710 * @param string $linkname Text to be displayed as web link 711 * @param int $height Height to assign to popup window 712 * @param int $width Height to assign to popup window 713 * @param string $title Text to be displayed as popup page title 714 * @param string $options List of additional options for popup window 715 * @param string $return If true, return as a string, otherwise print 716 * @param string $id id added to the element 717 * @param string $class class added to the element 718 * @return string 719 * @uses $CFG 720 */ 721 function element_to_popup_window ($type=null, $url=null, $name=null, $linkname=null, 722 $height=400, $width=500, $title=null, 723 $options=null, $return=false, $id=null, $class=null) { 724 725 if (is_null($url)) { 726 debugging('You must give the url to display in the popup. URL is missing - can\'t create popup window.', DEBUG_DEVELOPER); 727 } 728 729 global $CFG; 730 731 if ($options == 'none') { // 'none' is legacy, should be removed in v2.0 732 $options = null; 733 } 734 735 // add some sane default options for popup windows 736 if (!$options) { 737 $options = 'menubar=0,location=0,scrollbars,resizable'; 738 } 739 if ($width) { 740 $options .= ',width='. $width; 741 } 742 if ($height) { 743 $options .= ',height='. $height; 744 } 745 if ($id) { 746 $id = ' id="'.$id.'" '; 747 } 748 if ($class) { 749 $class = ' class="'.$class.'" '; 750 } 751 if ($name) { 752 $_name = $name; 753 if (($name = preg_replace("/\s/", '_', $name)) != $_name) { 754 debugging('The $name of a popup window shouldn\'t contain spaces - string modified. '. $_name .' changed to '. $name, DEBUG_DEVELOPER); 755 } 756 } else { 757 $name = 'popup'; 758 } 759 760 // get some default string, using the localized version of legacy defaults 761 if (is_null($linkname) || $linkname === '') { 762 $linkname = get_string('clickhere'); 763 } 764 if (!$title) { 765 $title = get_string('popupwindowname'); 766 } 767 768 $fullscreen = 0; // must be passed to openpopup 769 $element = ''; 770 771 switch ($type) { 772 case 'button' : 773 $element = '<input type="button" name="'. $name .'" title="'. $title .'" value="'. $linkname .'" '. $id . $class . 774 "onclick=\"return openpopup('$url', '$name', '$options', $fullscreen);\" />\n"; 775 break; 776 case 'link' : 777 // some log url entries contain _SERVER[HTTP_REFERRER] in which case wwwroot is already there. 778 if (!(strpos($url,$CFG->wwwroot) === false)) { 779 $url = substr($url, strlen($CFG->wwwroot)); 780 } 781 $element = '<a title="'. s(strip_tags($title)) .'" href="'. $CFG->wwwroot . $url .'" '. 782 "$CFG->frametarget onclick=\"this.target='$name'; return openpopup('$url', '$name', '$options', $fullscreen);\">$linkname</a>"; 783 break; 784 default : 785 error('Undefined element - can\'t create popup window.'); 786 break; 787 } 788 789 if ($return) { 790 return $element; 791 } else { 792 echo $element; 793 } 794 } 795 796 /** 797 * Creates and displays (or returns) a link to a popup window, using element_to_popup_window function. 798 * 799 * @return string html code to display a link to a popup window. 800 * @see element_to_popup_window() 801 */ 802 function link_to_popup_window ($url, $name=null, $linkname=null, 803 $height=400, $width=500, $title=null, 804 $options=null, $return=false) { 805 806 return element_to_popup_window('link', $url, $name, $linkname, $height, $width, $title, $options, $return, null, null); 807 } 808 809 /** 810 * Creates and displays (or returns) a buttons to a popup window, using element_to_popup_window function. 811 * 812 * @return string html code to display a button to a popup window. 813 * @see element_to_popup_window() 814 */ 815 function button_to_popup_window ($url, $name=null, $linkname=null, 816 $height=400, $width=500, $title=null, $options=null, $return=false, 817 $id=null, $class=null) { 818 819 return element_to_popup_window('button', $url, $name, $linkname, $height, $width, $title, $options, $return, $id, $class); 820 } 821 822 823 /** 824 * Prints a simple button to close a window 825 * @param string $name name of the window to close 826 * @param boolean $return whether this function should return a string or output it 827 * @return string if $return is true, nothing otherwise 828 */ 829 function close_window_button($name='closewindow', $return=false) { 830 global $CFG; 831 832 $output = ''; 833 834 $output .= '<div class="closewindow">' . "\n"; 835 $output .= '<form action="#"><div>'; 836 $output .= '<input type="button" onclick="self.close();" value="'.get_string($name).'" />'; 837 $output .= '</div></form>'; 838 $output .= '</div>' . "\n"; 839 840 if ($return) { 841 return $output; 842 } else { 843 echo $output; 844 } 845 } 846 847 /* 848 * Try and close the current window immediately using Javascript 849 * @param int $delay the delay in seconds before closing the window 850 */ 851 function close_window($delay=0) { 852 ?> 853 <script type="text/javascript"> 854 //<![CDATA[ 855 function close_this_window() { 856 self.close(); 857 } 858 setTimeout("close_this_window()", <?php echo $delay * 1000 ?>); 859 //]]> 860 </script> 861 <noscript><center> 862 <?php print_string('pleaseclose') ?> 863 </center></noscript> 864 <?php 865 die; 866 } 867 868 /** 869 * Given an array of values, output the HTML for a select element with those options. 870 * Normally, you only need to use the first few parameters. 871 * 872 * @param array $options The options to offer. An array of the form 873 * $options[{value}] = {text displayed for that option}; 874 * @param string $name the name of this form control, as in <select name="..." ... 875 * @param string $selected the option to select initially, default none. 876 * @param string $nothing The label for the 'nothing is selected' option. Defaults to get_string('choose'). 877 * Set this to '' if you don't want a 'nothing is selected' option. 878 * @param string $script in not '', then this is added to the <select> element as an onchange handler. 879 * @param string $nothingvalue The value corresponding to the $nothing option. Defaults to 0. 880 * @param boolean $return if false (the default) the the output is printed directly, If true, the 881 * generated HTML is returned as a string. 882 * @param boolean $disabled if true, the select is generated in a disabled state. Default, false. 883 * @param int $tabindex if give, sets the tabindex attribute on the <select> element. Default none. 884 * @param string $id value to use for the id attribute of the <select> element. If none is given, 885 * then a suitable one is constructed. 886 * @param mixed $listbox if false, display as a dropdown menu. If true, display as a list box. 887 * By default, the list box will have a number of rows equal to min(10, count($options)), but if 888 * $listbox is an integer, that number is used for size instead. 889 * @param boolean $multiple if true, enable multiple selections, else only 1 item can be selected. Used 890 * when $listbox display is enabled 891 * @param string $class value to use for the class attribute of the <select> element. If none is given, 892 * then a suitable one is constructed. 893 */ 894 function choose_from_menu ($options, $name, $selected='', $nothing='choose', $script='', 895 $nothingvalue='0', $return=false, $disabled=false, $tabindex=0, 896 $id='', $listbox=false, $multiple=false, $class='') { 897 898 if ($nothing == 'choose') { 899 $nothing = get_string('choose') .'...'; 900 } 901 902 $attributes = ($script) ? 'onchange="'. $script .'"' : ''; 903 if ($disabled) { 904 $attributes .= ' disabled="disabled"'; 905 } 906 907 if ($tabindex) { 908 $attributes .= ' tabindex="'.$tabindex.'"'; 909 } 910 911 if ($id ==='') { 912 $id = 'menu'.$name; 913 // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading 914 $id = str_replace('[', '', $id); 915 $id = str_replace(']', '', $id); 916 } 917 918 if ($class ==='') { 919 $class = 'menu'.$name; 920 // name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading 921 $class = str_replace('[', '', $class); 922 $class = str_replace(']', '', $class); 923 } 924 $class = 'select ' . $class; /// Add 'select' selector always 925 926 if ($listbox) { 927 if (is_integer($listbox)) { 928 $size = $listbox; 929 } else { 930 $numchoices = count($options); 931 if ($nothing) { 932 $numchoices += 1; 933 } 934 $size = min(10, $numchoices); 935 } 936 $attributes .= ' size="' . $size . '"'; 937 if ($multiple) { 938 $attributes .= ' multiple="multiple"'; 939 } 940 } 941 942 $output = '<select id="'. $id .'" class="'. $class .'" name="'. $name .'" '. $attributes .'>' . "\n"; 943 if ($nothing) { 944 $output .= ' <option value="'. s($nothingvalue) .'"'. "\n"; 945 if ($nothingvalue === $selected) { 946 $output .= ' selected="selected"'; 947 } 948 $output .= '>'. $nothing .'</option>' . "\n"; 949 } 950 951 if (!empty($options)) { 952 foreach ($options as $value => $label) { 953 $output .= ' <option value="'. s($value) .'"'; 954 if ((string)$value == (string)$selected || 955 (is_array($selected) && in_array($value, $selected))) { 956 $output .= ' selected="selected"'; 957 } 958 if ($label === '') { 959 $output .= '>'. $value .'</option>' . "\n"; 960 } else { 961 $output .= '>'. $label .'</option>' . "\n"; 962 } 963 } 964 } 965 $output .= '</select>' . "\n"; 966 967 if ($return) { 968 return $output; 969 } else { 970 echo $output; 971 } 972 } 973 974 /** 975 * Choose value 0 or 1 from a menu with options 'No' and 'Yes'. 976 * Other options like choose_from_menu. 977 * @param string $name 978 * @param string $selected 979 * @param string $string (defaults to '') 980 * @param boolean $return whether this function should return a string or output it (defaults to false) 981 * @param boolean $disabled (defaults to false) 982 * @param int $tabindex 983 */ 984 function choose_from_menu_yesno($name, $selected, $script = '', 985 $return = false, $disabled = false, $tabindex = 0) { 986 return choose_from_menu(array(get_string('no'), get_string('yes')), $name, 987 $selected, '', $script, '0', $return, $disabled, $tabindex); 988 } 989 990 /** 991 * Just like choose_from_menu, but takes a nested array (2 levels) and makes a dropdown menu 992 * including option headings with the first level. 993 */ 994 function choose_from_menu_nested($options,$name,$selected='',$nothing='choose',$script = '', 995 $nothingvalue=0,$return=false,$disabled=false,$tabindex=0) { 996 997 if ($nothing == 'choose') { 998 $nothing = get_string('choose') .'...'; 999 } 1000 1001 $attributes = ($script) ? 'onchange="'. $script .'"' : ''; 1002 if ($disabled) { 1003 $attributes .= ' disabled="disabled"'; 1004 } 1005 1006 if ($tabindex) { 1007 $attributes .= ' tabindex="'.$tabindex.'"'; 1008 } 1009 1010 $output = '<select id="menu'.$name.'" name="'. $name .'" '. $attributes .'>' . "\n"; 1011 if ($nothing) { 1012 $output .= ' <option value="'. $nothingvalue .'"'. "\n"; 1013 if ($nothingvalue === $selected) { 1014 $output .= ' selected="selected"'; 1015 } 1016 $output .= '>'. $nothing .'</option>' . "\n"; 1017 } 1018 if (!empty($options)) { 1019 foreach ($options as $section => $values) { 1020 $output .= ' <optgroup label="'. s(strip_tags(format_string($section))) .'">'."\n"; 1021 foreach ($values as $value => $label) { 1022 $output .= ' <option value="'. format_string($value) .'"'; 1023 if ((string)$value == (string)$selected) { 1024 $output .= ' selected="selected"'; 1025 } 1026 if ($label === '') { 1027 $output .= '>'. $value .'</option>' . "\n"; 1028 } else { 1029 $output .= '>'. $label .'</option>' . "\n"; 1030 } 1031 } 1032 $output .= ' </optgroup>'."\n"; 1033 } 1034 } 1035 $output .= '</select>' . "\n"; 1036 1037 if ($return) { 1038 return $output; 1039 } else { 1040 echo $output; 1041 } 1042 } 1043 1044 1045 /** 1046 * Given an array of values, creates a group of radio buttons to be part of a form 1047 * 1048 * @param array $options An array of value-label pairs for the radio group (values as keys) 1049 * @param string $name Name of the radiogroup (unique in the form) 1050 * @param string $checked The value that is already checked 1051 */ 1052 function choose_from_radio ($options, $name, $checked='', $return=false) { 1053 1054 static $idcounter = 0; 1055 1056 if (!$name) { 1057 $name = 'unnamed'; 1058 } 1059 1060 $output = '<span class="radiogroup '.$name."\">\n"; 1061 1062 if (!empty($options)) { 1063 $currentradio = 0; 1064 foreach ($options as $value => $label) { 1065 $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter); 1066 $output .= ' <span class="radioelement '.$name.' rb'.$currentradio."\">"; 1067 $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="radio" value="'.$value.'"'; 1068 if ($value == $checked) { 1069 $output .= ' checked="checked"'; 1070 } 1071 if ($label === '') { 1072 $output .= ' /> <label for="'.$htmlid.'">'. $value .'</label></span>' . "\n"; 1073 } else { 1074 $output .= ' /> <label for="'.$htmlid.'">'. $label .'</label></span>' . "\n"; 1075 } 1076 $currentradio = ($currentradio + 1) % 2; 1077 } 1078 } 1079 1080 $output .= '</span>' . "\n"; 1081 1082 if ($return) { 1083 return $output; 1084 } else { 1085 echo $output; 1086 } 1087 } 1088 1089 /** Display an standard html checkbox with an optional label 1090 * 1091 * @param string $name The name of the checkbox 1092 * @param string $value The valus that the checkbox will pass when checked 1093 * @param boolean $checked The flag to tell the checkbox initial state 1094 * @param string $label The label to be showed near the checkbox 1095 * @param string $alt The info to be inserted in the alt tag 1096 */ 1097 function print_checkbox ($name, $value, $checked = true, $label = '', $alt = '', $script='',$return=false) { 1098 1099 static $idcounter = 0; 1100 1101 if (!$name) { 1102 $name = 'unnamed'; 1103 } 1104 1105 if ($alt) { 1106 $alt = strip_tags($alt); 1107 } else { 1108 $alt = 'checkbox'; 1109 } 1110 1111 if ($checked) { 1112 $strchecked = ' checked="checked"'; 1113 } else { 1114 $strchecked = ''; 1115 } 1116 1117 $htmlid = 'auto-cb'.sprintf('%04d', ++$idcounter); 1118 $output = '<span class="checkbox '.$name."\">"; 1119 $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="checkbox" value="'.$value.'" alt="'.$alt.'"'.$strchecked.' '.((!empty($script)) ? ' onclick="'.$script.'" ' : '').' />'; 1120 if(!empty($label)) { 1121 $output .= ' <label for="'.$htmlid.'">'.$label.'</label>'; 1122 } 1123 $output .= '</span>'."\n"; 1124 1125 if (empty($return)) { 1126 echo $output; 1127 } else { 1128 return $output; 1129 } 1130 1131 } 1132 1133 /** Display an standard html text field with an optional label 1134 * 1135 * @param string $name The name of the text field 1136 * @param string $value The value of the text field 1137 * @param string $label The label to be showed near the text field 1138 * @param string $alt The info to be inserted in the alt tag 1139 */ 1140 function print_textfield ($name, $value, $alt = '',$size=50,$maxlength=0, $return=false) { 1141 1142 static $idcounter = 0; 1143 1144 if (empty($name)) { 1145 $name = 'unnamed'; 1146 } 1147 1148 if (empty($alt)) { 1149 $alt = 'textfield'; 1150 } 1151 1152 if (!empty($maxlength)) { 1153 $maxlength = ' maxlength="'.$maxlength.'" '; 1154 } 1155 1156 $htmlid = 'auto-tf'.sprintf('%04d', ++$idcounter); 1157 $output = '<span class="textfield '.$name."\">"; 1158 $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="text" value="'.$value.'" size="'.$size.'" '.$maxlength.' alt="'.$alt.'" />'; 1159 1160 $output .= '</span>'."\n"; 1161 1162 if (empty($return)) { 1163 echo $output; 1164 } else { 1165 return $output; 1166 } 1167 1168 } 1169 1170 1171 /** 1172 * Implements a complete little popup form 1173 * 1174 * @uses $CFG 1175 * @param string $common The URL up to the point of the variable that changes 1176 * @param array $options Alist of value-label pairs for the popup list 1177 * @param string $formid Id must be unique on the page (originaly $formname) 1178 * @param string $selected The option that is already selected 1179 * @param string $nothing The label for the "no choice" option 1180 * @param string $help The name of a help page if help is required 1181 * @param string $helptext The name of the label for the help button 1182 * @param boolean $return Indicates whether the function should return the text 1183 * as a string or echo it directly to the page being rendered 1184 * @param string $targetwindow The name of the target page to open the linked page in. 1185 * @param string $selectlabel Text to place in a [label] element - preferred for accessibility. 1186 * @param array $optionsextra TODO, an array? 1187 * @param mixed $gobutton If set, this turns off the JavaScript and uses a 'go' 1188 * button instead (as is always included for JS-disabled users). Set to true 1189 * for a literal 'Go' button, or to a string to change the name of the button. 1190 * @return string If $return is true then the entire form is returned as a string. 1191 * @todo Finish documenting this function<br> 1192 */ 1193 function popup_form($common, $options, $formid, $selected='', $nothing='choose', $help='', $helptext='', $return=false, 1194 $targetwindow='self', $selectlabel='', $optionsextra=NULL, $gobutton=NULL) { 1195 1196 global $CFG; 1197 static $go, $choose; /// Locally cached, in case there's lots on a page 1198 1199 if (empty($options)) { 1200 return ''; 1201 } 1202 1203 if (!isset($go)) { 1204 $go = get_string('go'); 1205 } 1206 1207 if ($nothing == 'choose') { 1208 if (!isset($choose)) { 1209 $choose = get_string('choose'); 1210 } 1211 $nothing = $choose.'...'; 1212 } 1213 1214 // changed reference to document.getElementById('id_abc') instead of document.abc 1215 // MDL-7861 1216 $output = '<form action="'.$CFG->wwwroot.'/course/jumpto.php"'. 1217 ' method="get" '. 1218 $CFG->frametarget. 1219 ' id="'.$formid.'"'. 1220 ' class="popupform">'; 1221 if ($help) { 1222 $button = helpbutton($help, $helptext, 'moodle', true, false, '', true); 1223 } else { 1224 $button = ''; 1225 } 1226 1227 if ($selectlabel) { 1228 $selectlabel = '<label for="'.$formid.'_jump">'.$selectlabel.'</label>'; 1229 } 1230 1231 if ($gobutton) { 1232 // Using the no-JavaScript version 1233 $javascript = ''; 1234 } else if (check_browser_version('MSIE') || (check_browser_version('Opera') && !check_browser_operating_system("Linux"))) { 1235 //IE and Opera fire the onchange when ever you move into a dropdown list with the keyboard. 1236 //onfocus will call a function inside dropdown.js. It fixes this IE/Opera behavior. 1237 //Note: There is a bug on Opera+Linux with the javascript code (first mouse selection is inactive), 1238 //so we do not fix the Opera behavior on Linux 1239 $javascript = ' onfocus="initSelect(\''.$formid.'\','.$targetwindow.')"'; 1240 } else { 1241 //Other browser 1242 $javascript = ' onchange="'.$targetwindow. 1243 '.location=document.getElementById(\''.$formid. 1244 '\').jump.options[document.getElementById(\''. 1245 $formid.'\').jump.selectedIndex].value;"'; 1246 } 1247 1248 $output .= '<div>'.$selectlabel.$button.'<select id="'.$formid.'_jump" name="jump"'.$javascript.'>'."\n"; 1249 1250 if ($nothing != '') { 1251 $output .= " <option value=\"javascript:void(0)\">$nothing</option>\n"; 1252 } 1253 1254 $inoptgroup = false; 1255 1256 foreach ($options as $value => $label) { 1257 1258 if ($label == '--') { /// we are ending previous optgroup 1259 /// Check to see if we already have a valid open optgroup 1260 /// XHTML demands that there be at least 1 option within an optgroup 1261 if ($inoptgroup and (count($optgr) > 1) ) { 1262 $output .= implode('', $optgr); 1263 $output .= ' </optgroup>'; 1264 } 1265 $optgr = array(); 1266 $inoptgroup = false; 1267 continue; 1268 } else if (substr($label,0,2) == '--') { /// we are starting a new optgroup 1269 1270 /// Check to see if we already have a valid open optgroup 1271 /// XHTML demands that there be at least 1 option within an optgroup 1272 if ($inoptgroup and (count($optgr) > 1) ) { 1273 $output .= implode('', $optgr); 1274 $output .= ' </optgroup>'; 1275 } 1276 1277 unset($optgr); 1278 $optgr = array(); 1279 1280 $optgr[] = ' <optgroup label="'. s(format_string(substr($label,2))) .'">'; // Plain labels 1281 1282 $inoptgroup = true; /// everything following will be in an optgroup 1283 continue; 1284 1285 } else { 1286 if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) 1287 { 1288 $url=sid_process_url( $common . $value ); 1289 } else 1290 { 1291 $url=$common . $value; 1292 } 1293 $optstr = ' <option value="' . $url . '"'; 1294 1295 if ($value == $selected) { 1296 $optstr .= ' selected="selected"'; 1297 } 1298 1299 if (!empty($optionsextra[$value])) { 1300 $optstr .= ' '.$optionsextra[$value]; 1301 } 1302 1303 if ($label) { 1304 $optstr .= '>'. $label .'</option>' . "\n"; 1305 } else { 1306 $optstr .= '>'. $value .'</option>' . "\n"; 1307 } 1308 1309 if ($inoptgroup) { 1310 $optgr[] = $optstr; 1311 } else { 1312 $output .= $optstr; 1313 } 1314 } 1315 1316 } 1317 1318 /// catch the final group if not closed 1319 if ($inoptgroup and count($optgr) > 1) { 1320 $output .= implode('', $optgr); 1321 $output .= ' </optgroup>'; 1322 } 1323 1324 $output .= '</select>'; 1325 $output .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />'; 1326 if ($gobutton) { 1327 $output .= '<input type="submit" value="'. 1328 ($gobutton===true ? $go : $gobutton).'" />'; 1329 } else { 1330 $output .= '<div id="noscript'.$formid.'" style="display: inline;">'; 1331 $output .= '<input type="submit" value="'.$go.'" /></div>'; 1332 $output .= '<script type="text/javascript">'. 1333 "\n//<![CDATA[\n". 1334 'document.getElementById("noscript'.$formid.'").style.display = "none";'. 1335 "\n//]]>\n".'</script>'; 1336 } 1337 $output .= '</div></form>'; 1338 1339 if ($return) { 1340 return $output; 1341 } else { 1342 echo $output; 1343 } 1344 } 1345 1346 1347 /** 1348 * Prints some red text 1349 * 1350 * @param string $error The text to be displayed in red 1351 */ 1352 function formerr($error) { 1353 1354 if (!empty($error)) { 1355 echo '<span class="error">'. $error .'</span>'; 1356 } 1357 } 1358 1359 /** 1360 * Validates an email to make sure it makes sense. 1361 * 1362 * @param string $address The email address to validate. 1363 * @return boolean 1364 */ 1365 function validate_email($address) { 1366 1367 return (ereg('^[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+'. 1368 '(\.[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+)*'. 1369 '@'. 1370 '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'. 1371 '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$', 1372 $address)); 1373 } 1374 1375 /** 1376 * Extracts file argument either from file parameter or PATH_INFO 1377 * 1378 * @param string $scriptname name of the calling script 1379 * @return string file path (only safe characters) 1380 */ 1381 function get_file_argument($scriptname) { 1382 global $_SERVER; 1383 1384 $relativepath = FALSE; 1385 1386 // first try normal parameter (compatible method == no relative links!) 1387 $relativepath = optional_param('file', FALSE, PARAM_PATH); 1388 if ($relativepath === '/testslasharguments') { 1389 echo 'test -1 : Incorrect use - try "file.php/testslasharguments" instead'; //indicate fopen/fread works for health center 1390 die; 1391 } 1392 1393 // then try extract file from PATH_INFO (slasharguments method) 1394 if (!$relativepath and !empty($_SERVER['PATH_INFO'])) { 1395 $path_info = $_SERVER['PATH_INFO']; 1396 // check that PATH_INFO works == must not contain the script name 1397 if (!strpos($path_info, $scriptname)) { 1398 $relativepath = clean_param(rawurldecode($path_info), PARAM_PATH); 1399 if ($relativepath === '/testslasharguments') { 1400 echo 'test 1 : Slasharguments test passed. Server confguration is compatible with file.php/1/pic.jpg slashargument setting.'; //indicate ok for health center 1401 die; 1402 } 1403 } 1404 } 1405 1406 // now if both fail try the old way 1407 // (for compatibility with misconfigured or older buggy php implementations) 1408 if (!$relativepath) { 1409 $arr = explode($scriptname, me()); 1410 if (!empty($arr[1])) { 1411 $path_info = strip_querystring($arr[1]); 1412 $relativepath = clean_param(rawurldecode($path_info), PARAM_PATH); 1413 if ($relativepath === '/testslasharguments') { 1414 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 1415 die; 1416 } 1417 } 1418 } 1419 1420 return $relativepath; 1421 } 1422 1423 /** 1424 * Searches the current environment variables for some slash arguments 1425 * 1426 * @param string $file ? 1427 * @todo Finish documenting this function 1428 */ 1429 function get_slash_arguments($file='file.php') { 1430 1431 if (!$string = me()) { 1432 return false; 1433 } 1434 1435 $pathinfo = explode($file, $string); 1436 1437 if (!empty($pathinfo[1])) { 1438 return addslashes($pathinfo[1]); 1439 } else { 1440 return false; 1441 } 1442 } 1443 1444 /** 1445 * Extracts arguments from "/foo/bar/something" 1446 * eg http://mysite.com/script.php/foo/bar/something 1447 * 1448 * @param string $string ? 1449 * @param int $i ? 1450 * @return array|string 1451 * @todo Finish documenting this function 1452 */ 1453 function parse_slash_arguments($string, $i=0) { 1454 1455 if (detect_munged_arguments($string)) { 1456 return false; 1457 } 1458 $args = explode('/', $string); 1459 1460 if ($i) { // return just the required argument 1461 return $args[$i]; 1462 1463 } else { // return the whole array 1464 array_shift($args); // get rid of the empty first one 1465 return $args; 1466 } 1467 } 1468 1469 /** 1470 * Just returns an array of text formats suitable for a popup menu 1471 * 1472 * @uses FORMAT_MOODLE 1473 * @uses FORMAT_HTML 1474 * @uses FORMAT_PLAIN 1475 * @uses FORMAT_MARKDOWN 1476 * @return array 1477 */ 1478 function format_text_menu() { 1479 1480 return array (FORMAT_MOODLE => get_string('formattext'), 1481 FORMAT_HTML => get_string('formathtml'), 1482 FORMAT_PLAIN => get_string('formatplain'), 1483 FORMAT_MARKDOWN => get_string('formatmarkdown')); 1484 } 1485 1486 /** 1487 * Given text in a variety of format codings, this function returns 1488 * the text as safe HTML. 1489 * 1490 * This function should mainly be used for long strings like posts, 1491 * answers, glossary items etc. For short strings @see format_string(). 1492 * 1493 * @uses $CFG 1494 * @uses FORMAT_MOODLE 1495 * @uses FORMAT_HTML 1496 * @uses FORMAT_PLAIN 1497 * @uses FORMAT_WIKI 1498 * @uses FORMAT_MARKDOWN 1499 * @param string $text The text to be formatted. This is raw text originally from user input. 1500 * @param int $format Identifier of the text format to be used 1501 * (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN) 1502 * @param array $options ? 1503 * @param int $courseid ? 1504 * @return string 1505 * @todo Finish documenting this function 1506 */ 1507 function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) { 1508 1509 global $CFG, $COURSE; 1510 1511 static $croncache = array(); 1512 1513 if ($text === '' || is_null($text)) { 1514 return ''; // no need to do any filters and cleaning 1515 } 1516 1517 if (!isset($options->trusttext)) { 1518 $options->trusttext = false; 1519 } 1520 1521 if (!isset($options->noclean)) { 1522 $options->noclean=false; 1523 } 1524 if (!isset($options->nocache)) { 1525 $options->nocache=false; 1526 } 1527 if (!isset($options->smiley)) { 1528 $options->smiley=true; 1529 } 1530 if (!isset($options->filter)) { 1531 $options->filter=true; 1532 } 1533 if (!isset($options->para)) { 1534 $options->para=true; 1535 } 1536 if (!isset($options->newlines)) { 1537 $options->newlines=true; 1538 } 1539 1540 if (empty($courseid)) { 1541 $courseid = $COURSE->id; 1542 } 1543 1544 if (!empty($CFG->cachetext) and empty($options->nocache)) { 1545 $time = time() - $CFG->cachetext; 1546 $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); 1547 1548 if (defined('FULLME') and FULLME == 'cron') { 1549 if (isset($croncache[$md5key])) { 1550 return $croncache[$md5key]; 1551 } 1552 } 1553 1554 if ($oldcacheitem = get_record_sql('SELECT * FROM '.$CFG->prefix.'cache_text WHERE md5key = \''.$md5key.'\'', true)) { 1555 if ($oldcacheitem->timemodified >= $time) { 1556 if (defined('FULLME') and FULLME == 'cron') { 1557 if (count($croncache) > 150) { 1558 reset($croncache); 1559 $key = key($croncache); 1560 unset($croncache[$key]); 1561 } 1562 $croncache[$md5key] = $oldcacheitem->formattedtext; 1563 } 1564 return $oldcacheitem->formattedtext; 1565 } 1566 } 1567 } 1568 1569 // trusttext overrides the noclean option! 1570 if ($options->trusttext) { 1571 if (trusttext_present($text)) { 1572 $text = trusttext_strip($text); 1573 if (!empty($CFG->enabletrusttext)) { 1574 $options->noclean = true; 1575 } else { 1576 $options->noclean = false; 1577 } 1578 } else { 1579 $options->noclean = false; 1580 } 1581 } else if (!debugging('', DEBUG_DEVELOPER)) { 1582 // strip any forgotten trusttext in non-developer mode 1583 // do not forget to disable text cache when debugging trusttext!! 1584 $text = trusttext_strip($text); 1585 } 1586 1587 $CFG->currenttextiscacheable = true; // Default status - can be changed by any filter 1588 1589 switch ($format) { 1590 case FORMAT_HTML: 1591 if ($options->smiley) { 1592 replace_smilies($text); 1593 } 1594 if (!$options->noclean) { 1595 $text = clean_text($text, FORMAT_HTML); 1596 } 1597 if ($options->filter) { 1598 $text = filter_text($text, $courseid); 1599 } 1600 break; 1601 1602 case FORMAT_PLAIN: 1603 $text = s($text); // cleans dangerous JS 1604 $text = rebuildnolinktag($text); 1605 $text = str_replace(' ', ' ', $text); 1606 $text = nl2br($text); 1607 break; 1608 1609 case FORMAT_WIKI: 1610 // this format is deprecated 1611 $text = '<p>NOTICE: Wiki-like formatting has been removed from Moodle. You should not be seeing 1612 this message as all texts should have been converted to Markdown format instead. 1613 Please post a bug report to http://moodle.org/bugs with information about where you 1614 saw this message.</p>'.s($text); 1615 break; 1616 1617 case FORMAT_MARKDOWN: 1618 $text = markdown_to_html($text); 1619 if ($options->smiley) { 1620 replace_smilies($text); 1621 } 1622 if (!$options->noclean) { 1623 $text = clean_text($text, FORMAT_HTML); 1624 } 1625 1626 if ($options->filter) { 1627 $text = filter_text($text, $courseid); 1628 } 1629 break; 1630 1631 default: // FORMAT_MOODLE or anything else 1632 $text = text_to_html($text, $options->smiley, $options->para, $options->newlines); 1633 if (!$options->noclean) { 1634 $text = clean_text($text, FORMAT_HTML); 1635 } 1636 1637 if ($options->filter) { 1638 $text = filter_text($text, $courseid); 1639 } 1640 break; 1641 } 1642 1643 if (empty($options->nocache) and !empty($CFG->cachetext) and $CFG->currenttextiscacheable) { 1644 if (defined('FULLME') and FULLME == 'cron') { 1645 // special static cron cache - no need to store it in db if its not already there 1646 if (count($croncache) > 150) { 1647 reset($croncache); 1648 $key = key($croncache); 1649 unset($croncache[$key]); 1650 } 1651 $croncache[$md5key] = $text; 1652 return $text; 1653 } 1654 1655 $newcacheitem = new object(); 1656 $newcacheitem->md5key = $md5key; 1657 $newcacheitem->formattedtext = addslashes($text); 1658 $newcacheitem->timemodified = time(); 1659 if ($oldcacheitem) { // See bug 4677 for discussion 1660 $newcacheitem->id = $oldcacheitem->id; 1661 @update_record('cache_text', $newcacheitem); // Update existing record in the cache table 1662 // It's unlikely that the cron cache cleaner could have 1663 // deleted this entry in the meantime, as it allows 1664 // some extra time to cover these cases. 1665 } else { 1666 @insert_record('cache_text', $newcacheitem); // Insert a new record in the cache table 1667 // Again, it's possible that another user has caused this 1668 // record to be created already in the time that it took 1669 // to traverse this function. That's OK too, as the 1670 // call above handles duplicate entries, and eventually 1671 // the cron cleaner will delete them. 1672 } 1673 } 1674 1675 return $text; 1676 } 1677 1678 /** Converts the text format from the value to the 'internal' 1679 * name or vice versa. $key can either be the value or the name 1680 * and you get the other back. 1681 * 1682 * @param mixed int 0-4 or string one of 'moodle','html','plain','markdown' 1683 * @return mixed as above but the other way around! 1684 */ 1685 function text_format_name( $key ) { 1686 $lookup = array(); 1687 $lookup[FORMAT_MOODLE] = 'moodle'; 1688 $lookup[FORMAT_HTML] = 'html'; 1689 $lookup[FORMAT_PLAIN] = 'plain'; 1690 $lookup[FORMAT_MARKDOWN] = 'markdown'; 1691 $value = "error"; 1692 if (!is_numeric($key)) { 1693 $key = strtolower( $key ); 1694 $value = array_search( $key, $lookup ); 1695 } 1696 else { 1697 if (isset( $lookup[$key] )) { 1698 $value = $lookup[ $key ]; 1699 } 1700 } 1701 return $value; 1702 } 1703 1704 /** 1705 * Resets all data related to filters, called during upgrade or when filter settings change. 1706 * @return void 1707 */ 1708 function reset_text_filters_cache() { 1709 global $CFG; 1710 1711 delete_records('cache_text'); 1712 $purifdir = $CFG->dataroot.'/cache/htmlpurifier'; 1713 remove_dir($purifdir, true); 1714 } 1715 1716 /** Given a simple string, this function returns the string 1717 * processed by enabled string filters if $CFG->filterall is enabled 1718 * 1719 * This function should be used to print short strings (non html) that 1720 * need filter processing e.g. activity titles, post subjects, 1721 * glossary concepts. 1722 * 1723 * @param string $string The string to be filtered. 1724 * @param boolean $striplinks To strip any link in the result text (Moodle 1.8 default changed from false to true! MDL-8713) 1725 * @param int $courseid Current course as filters can, potentially, use it 1726 * @return string 1727 */ 1728 function format_string ($string, $striplinks=true, $courseid=NULL ) { 1729 1730 global $CFG, $COURSE; 1731 1732 //We'll use a in-memory cache here to speed up repeated strings 1733 static $strcache = false; 1734 1735 if ($strcache === false or count($strcache) > 2000 ) { // this number might need some tuning to limit memory usage in cron 1736 $strcache = array(); 1737 } 1738 1739 //init course id 1740 if (empty($courseid)) { 1741 $courseid = $COURSE->id; 1742 } 1743 1744 //Calculate md5 1745 $md5 = md5($string.'<+>'.$striplinks.'<+>'.$courseid.'<+>'.current_language()); 1746 1747 //Fetch from cache if possible 1748 if (isset($strcache[$md5])) { 1749 return $strcache[$md5]; 1750 } 1751 1752 // First replace all ampersands not followed by html entity code 1753 $string = preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&", $string); 1754 1755 if (!empty($CFG->filterall)) { 1756 $string = filter_string($string, $courseid); 1757 } 1758 1759 // If the site requires it, strip ALL tags from this string 1760 if (!empty($CFG->formatstringstriptags)) { 1761 $string = strip_tags($string); 1762 1763 } else { 1764 // Otherwise strip just links if that is required (default) 1765 if ($striplinks) { //strip links in string 1766 $string = preg_replace('/(<a\s[^>]+?>)(.+?)(<\/a>)/is','$2',$string); 1767 } 1768 $string = clean_text($string); 1769 } 1770 1771 //Store to cache 1772 $strcache[$md5] = $string; 1773 1774 return $string; 1775 } 1776 1777 /** 1778 * Given text in a variety of format codings, this function returns 1779 * the text as plain text suitable for plain email. 1780 * 1781 * @uses FORMAT_MOODLE 1782 * @uses FORMAT_HTML 1783 * @uses FORMAT_PLAIN 1784 * @uses FORMAT_WIKI 1785 * @uses FORMAT_MARKDOWN 1786 * @param string $text The text to be formatted. This is raw text originally from user input. 1787 * @param int $format Identifier of the text format to be used 1788 * (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN) 1789 * @return string 1790 */ 1791 function format_text_email($text, $format) { 1792 1793 switch ($format) { 1794 1795 case FORMAT_PLAIN: 1796 return $text; 1797 break; 1798 1799 case FORMAT_WIKI: 1800 // there should not be any of these any more! 1801 /// This expression turns links into something nice in a text format. (Russell Jungwirth) 1802 /// From: http://php.net/manual/en/function.eregi-replace.php and simplified 1803 $text = eregi_replace('(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)','\\3 [ \\2 ]', $text); 1804 return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES))); 1805 break; 1806 1807 case FORMAT_HTML: 1808 return html_to_text($text); 1809 break; 1810 1811 case FORMAT_MOODLE: 1812 case FORMAT_MARKDOWN: 1813 default: 1814 $text = eregi_replace('(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)','\\3 [ \\2 ]', $text); 1815 return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES))); 1816 break; 1817 } 1818 } 1819 1820 /** 1821 * Given some text in HTML format, this function will pass it 1822 * through any filters that have been defined in $CFG->textfilterx 1823 * The variable defines a filepath to a file containing the 1824 * filter function. The file must contain a variable called 1825 * $textfilter_function which contains the name of the function 1826 * with $courseid and $text parameters 1827 * 1828 * @param string $text The text to be passed through format filters 1829 * @param int $courseid ? 1830 * @return string 1831 * @todo Finish documenting this function 1832 */ 1833 function filter_text($text, $courseid=NULL) { 1834 global $CFG, $COURSE; 1835 1836 if (empty($courseid)) { 1837 $courseid = $COURSE->id; // (copied from format_text) 1838 } 1839 1840 if (!empty($CFG->textfilters)) { 1841 require_once($CFG->libdir.'/filterlib.php'); 1842 $textfilters = explode(',', $CFG->textfilters); 1843 foreach ($textfilters as $textfilter) { 1844 if (is_readable($CFG->dirroot .'/'. $textfilter .'/filter.php')) { 1845 include_once($CFG->dirroot .'/'. $textfilter .'/filter.php'); 1846 $functionname = basename($textfilter).'_filter'; 1847 if (function_exists($functionname)) { 1848 $text = $functionname($courseid, $text); 1849 } 1850 } 1851 } 1852 } 1853 1854 /// <nolink> tags removed for XHTML compatibility 1855 $text = str_replace('<nolink>', '', $text); 1856 $text = str_replace('</nolink>', '', $text); 1857 1858 return $text; 1859 } 1860 1861 1862 /** 1863 * Given a string (short text) in HTML format, this function will pass it 1864 * through any filters that have been defined in $CFG->stringfilters 1865 * The variable defines a filepath to a file containing the 1866 * filter function. The file must contain a variable called 1867 * $textfilter_function which contains the name of the function 1868 * with $courseid and $text parameters 1869 * 1870 * @param string $string The text to be passed through format filters 1871 * @param int $courseid The id of a course 1872 * @return string 1873 */ 1874 function filter_string($string, $courseid=NULL) { 1875 global $CFG, $COURSE; 1876 1877 if (empty($CFG->textfilters)) { // All filters are disabled anyway so quit 1878 return $string; 1879 } 1880 1881 if (empty($courseid)) { 1882 $courseid = $COURSE->id; 1883 } 1884 1885 require_once($CFG->libdir.'/filterlib.php'); 1886 1887 if (isset($CFG->stringfilters)) { // We have a predefined list to use, great! 1888 if (empty($CFG->stringfilters)) { // but it's blank, so finish now 1889 return $string; 1890 } 1891 $stringfilters = explode(',', $CFG->stringfilters); // ..use the list we have 1892 1893 } else { // Otherwise try to derive a list from textfilters 1894 if (strpos($CFG->textfilters, 'filter/multilang') !== false) { // Multilang is here 1895 $stringfilters = array('filter/multilang'); // Let's use just that 1896 $CFG->stringfilters = 'filter/multilang'; // Save it for next time through 1897 } else { 1898 $CFG->stringfilters = ''; // Save the result and return 1899 return $string; 1900 } 1901 } 1902 1903 1904 foreach ($stringfilters as $stringfilter) { 1905 if (is_readable($CFG->dirroot .'/'. $stringfilter .'/filter.php')) { 1906 include_once($CFG->dirroot .'/'. $stringfilter .'/filter.php'); 1907 $functionname = basename($stringfilter).'_filter'; 1908 if (function_exists($functionname)) { 1909 $string = $functionname($courseid, $string); 1910 } 1911 } 1912 } 1913 1914 /// <nolink> tags removed for XHTML compatibility 1915 $string = str_replace('<nolink>', '', $string); 1916 $string = str_replace('</nolink>', '', $string); 1917 1918 return $string; 1919 } 1920 1921 /** 1922 * Is the text marked as trusted? 1923 * 1924 * @param string $text text to be searched for TRUSTTEXT marker 1925 * @return boolean 1926 */ 1927 function trusttext_present($text) { 1928 if (strpos($text, TRUSTTEXT) !== FALSE) { 1929 return true; 1930 } else { 1931 return false; 1932 } 1933 } 1934 1935 /** 1936 * This funtion MUST be called before the cleaning or any other 1937 * function that modifies the data! We do not know the origin of trusttext 1938 * in database, if it gets there in tweaked form we must not convert it 1939 * to supported form!!! 1940 * 1941 * Please be carefull not to use stripslashes on data from database 1942 * or twice stripslashes when processing data recieved from user. 1943 * 1944 * @param string $text text that may contain TRUSTTEXT marker 1945 * @return text without any TRUSTTEXT marker 1946 */ 1947 function trusttext_strip($text) { 1948 global $CFG; 1949 1950 while (true) { //removing nested TRUSTTEXT 1951 $orig = $text; 1952 $text = str_replace(TRUSTTEXT, '', $text); 1953 if (strcmp($orig, $text) === 0) { 1954 return $text; 1955 } 1956 } 1957 } 1958 1959 /** 1960 * Mark text as trusted, such text may contain any HTML tags because the 1961 * normal text cleaning will be bypassed. 1962 * Please make sure that the text comes from trusted user before storing 1963 * it into database! 1964 */ 1965 function trusttext_mark($text) { 1966 global $CFG; 1967 if (!empty($CFG->enabletrusttext) and (strpos($text, TRUSTTEXT) === FALSE)) { 1968 return TRUSTTEXT.$text; 1969 } else { 1970 return $text; 1971 } 1972 } 1973 function trusttext_after_edit(&$text, $context) { 1974 if (has_capability('moodle/site:trustcontent', $context)) { 1975 $text = trusttext_strip($text); 1976 $text = trusttext_mark($text); 1977 } else { 1978 $text = trusttext_strip($text); 1979 } 1980 } 1981 1982 function trusttext_prepare_edit(&$text, &$format, $usehtmleditor, $context) { 1983 global $CFG; 1984 1985 $options = new object(); 1986 $options->smiley = false; 1987 $options->filter = false; 1988 if (!empty($CFG->enabletrusttext) 1989 and has_capability('moodle/site:trustcontent', $context) 1990 and trusttext_present($text)) { 1991 $options->noclean = true; 1992 } else { 1993 $options->noclean = false; 1994 } 1995 $text = trusttext_strip($text); 1996 if ($usehtmleditor) { 1997 $text = format_text($text, $format, $options); 1998 $format = FORMAT_HTML; 1999 } else if (!$options->noclean){ 2000 $text = clean_text($text, $format); 2001 } 2002 } 2003 2004 /** 2005 * Given raw text (eg typed in by a user), this function cleans it up 2006 * and removes any nasty tags that could mess up Moodle pages. 2007 * 2008 * @uses FORMAT_MOODLE 2009 * @uses FORMAT_PLAIN 2010 * @uses ALLOWED_TAGS 2011 * @param string $text The text to be cleaned 2012 * @param int $format Identifier of the text format to be used 2013 * (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN) 2014 * @return string The cleaned up text 2015 */ 2016 function clean_text($text, $format=FORMAT_MOODLE) { 2017 2018 global $ALLOWED_TAGS, $CFG; 2019 2020 if (empty($text) or is_numeric($text)) { 2021 return (string)$text; 2022 } 2023 2024 switch ($format) { 2025 case FORMAT_PLAIN: 2026 return $text; 2027 2028 default: 2029 2030 if (!empty($CFG->enablehtmlpurifier)) { 2031 //this is PHP5 only, the lib/setup.php contains a disabler for PHP4 2032 $text = purify_html($text); 2033 } else { 2034 /// Fix non standard entity notations 2035 $text = preg_replace('/�*([0-9]+);?/', "&#\\1;", $text); 2036 $text = preg_replace('/�*([0-9a-fA-F]+);?/', "&#x\\1;", $text); 2037 $text = preg_replace('[\x00-\x08\x0b-\x0c\x0e-\x1f]', '', $text); 2038 2039 /// Remove tags that are not allowed 2040 $text = strip_tags($text, $ALLOWED_TAGS); 2041 2042 /// Clean up embedded scripts and , using kses 2043 $text = cleanAttributes($text); 2044 2045 /// Again remove tags that are not allowed 2046 $text = strip_tags($text, $ALLOWED_TAGS); 2047 2048 } 2049 2050 /// Remove potential script events - some extra protection for undiscovered bugs in our code 2051 $text = eregi_replace("([^a-z])language([[:space:]]*)=", "\\1Xlanguage=", $text); 2052 $text = eregi_replace("([^a-z])on([a-z]+)([[:space:]]*)=", "\\1Xon\\2=", $text); 2053 2054 return $text; 2055 } 2056 } 2057 2058 /** 2059 * KSES replacement cleaning function - uses HTML Purifier. 2060 * 2061 * @global object 2062 * @param string $text The (X)HTML string to purify 2063 */ 2064 function purify_html($text) { 2065 global $CFG; 2066 2067 // this can not be done only once because we sometimes need to reset the cache 2068 $cachedir = $CFG->dataroot.'/cache/htmlpurifier'; 2069 check_dir_exists($cachedir); 2070 2071 static $purifier = false; 2072 if ($purifier === false) { 2073 require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.safe-includes.php'; 2074 $config = HTMLPurifier_Config::createDefault(); 2075 2076 $config->set('HTML.DefinitionID', 'moodlehtml'); 2077 $config->set('HTML.DefinitionRev', 1); 2078 $config->set('Cache.SerializerPath', $cachedir); 2079 //$config->set('Cache.SerializerPermission', $CFG->directorypermissions); // it would be nice to get this upstream 2080 $config->set('Core.NormalizeNewlines', false); 2081 $config->set('Core.ConvertDocumentToFragment', true); 2082 $config->set('Core.Encoding', 'UTF-8'); 2083 $config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); 2084 $config->set('URI.AllowedSchemes', array('http'=>true, 'https'=>true, 'ftp'=>true, 'irc'=>true, 'nntp'=>true, 'news'=>true, 'rtsp'=>true, 'teamspeak'=>true, 'gopher'=>true, 'mms'=>true)); 2085 $config->set('Attr.AllowedFrameTargets', array('_blank')); 2086 2087 if (!empty($CFG->allowobjectembed)) { 2088 $config->set('HTML.SafeObject', true); 2089 $config->set('Output.FlashCompat', true); 2090 $config->set('HTML.SafeEmbed', true); 2091 } 2092 2093 $def = $config->getHTMLDefinition(true); 2094 $def->addElement('nolink', 'Block', 'Flow', array()); // skip our filters inside 2095 $def->addElement('tex', 'Inline', 'Inline', array()); // tex syntax, equivalent to $$xx$$ 2096 $def->addElement('algebra', 'Inline', 'Inline', array()); // algebra syntax, equivalent to @@xx@@ 2097 $def->addElement('lang', 'Block', 'Flow', array(), array('lang'=>'CDATA')); // old anf future style multilang - only our hacked lang attribute 2098 $def->addAttribute('span', 'xxxlang', 'CDATA'); // current problematic multilang 2099 2100 $purifier = new HTMLPurifier($config); 2101 } 2102 2103 $multilang = (strpos($text, 'class="multilang"') !== false); 2104 2105 if ($multilang) { 2106 $text = preg_replace('/<span(\s+lang="([a-zA-Z0-9_-]+)"|\s+class="multilang"){2}\s*>/', '<span xxxlang="$2}">', $text); 2107 } 2108 $text = $purifier->purify($text); 2109 if ($multilang) { 2110 $text = preg_replace('/<span xxxlang="([a-zA-Z0-9_-]+)">/', '<span lang="$1}" class="multilang">', $text); 2111 } 2112 2113 return $text; 2114 } 2115 2116 /** 2117 * This function takes a string and examines it for HTML tags. 2118 * If tags are detected it passes the string to a helper function {@link cleanAttributes2()} 2119 * which checks for attributes and filters them for malicious content 2120 * 17/08/2004 :: Eamon DOT Costello AT dcu DOT ie 2121 * 2122 * @param string $str The string to be examined for html tags 2123 * @return string 2124 */ 2125 function cleanAttributes($str){ 2126 $result = preg_replace_callback( 2127 '%(<[^>]*(>|$)|>)%m', #search for html tags 2128 "cleanAttributes2", 2129 $str 2130 ); 2131 return $result; 2132 } 2133 2134 /** 2135 * This function takes a string with an html tag and strips out any unallowed 2136 * protocols e.g. javascript: 2137 * It calls ancillary functions in kses which are prefixed by kses 2138 * 17/08/2004 :: Eamon DOT Costello AT dcu DOT ie 2139 * 2140 * @param array $htmlArray An array from {@link cleanAttributes()}, containing in its 1st 2141 * element the html to be cleared 2142 * @return string 2143 */ 2144 function cleanAttributes2($htmlArray){ 2145 2146 global $CFG, $ALLOWED_PROTOCOLS; 2147 require_once($CFG->libdir .'/kses.php'); 2148 2149 $htmlTag = $htmlArray[1]; 2150 if (substr($htmlTag, 0, 1) != '<') { 2151 return '>'; //a single character ">" detected 2152 } 2153 if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $htmlTag, $matches)) { 2154 return ''; // It's seriously malformed 2155 } 2156 $slash = trim($matches[1]); //trailing xhtml slash 2157 $elem = $matches[2]; //the element name 2158 $attrlist = $matches[3]; // the list of attributes as a string 2159 2160 $attrArray = kses_hair($attrlist, $ALLOWED_PROTOCOLS); 2161 2162 $attStr = ''; 2163 foreach ($attrArray as $arreach) { 2164 $arreach['name'] = strtolower($arreach['name']); 2165 if ($arreach['name'] == 'style') { 2166 $value = $arreach['value']; 2167 while (true) { 2168 $prevvalue = $value; 2169 $value = kses_no_null($value); 2170 $value = preg_replace("/\/\*.*\*\//Us", '', $value); 2171 $value = kses_decode_entities($value); 2172 $value = preg_replace('/(&#[0-9]+)(;?)/', "\\1;", $value); 2173 $value = preg_replace('/(&#x[0-9a-fA-F]+)(;?)/', "\\1;", $value); 2174 if ($value === $prevvalue) { 2175 $arreach['value'] = $value; 2176 break; 2177 } 2178 } 2179 $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']); 2180 $arreach['value'] = preg_replace("/v\s*b\s*s\s*c\s*r\s*i\s*p\s*t/i", "Xvbscript", $arreach['value']); 2181 $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']); 2182 $arreach['value'] = preg_replace("/b\s*i\s*n\s*d\s*i\s*n\s*g/i", "Xbinding", $arreach['value']); 2183 } else if ($arreach['name'] == 'href') { 2184 //Adobe Acrobat Reader XSS protection 2185 $arreach['value'] = preg_replace('/(\.(pdf|fdf|xfdf|xdp|xfd)[^#]*)#.*$/i', '$1', $arreach['value']); 2186 } 2187 $attStr .= ' '.$arreach['name'].'="'.$arreach['value'].'"'; 2188 } 2189 2190 $xhtml_slash = ''; 2191 if (preg_match('%/\s*$%', $attrlist)) { 2192 $xhtml_slash = ' /'; 2193 } 2194 return '<'. $slash . $elem . $attStr . $xhtml_slash .'>'; 2195 } 2196 2197 /** 2198 * Replaces all known smileys in the text with image equivalents 2199 * 2200 * @uses $CFG 2201 * @param string $text Passed by reference. The string to search for smily strings. 2202 * @return string 2203 */ 2204 function replace_smilies(&$text) { 2205 2206 global $CFG; 2207 2208 if (empty($CFG->emoticons)) { /// No emoticons defined, nothing to process here 2209 return; 2210 } 2211 2212 $lang = current_language(); 2213 $emoticonstring = $CFG->emoticons; 2214 static $e = array(); 2215 static $img = array(); 2216 static $emoticons = null; 2217 2218 if (is_null($emoticons)) { 2219 $emoticons = array(); 2220 if ($emoticonstring) { 2221 $items = explode('{;}', $CFG->emoticons); 2222 foreach ($items as $item) { 2223 $item = explode('{:}', $item); 2224 $emoticons[$item[0]] = $item[1]; 2225 } 2226 } 2227 } 2228 2229 2230 if (empty($img[$lang])) { /// After the first time this is not run again 2231 $e[$lang] = array(); 2232 $img[$lang] = array(); 2233 foreach ($emoticons as $emoticon => $image){ 2234 $alttext = get_string($image, 'pix'); 2235 $alttext = preg_replace('/^\[\[(.*)\]\]$/', '$1', $alttext); /// Clean alttext in case there isn't lang string for it. 2236 $e[$lang][] = $emoticon; 2237 $img[$lang][] = '<img alt="'. $alttext .'" width="15" height="15" src="'. $CFG->pixpath .'/s/'. $image .'.gif" />'; 2238 } 2239 } 2240 2241 // Exclude from transformations all the code inside <script> tags 2242 // Needed to solve Bug 1185. Thanks to jouse 2001 detecting it. :-) 2243 // Based on code from glossary fiter by Williams Castillo. 2244 // - Eloy 2245 2246 // Detect all the <script> zones to take out 2247 $excludes = array(); 2248 preg_match_all('/<script language(.+?)<\/script>/is',$text,$list_of_excludes); 2249 2250 // Take out all the <script> zones from text 2251 foreach (array_unique($list_of_excludes[0]) as $key=>$value) { 2252 $excludes['<+'.$key.'+>'] = $value; 2253 } 2254 if ($excludes) { 2255 $text = str_replace($excludes,array_keys($excludes),$text); 2256 } 2257 2258 /// this is the meat of the code - this is run every time 2259 $text = str_replace($e[$lang], $img[$lang], $text); 2260 2261 // Recover all the <script> zones to text 2262 if ($excludes) { 2263 $text = str_replace(array_keys($excludes),$excludes,$text); 2264 } 2265 } 2266 2267 /** 2268 * Given plain text, makes it into HTML as nicely as possible. 2269 * May contain HTML tags already 2270 * 2271 * @uses $CFG 2272 * @param string $text The string to convert. 2273 * @param boolean $smiley Convert any smiley characters to smiley images? 2274 * @param boolean $para If true then the returned string will be wrapped in paragraph tags 2275 * @param boolean $newlines If true then lines newline breaks will be converted to HTML newline breaks. 2276 * @return string 2277 */ 2278 2279 function text_to_html($text, $smiley=true, $para=true, $newlines=true) { 2280 /// 2281 2282 global $CFG; 2283 2284 /// Remove any whitespace that may be between HTML tags 2285 $text = eregi_replace(">([[:space:]]+)<", "><", $text); 2286 2287 /// Remove any returns that precede or follow HTML tags 2288 $text = eregi_replace("([\n\r])<", " <", $text); 2289 $text = eregi_replace(">([\n\r])", "> ", $text); 2290 2291 convert_urls_into_links($text); 2292 2293 /// Make returns into HTML newlines. 2294 if ($newlines) { 2295 $text = nl2br($text); 2296 } 2297 2298 /// Turn smileys into images. 2299 if ($smiley) { 2300 replace_smilies($text); 2301 } 2302 2303 /// Wrap the whole thing in a paragraph tag if required 2304 if ($para) { 2305 return '<p>'.$text.'</p>'; 2306 } else { 2307 return $text; 2308 } 2309 } 2310 2311 /** 2312 * Given Markdown formatted text, make it into XHTML using external function 2313 * 2314 * @uses $CFG 2315 * @param string $text The markdown formatted text to be converted. 2316 * @return string Converted text 2317 */ 2318 function markdown_to_html($text) { 2319 global $CFG; 2320 2321 require_once($CFG->libdir .'/markdown.php'); 2322 2323 return Markdown($text); 2324 } 2325 2326 /** 2327 * Given HTML text, make it into plain text using external function 2328 * 2329 * @param string $html The text to be converted. 2330 * @param integer $width Width to wrap the text at. (optional, default 75 which 2331 * is a good value for email. 0 means do not limit line length.) 2332 * @return string plain text equivalent of the HTML. 2333 */ 2334 function html_to_text($html, $width = 75) { 2335 2336 global $CFG; 2337 2338 require_once($CFG->libdir .'/html2text.php'); 2339 2340 $h2t = new html2text($html, false, true, $width); 2341 $result = $h2t->get_text(); 2342 2343 return $result; 2344 } 2345 2346 /** 2347 * Given some text this function converts any URLs it finds into HTML links 2348 * 2349 * @param string $text Passed in by reference. The string to be searched for urls. 2350 */ 2351 function convert_urls_into_links(&$text) { 2352 //I've added img tags to this list of tags to ignore. 2353 //See MDL-21168 for more info. A better way to ignore tags whether or not 2354 //they are escaped partially or completely would be desirable. For example: 2355 //<a href="blah"> 2356 //<a href="blah"> 2357 //<a href="blah"> 2358 $filterignoretagsopen = array('<a\s[^>]+?>'); 2359 $filterignoretagsclose = array('</a>'); 2360 filter_save_ignore_tags($text,$filterignoretagsopen,$filterignoretagsclose,$ignoretags); 2361 2362 // Check if we support unicode modifiers in regular expressions. Cache it. 2363 // TODO: this check should be a environment requirement in Moodle 2.0, as far as unicode 2364 // chars are going to arrive to URLs officially really soon (2010?) 2365 // Original RFC regex from: http://www.bytemycode.com/snippets/snippet/796/ 2366 // Various ideas from: http://alanstorm.com/url_regex_explained 2367 // Unicode check, negative assertion and other bits from Moodle. 2368 static $unicoderegexp; 2369 if (!isset($unicoderegexp)) { 2370 $unicoderegexp = @preg_match('/\pL/u', 'a'); // This will fail silenty, returning false, 2371 } 2372 2373 $unicoderegexp = false;//force non use of unicode modifiers. MDL-21296 2374 if ($unicoderegexp) { //We can use unicode modifiers 2375 $text = preg_replace('#(?<!=["\'])(((http(s?))://)(((([\pLl0-9]([\pLl0-9]|-)*[\pLl0-9]|[\pLl0-9])\.)+([\pLl]([\pLl0-9]|-)*[\pLl0-9]|[\pLl]))|(([0-9]{1,3}\.){3}[0-9]{1,3}))(:[\pL0-9]*)?(/([\pLl0-9\.!$&\'\(\)*+,;=_~:@-]|%[a-fA-F0-9]{2})*)*(\?([\pLl0-9\.!$&\'\(\)*+,;=_~:@/?-]|%[a-fA-F0-9]{2})*)?(\#[\pLl0-9\.!$&\'\(\)*+,;=_~:@/?-]*)?)(?<![,\.;])#iu', 2376 '<a href="\\1" target="_blank">\\1</a>', $text); 2377 $text = preg_replace('#(?<!=["\']|//)((www\.([\pLl0-9]([\pLl0-9]|-)*[\pLl0-9]|[\pLl0-9])\.)+([\pLl]([\pLl0-9]|-)*[\pLl0-9]|[\pLl])(:[\pL0-9]*)?(/([\pLl0-9\.!$&\'\(\)*+,;=_~:@-]|%[a-fA-F0-9]{2})*)*(\?([\pLl0-9\.!$&\'\(\)*+,;=_~:@/?-]|%[a-fA-F0-9]{2})*)?(\#[\pLl0-9\.!$&\'\(\)*+,;=_~:@/?-]*)?)(?<![,\.;])#iu', 2378 '<a href="http://\\1" target="_blank">\\1</a>', $text); 2379 } else { //We cannot use unicode modifiers 2380 $text = preg_replace('#(?<!=["\'])(((http(s?))://)(((([a-z0-9]([a-z0-9]|-)*[a-z0-9]|[a-z0-9])\.)+([a-z]([a-z0-9]|-)*[a-z0-9]|[a-z]))|(([0-9]{1,3}\.){3}[0-9]{1,3}))(:[a-zA-Z0-9]*)?(/([a-z0-9\.!$&\'\(\)*+,;=_~:@-]|%[a-f0-9]{2})*)*(\?([a-z0-9\.!$&\'\(\)*+,;=_~:@/?-]|%[a-fA-F0-9]{2})*)?(\#[a-z0-9\.!$&\'\(\)*+,;=_~:@/?-]*)?)(?<![,\.;])#i', 2381 '<a href="\\1" target="_blank">\\1</a>', $text); 2382 $text = preg_replace('#(?<!=["\']|//)((www\.([a-z0-9]([a-z0-9]|-)*[a-z0-9]|[a-z0-9])\.)+([a-z]([a-z0-9]|-)*[a-z0-9]|[a-z])(:[a-zA-Z0-9]*)?(/([a-z0-9\.!$&\'\(\)*+,;=_~:@-]|%[a-f0-9]{2})*)*(\?([a-z0-9\.!$&\'\(\)*+,;=_~:@/?-]|%[a-fA-F0-9]{2})*)?(\#[a-z0-9\.!$&\'\(\)*+,;=_~:@/?-]*)?)(?<![,\.;])#i', 2383 '<a href="http://\\1" target="_blank">\\1</a>', $text); 2384 } 2385 2386 if (!empty($ignoretags)) { 2387 $ignoretags = array_reverse($ignoretags); /// Reversed so "progressive" str_replace() will solve some nesting problems. 2388 $text = str_replace(array_keys($ignoretags),$ignoretags,$text); 2389 } 2390 } 2391 2392 /** 2393 * This function will highlight search words in a given string 2394 * It cares about HTML and will not ruin links. It's best to use 2395 * this function after performing any conversions to HTML. 2396 * 2397 * @param string $needle The search string. Syntax like "word1 +word2 -word3" is dealt with correctly. 2398 * @param string $haystack The string (HTML) within which to highlight the search terms. 2399 * @param boolean $matchcase whether to do case-sensitive. Default case-insensitive. 2400 * @param string $prefix the string to put before each search term found. 2401 * @param string $suffix the string to put after each search term found. 2402 * @return string The highlighted HTML. 2403 */ 2404 function highlight($needle, $haystack, $matchcase = false, 2405 $prefix = '<span class="highlight">', $suffix = '</span>') { 2406 2407 /// Quick bail-out in trivial cases. 2408 if (empty($needle) or empty($haystack)) { 2409 return $haystack; 2410 } 2411 2412 /// Break up the search term into words, discard any -words and build a regexp. 2413 $words = preg_split('/ +/', trim($needle)); 2414 foreach ($words as $index => $word) { 2415 if (strpos($word, '-') === 0) { 2416 unset($words[$index]); 2417 } else if (strpos($word, '+') === 0) { 2418 $words[$index] = '\b' . preg_quote(ltrim($word, '+'), '/') . '\b'; // Match only as a complete word. 2419 } else { 2420 $words[$index] = preg_quote($word, '/'); 2421 } 2422 } 2423 $regexp = '/(' . implode('|', $words) . ')/u'; // u is do UTF-8 matching. 2424 if (!$matchcase) { 2425 $regexp .= 'i'; 2426 } 2427 2428 /// Another chance to bail-out if $search was only -words 2429 if (empty($words)) { 2430 return $haystack; 2431 } 2432 2433 /// Find all the HTML tags in the input, and store them in a placeholders array. 2434 $placeholders = array(); 2435 $matches = array(); 2436 preg_match_all('/<[^>]*>/', $haystack, $matches); 2437 foreach (array_unique($matches[0]) as $key => $htmltag) { 2438 $placeholders['<|' . $key . '|>'] = $htmltag; 2439 } 2440 2441 /// In $hastack, replace each HTML tag with the corresponding placeholder. 2442 $haystack = str_replace($placeholders, array_keys($placeholders), $haystack); 2443 2444 /// In the resulting string, Do the highlighting. 2445 $haystack = preg_replace($regexp, $prefix . '$1' . $suffix, $haystack); 2446 2447 /// Turn the placeholders back into HTML tags. 2448 $haystack = str_replace(array_keys($placeholders), $placeholders, $haystack); 2449 2450 return $haystack; 2451 } 2452 2453 /** 2454 * This function will highlight instances of $needle in $haystack 2455 * It's faster that the above function and doesn't care about 2456 * HTML or anything. 2457 * 2458 * @param string $needle The string to search for 2459 * @param string $haystack The string to search for $needle in 2460 * @return string 2461 */ 2462 function highlightfast($needle, $haystack) { 2463 2464 if (empty($needle) or empty($haystack)) { 2465 return $haystack; 2466 } 2467 2468 $parts = explode(moodle_strtolower($needle), moodle_strtolower($haystack)); 2469 2470 if (count($parts) === 1) { 2471 return $haystack; 2472 } 2473 2474 $pos = 0; 2475 2476 foreach ($parts as $key => $part) { 2477 $parts[$key] = substr($haystack, $pos, strlen($part)); 2478 $pos += strlen($part); 2479 2480 $parts[$key] .= '<span class="highlight">'.substr($haystack, $pos, strlen($needle)).'</span>'; 2481 $pos += strlen($needle); 2482 } 2483 2484 return str_replace('<span class="highlight"></span>', '', join('', $parts)); 2485 } 2486 2487 /** 2488 * Return a string containing 'lang', xml:lang and optionally 'dir' HTML attributes. 2489 * Internationalisation, for print_header and backup/restorelib. 2490 * @param $dir Default false. 2491 * @return string Attributes. 2492 */ 2493 function get_html_lang($dir = false) { 2494 $direction = ''; 2495 if ($dir) { 2496 if (get_string('thisdirection') == 'rtl') { 2497 $direction = ' dir="rtl"'; 2498 } else { 2499 $direction = ' dir="ltr"'; 2500 } 2501 } 2502 //Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag. 2503 $language = str_replace('_', '-', str_replace('_utf8', '', current_language())); 2504 @header('Content-Language: '.$language); 2505 return ($direction.' lang="'.$language.'" xml:lang="'.$language.'"'); 2506 } 2507 2508 /** 2509 * Return the markup for the destination of the 'Skip to main content' links. 2510 * Accessibility improvement for keyboard-only users. 2511 * Used in course formats, /index.php and /course/index.php 2512 * @return string HTML element. 2513 */ 2514 function skip_main_destination() { 2515 return '<span id="maincontent"></span>'; 2516 } 2517 2518 2519 /// STANDARD WEB PAGE PARTS /////////////////////////////////////////////////// 2520 2521 /** 2522 * Print a standard header 2523 * 2524 * @uses $USER 2525 * @uses $CFG 2526 * @uses $SESSION 2527 * @param string $title Appears at the top of the window 2528 * @param string $heading Appears at the top of the page 2529 * @param array $navigation Array of $navlinks arrays (keys: name, link, type) for use as breadcrumbs links 2530 * @param string $focus Indicates form element to get cursor focus on load eg inputform.password 2531 * @param string $meta Meta tags to be added to the header 2532 * @param boolean $cache Should this page be cacheable? 2533 * @param string $button HTML code for a button (usually for module editing) 2534 * @param string $menu HTML code for a popup menu 2535 * @param boolean $usexml use XML for this page 2536 * @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc) 2537 * @param bool $return If true, return the visible elements of the header instead of echoing them. 2538 */ 2539 function print_header ($title='', $heading='', $navigation='', $focus='', 2540 $meta='', $cache=true, $button=' ', $menu='', 2541 $usexml=false, $bodytags='', $return=false) { 2542 2543 global $USER, $CFG, $THEME, $SESSION, $ME, $SITE, $COURSE; 2544 2545 if (gettype($navigation) == 'string' && strlen($navigation) != 0 && $navigation != 'home') { 2546 debugging("print_header() was sent a string as 3rd ($navigation) parameter. " 2547 . "This is deprecated in favour of an array built by build_navigation(). Please upgrade your code.", DEBUG_DEVELOPER); 2548 } 2549 2550 $heading = format_string($heading); // Fix for MDL-8582 2551 2552 /// This makes sure that the header is never repeated twice on a page 2553 if (defined('HEADER_PRINTED')) { 2554 debugging('print_header() was called more than once - this should not happen. Please check the code for this page closely. Note: error() and redirect() are now safe to call after print_header().'); 2555 return; 2556 } 2557 define('HEADER_PRINTED', 'true'); 2558 2559 /// Perform a browser environment check for the flash version. Should only run once per login session. 2560 if (isloggedin() && !empty($CFG->excludeoldflashclients) && empty($SESSION->flashversion)) { 2561 // Unfortunately we can't use require_js here and keep it all clean in 1.9 ... 2562 // require_js(array('yui_yahoo', 'yui_event', 'yui_connection', $CFG->httpswwwroot."/lib/swfobject/swfobject.js")); 2563 $meta .= '<script type="text/javascript" src="'.$CFG->httpswwwroot.'/lib/yui/yahoo/yahoo-min.js"></script>'; 2564 $meta .= '<script type="text/javascript" src="'.$CFG->httpswwwroot.'/lib/yui/event/event-min.js"></script>'; 2565 $meta .= '<script type="text/javascript" src="'.$CFG->httpswwwroot.'/lib/yui/connection/connection-min.js"></script>'; 2566 $meta .= '<script type="text/javascript" src="'.$CFG->httpswwwroot.'/lib/swfobject/swfobject.js"></script>'; 2567 $meta .= 2568 "<script type=\"text/javascript\">\n". 2569 "//<![CDATA[\n". 2570 " var flashversion = swfobject.getFlashPlayerVersion();\n". 2571 " YAHOO.util.Connect.asyncRequest('GET','".$CFG->httpswwwroot."/login/environment.php?sesskey=".sesskey()."&flashversion='+flashversion.major+'.'+flashversion.minor+'.'+flashversion.release);\n". 2572 "//]]>\n". 2573 "</script>"; 2574 } 2575 2576 2577 /// Add the required stylesheets 2578 $stylesheetshtml = ''; 2579 foreach ($CFG->stylesheets as $stylesheet) { 2580 $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n"; 2581 } 2582 $meta = $stylesheetshtml.$meta; 2583 2584 2585 /// Add the meta page from the themes if any were requested 2586 2587 $metapage = ''; 2588 2589 if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) { 2590 ob_start(); 2591 include_once($CFG->dirroot.'/theme/standard/meta.php'); 2592 $metapage .= ob_get_contents(); 2593 ob_end_clean(); 2594 } 2595 2596 if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) { 2597 if (file_exists($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php')) { 2598 ob_start(); 2599 include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php'); 2600 $metapage .= ob_get_contents(); 2601 ob_end_clean(); 2602 } 2603 } 2604 2605 if (!isset($THEME->metainclude) || $THEME->metainclude) { 2606 if (file_exists($CFG->dirroot.'/theme/'.current_theme().'/meta.php')) { 2607 ob_start(); 2608 include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php'); 2609 $metapage .= ob_get_contents(); 2610 ob_end_clean(); 2611 } 2612 } 2613 2614 $meta = $meta."\n".$metapage; 2615 2616 $meta .= "\n".require_js('',1); 2617 2618 /// Set up some navigation variables 2619 2620 if (is_newnav($navigation)){ 2621 $home = false; 2622 } else { 2623 if ($navigation == 'home') { 2624 $home = true; 2625 $navigation = ''; 2626 } else { 2627 $home = false; 2628 } 2629 } 2630 2631 /// This is another ugly hack to make navigation elements available to print_footer later 2632 $THEME->title = $title; 2633 $THEME->heading = $heading; 2634 $THEME->navigation = $navigation; 2635 $THEME->button = $button; 2636 $THEME->menu = $menu; 2637 $navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : ''; 2638 2639 if ($button == '') { 2640 $button = ' '; 2641 } 2642 2643 if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) { 2644 $button = '<a href="'.$CFG->wwwroot.'/'.$CFG->admin.'/maintenance.php">'.get_string('maintenancemode', 'admin').'</a> '.$button; 2645 if(!empty($title)) { 2646 $title .= ' - '; 2647 } 2648 $title .= get_string('maintenancemode', 'admin'); 2649 } 2650 2651 if (!$menu and $navigation) { 2652 if (empty($CFG->loginhttps)) { 2653 $wwwroot = $CFG->wwwroot; 2654 } else { 2655 2656 $wwwroot = str_replace('http:','https:',$CFG->wwwroot); 2657 } 2658 $menu = user_login_string($COURSE); 2659 } 2660 2661 if (isset($SESSION->justloggedin)) { 2662 unset($SESSION->justloggedin); 2663 if (!empty($CFG->displayloginfailures)) { 2664 if (!empty($USER->username) and $USER->username != 'guest') { 2665 if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) { 2666 $menu .= ' <font size="1">'; 2667 if (empty($count->accounts)) { 2668 $menu .= get_string('failedloginattempts', '', $count); 2669 } else { 2670 $menu .= get_string('failedloginattemptsall', '', $count); 2671 } 2672 if (has_capability('coursereport/log:view', get_context_instance(CONTEXT_SYSTEM))) { 2673 $menu .= ' (<a href="'.$CFG->wwwroot.'/course/report/log/index.php'. 2674 '?chooselog=1&id=1&modid=site_errors">'.get_string('logs').'</a>)'; 2675 } 2676 $menu .= '</font>'; 2677 } 2678 } 2679 } 2680 } 2681 2682 2683 $meta = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . 2684 "\n" . $meta . "\n"; 2685 if (!$usexml) { 2686 @header('Content-Type: text/html; charset=utf-8'); 2687 } 2688 @header('Content-Script-Type: text/javascript'); 2689 @header('Content-Style-Type: text/css'); 2690 2691 //Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag. 2692 $direction = get_html_lang($dir=true); 2693 2694 if ($cache) { // Allow caching on "back" (but not on normal clicks) 2695 @header('Cache-Control: private, pre-check=0, post-check=0, max-age=0'); 2696 @header('Pragma: no-cache'); 2697 @header('Expires: '); 2698 } else { // Do everything we can to always prevent clients and proxies caching 2699 @header('Cache-Control: no-store, no-cache, must-revalidate'); 2700 @header('Cache-Control: post-check=0, pre-check=0', false); 2701 @header('Pragma: no-cache'); 2702 @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT'); 2703 @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); 2704 2705 $meta .= "\n<meta http-equiv=\"pragma\" content=\"no-cache\" />"; 2706 $meta .= "\n<meta http-equiv=\"expires\" content=\"0\" />"; 2707 } 2708 @header('Accept-Ranges: none'); 2709 2710 $currentlanguage = current_language(); 2711 2712 if (empty($usexml)) { 2713 $direction = ' xmlns="http://www.w3.org/1999/xhtml"'. $direction; // See debug_header 2714 } else { 2715 $mathplayer = preg_match("/MathPlayer/i", $_SERVER['HTTP_USER_AGENT']); 2716 if(!$mathplayer) { 2717 header('Content-Type: application/xhtml+xml'); 2718 } 2719 echo '<?xml version="1.0" ?>'."\n"; 2720 if (!empty($CFG->xml_stylesheets)) { 2721 $stylesheets = explode(';', $CFG->xml_stylesheets); 2722 foreach ($stylesheets as $stylesheet) { 2723 echo '<?xml-stylesheet type="text/xsl" href="'. $CFG->wwwroot .'/'. $stylesheet .'" ?>' . "\n"; 2724 } 2725 } 2726 echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1'; 2727 if (!empty($CFG->xml_doctype_extra)) { 2728 echo ' plus '. $CFG->xml_doctype_extra; 2729 } 2730 echo '//' . strtoupper($currentlanguage) . '" "'. $CFG->xml_dtd .'">'."\n"; 2731 $direction = " xmlns=\"http://www.w3.org/1999/xhtml\" 2732 xmlns:math=\"http://www.w3.org/1998/Math/MathML\" 2733 xmlns:xlink=\"http://www.w3.org/1999/xlink\" 2734 $direction"; 2735 if($mathplayer) { 2736 $meta .= '<object id="mathplayer" classid="clsid:32F66A20-7614-11D4-BD11-00104BD3F987">' . "\n"; 2737 $meta .= '<!--comment required to prevent this becoming an empty tag-->'."\n"; 2738 $meta .= '</object>'."\n"; 2739 $meta .= '<?import namespace="math" implementation="#mathplayer" ?>' . "\n"; 2740 } 2741 } 2742 2743 // Clean up the title 2744 2745 $title = format_string($title); // fix for MDL-8582 2746 $title = str_replace('"', '"', $title); 2747 2748 // Create class and id for this page 2749 2750 page_id_and_class($pageid, $pageclass); 2751 2752 $pageclass .= ' course-'.$COURSE->id; 2753 2754 if (!isloggedin()) { 2755 $pageclass .= ' notloggedin'; 2756 } 2757 2758 if (!empty($USER->editing)) { 2759 $pageclass .= ' editing'; 2760 } 2761 2762 if (!empty($CFG->blocksdrag)) { 2763 $pageclass .= ' drag'; 2764 } 2765 2766 $pageclass .= ' dir-'.get_string('thisdirection'); 2767 2768 $pageclass .= ' lang-'.s($currentlanguage); 2769 2770 $bodytags .= ' class="'.s($pageclass).'" id="'.s($pageid).'"'; 2771 2772 ob_start(); 2773 include($CFG->header); 2774 $output = ob_get_contents(); 2775 ob_end_clean(); 2776 2777 // container debugging info 2778 $THEME->open_header_containers = open_containers(); 2779 2780 // Skip to main content, see skip_main_destination(). 2781 if ($pageid=='course-view' or $pageid=='site-index' or $pageid=='course-index') { 2782 $skiplink = '<a class="skip" href="#maincontent">'.get_string('tocontent', 'access').'</a>'; 2783 if (! preg_match('/(.*<div[^>]+id="page"[^>]*>)(.*)/s', $output, $matches)) { 2784 preg_match('/(.*<body.*?>)(.*)/s', $output, $matches); 2785 } 2786 $output = $matches[1]."\n". $skiplink .$matches[2]; 2787 } 2788 2789 $output = force_strict_header($output); 2790 2791 if (!empty($CFG->messaging)) { 2792 $output .= message_popup_window(); 2793 } 2794 2795 // Add in any extra JavaScript libraries that occurred during the header 2796 $output .= require_js('', 2); 2797 2798 if ($return) { 2799 return $output; 2800 } else { 2801 echo $output; 2802 } 2803 } 2804 2805 /** 2806 * Used to include JavaScript libraries. 2807 * 2808 * When the $lib parameter is given, the function will ensure that the 2809 * named library is loaded onto the page - either in the HTML <head>, 2810 * just after the header, or at an arbitrary later point in the page, 2811 * depending on where this function is called. 2812 * 2813 * Libraries will not be included more than once, so this works like 2814 * require_once in PHP. 2815 * 2816 * There are two special-case calls to this function which are both used only 2817 * by weblib print_header: 2818 * $extracthtml = 1: this is used before printing the header. 2819 * It returns the script tag code that should go inside the <head>. 2820 * $extracthtml = 2: this is used after printing the header and handles any 2821 * require_js calls that occurred within the header itself. 2822 * 2823 * @param mixed $lib - string or array of strings 2824 * string(s) should be the shortname for the library or the 2825 * full path to the library file. 2826 * @param int $extracthtml Do not set this parameter usually (leave 0), only 2827 * weblib should set this to 1 or 2 in print_header function. 2828 * @return mixed No return value, except when using $extracthtml it returns the html code. 2829 */ 2830 function require_js($lib,$extracthtml=0) { 2831 global $CFG; 2832 static $loadlibs = array(); 2833 2834 static $state = REQUIREJS_BEFOREHEADER; 2835 static $latecode = ''; 2836 2837 if (!empty($lib)) { 2838 // Add the lib to the list of libs to be loaded, if it isn't already 2839 // in the list. 2840 if (is_array($lib)) { 2841 foreach($lib as $singlelib) { 2842 require_js($singlelib); 2843 } 2844 } else { 2845 $libpath = ajax_get_lib($lib); 2846 if (array_search($libpath, $loadlibs) === false) { 2847 $loadlibs[] = $libpath; 2848 2849 // For state other than 0 we need to take action as well as just 2850 // adding it to loadlibs 2851 if($state != REQUIREJS_BEFOREHEADER) { 2852 // Get the script statement for this library 2853 $scriptstatement=get_require_js_code(array($libpath)); 2854 2855 if($state == REQUIREJS_AFTERHEADER) { 2856 // After the header, print it immediately 2857 print $scriptstatement; 2858 } else { 2859 // Haven't finished the header yet. Add it after the 2860 // header 2861 $latecode .= $scriptstatement; 2862 } 2863 } 2864 } 2865 } 2866 } else if($extracthtml==1) { 2867 if($state !== REQUIREJS_BEFOREHEADER) { 2868 debugging('Incorrect state in require_js (expected BEFOREHEADER): be careful not to call with empty $lib (except in print_header)'); 2869 } else { 2870 $state = REQUIREJS_INHEADER; 2871 } 2872 2873 return get_require_js_code($loadlibs); 2874 } else if($extracthtml==2) { 2875 if($state !== REQUIREJS_INHEADER) { 2876 debugging('Incorrect state in require_js (expected INHEADER): be careful not to call with empty $lib (except in print_header)'); 2877 return ''; 2878 } else { 2879 $state = REQUIREJS_AFTERHEADER; 2880 return $latecode; 2881 } 2882 } else { 2883 debugging('Unexpected value for $extracthtml'); 2884 } 2885 } 2886 2887 /** 2888 * Should not be called directly - use require_js. This function obtains the code 2889 * (script tags) needed to include JavaScript libraries. 2890 * @param array $loadlibs Array of library files to include 2891 * @return string HTML code to include them 2892 */ 2893 function get_require_js_code($loadlibs) { 2894 global $CFG; 2895 // Return the html needed to load the JavaScript files defined in 2896 // our list of libs to be loaded. 2897 $output = ''; 2898 foreach ($loadlibs as $loadlib) { 2899 $output .= '<script type="text/javascript" '; 2900 $output .= " src=\"$loadlib\"></script>\n"; 2901 if ($loadlib == $CFG->wwwroot.'/lib/yui/logger/logger-min.js') { 2902 // Special case, we need the CSS too. 2903 $output .= '<link type="text/css" rel="stylesheet" '; 2904 $output .= " href=\"{$CFG->wwwroot}/lib/yui/logger/assets/logger.css\" />\n"; 2905 } 2906 } 2907 return $output; 2908 } 2909 2910 2911 /** 2912 * Debugging aid: serve page as 'application/xhtml+xml' where possible, 2913 * and substitute the XHTML strict document type. 2914 * Note, requires the 'xmlns' fix in function print_header above. 2915 * See: http://tracker.moodle.org/browse/MDL-7883 2916 * TODO: 2917 */ 2918 function force_strict_header($output) { 2919 global $CFG; 2920 $strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; 2921 $xsl = '/lib/xhtml.xsl'; 2922 2923 if (!headers_sent() && !empty($CFG->xmlstrictheaders)) { // With xml strict headers, the browser will barf 2924 $ctype = 'Content-Type: '; 2925 $prolog= "<?xml version='1.0' encoding='utf-8'?>\n"; 2926 2927 if (isset($_SERVER['HTTP_ACCEPT']) 2928 && false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml')) { 2929 //|| false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') //Safari "Entity 'copy' not defined". 2930 // Firefox et al. 2931 $ctype .= 'application/xhtml+xml'; 2932 $prolog .= "<!--\n DEBUG: $ctype \n-->\n"; 2933 2934 } else if (file_exists($CFG->dirroot.$xsl) 2935 && preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) { 2936 // XSL hack for IE 5+ on Windows. 2937 //$www_xsl = preg_replace('/(http:\/\/.+?\/).*/', '', $CFG->wwwroot) .$xsl; 2938 $www_xsl = $CFG->wwwroot .$xsl; 2939 $ctype .= 'application/xml'; 2940 $prolog .= "<?xml-stylesheet type='text/xsl' href='$www_xsl'?>\n"; 2941 $prolog .= "<!--\n DEBUG: $ctype \n-->\n"; 2942 2943 } else { 2944 //ELSE: Mac/IE, old/non-XML browsers. 2945 $ctype .= 'text/html'; 2946 $prolog = ''; 2947 } 2948 @header($ctype.'; charset=utf-8'); 2949 $output = $prolog . $output; 2950 2951 // Test parser error-handling. 2952 if (isset($_GET['error'])) { 2953 $output .= "__ TEST: XML well-formed error < __\n"; 2954 } 2955 } 2956 2957 $output = preg_replace('/(<!DOCTYPE.+?>)/s', $strict, $output); // Always change the DOCTYPE to Strict 1.0 2958 2959 return $output; 2960 } 2961 2962 2963 2964 /** 2965 * This version of print_header is simpler because the course name does not have to be 2966 * provided explicitly in the strings. It can be used on the site page as in courses 2967 * Eventually all print_header could be replaced by print_header_simple 2968 * 2969 * @param string $title Appears at the top of the window 2970 * @param string $heading Appears at the top of the page 2971 * @param string $navigation Premade navigation string (for use as breadcrumbs links) 2972 * @param string $focus Indicates form element to get cursor focus on load eg inputform.password 2973 * @param string $meta Meta tags to be added to the header 2974 * @param boolean $cache Should this page be cacheable? 2975 * @param string $button HTML code for a button (usually for module editing) 2976 * @param string $menu HTML code for a popup menu 2977 * @param boolean $usexml use XML for this page 2978 * @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc) 2979 * @param bool $return If true, return the visible elements of the header instead of echoing them. 2980 */ 2981 function print_header_simple($title='', $heading='', $navigation='', $focus='', $meta='', 2982 $cache=true, $button=' ', $menu='', $usexml=false, $bodytags='', $return=false) { 2983 2984 global $COURSE, $CFG; 2985 2986 // if we have no navigation specified, build it 2987 if( empty($navigation) ){ 2988 $navigation = build_navigation(''); 2989 } 2990 2991 // If old style nav prepend course short name otherwise leave $navigation object alone 2992 if (!is_newnav($navigation)) { 2993 if ($COURSE->id != SITEID) { 2994 $shortname = '<a href="'.$CFG->wwwroot.'/course/view.php?id='. $COURSE->id .'">'. $COURSE->shortname .'</a> ->'; 2995 $navigation = $shortname.' '.$navigation; 2996 } 2997 } 2998 2999 $output = print_header($COURSE->shortname .': '. $title, $COURSE->fullname .' '. $heading, $navigation, $focus, $meta, 3000 $cache, $button, $menu, $usexml, $bodytags, true); 3001 3002 if ($return) { 3003 return $output; 3004 } else { 3005 echo $output; 3006 } 3007 } 3008 3009 3010 /** 3011 * Can provide a course object to make the footer contain a link to 3012 * to the course home page, otherwise the link will go to the site home 3013 * @uses $USER 3014 * @param mixed $course course object, used for course link button or 3015 * 'none' means no user link, only docs link 3016 * 'empty' means nothing printed in footer 3017 * 'home' special frontpage footer 3018 * @param object $usercourse course used in user link 3019 * @param boolean $return output as string 3020 * @return mixed string or void 3021 */ 3022 function print_footer($course=NULL, $usercourse=NULL, $return=false) { 3023 global $USER, $CFG, $THEME, $COURSE; 3024 3025 if (defined('ADMIN_EXT_HEADER_PRINTED') and !defined('ADMIN_EXT_FOOTER_PRINTED')) { 3026 admin_externalpage_print_footer(); 3027 return; 3028 } 3029 3030 /// Course links or special footer 3031 if ($course) { 3032 if ($course === 'empty') { 3033 // special hack - sometimes we do not want even the docs link in footer 3034 $output = ''; 3035 if (!empty($THEME->open_header_containers)) { 3036 for ($i=0; $i<$THEME->open_header_containers; $i++) { 3037 $output .= print_container_end_all(); // containers opened from header 3038 } 3039 } else { 3040 //1.8 theme compatibility 3041 $output .= "\n</div>"; // content div 3042 } 3043 $output .= "\n</div>\n</body>\n</html>"; // close page div started in header 3044 if ($return) { 3045 return $output; 3046 } else { 3047 echo $output; 3048 return; 3049 } 3050 3051 } else if ($course === 'none') { // Don't print any links etc 3052 $homelink = ''; 3053 $loggedinas = ''; 3054 $home = false; 3055 3056 } else if ($course === 'home') { // special case for site home page - please do not remove 3057 $course = get_site(); 3058 $homelink = '<div class="sitelink">'. 3059 '<a title="Moodle" href="http://moodle.org/">'. 3060 '<img style="width:100px;height:30px" src="pix/moodlelogo.gif" alt="moodlelogo" /></a></div>'; 3061 $home = true; 3062 3063 } else { 3064 $homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot. 3065 '/course/view.php?id='.$course->id.'">'.format_string($course->shortname).'</a></div>'; 3066 $home = false; 3067 } 3068 3069 } else { 3070 $course = get_site(); // Set course as site course by default 3071 $homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot.'/">'.get_string('home').'</a></div>'; 3072 $home = false; 3073 } 3074 3075 /// Set up some other navigation links (passed from print_header by ugly hack) 3076 $menu = isset($THEME->menu) ? str_replace('navmenu', 'navmenufooter', $THEME->menu) : ''; 3077 $title = isset($THEME->title) ? $THEME->title : ''; 3078 $button = isset($THEME->button) ? $THEME->button : ''; 3079 $heading = isset($THEME->heading) ? $THEME->heading : ''; 3080 $navigation = isset($THEME->navigation) ? $THEME->navigation : ''; 3081 $navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : ''; 3082 3083 3084 /// Set the user link if necessary 3085 if (!$usercourse and is_object($course)) { 3086 $usercourse = $course; 3087 } 3088 3089 if (!isset($loggedinas)) { 3090 $loggedinas = user_login_string($usercourse, $USER); 3091 } 3092 3093 if ($loggedinas == $menu) { 3094 $menu = ''; 3095 } 3096 3097 /// there should be exactly the same number of open containers as after the header 3098 if ($THEME->open_header_containers != open_containers()) { 3099 debugging('Unexpected number of open containers: '.open_containers().', expecting '.$THEME->open_header_containers, DEBUG_DEVELOPER); 3100 } 3101 3102 /// Provide some performance info if required 3103 $performanceinfo = ''; 3104 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) { 3105 $perf = get_performance_info(); 3106 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) { 3107 error_log("PERF: " . $perf['txt']); 3108 } 3109 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) { 3110 $performanceinfo = $perf['html']; 3111 } 3112 } 3113 3114 /// Include the actual footer file 3115 3116 ob_start(); 3117 include($CFG->footer); 3118 $output = ob_get_contents(); 3119 ob_end_clean(); 3120 3121 if ($return) { 3122 return $output; 3123 } else { 3124 echo $output; 3125 } 3126 } 3127 3128 /** 3129 * Returns the name of the current theme 3130 * 3131 * @uses $CFG 3132 * @uses $USER 3133 * @uses $SESSION 3134 * @uses $COURSE 3135 * @uses $FULLME 3136 * @return string 3137 */ 3138 function current_theme() { 3139 global $CFG, $USER, $SESSION, $COURSE, $FULLME; 3140 3141 if (empty($CFG->themeorder)) { 3142 $themeorder = array('page', 'course', 'category', 'session', 'user', 'site'); 3143 } else { 3144 $themeorder = $CFG->themeorder; 3145 } 3146 3147 if (isloggedin() and isset($CFG->mnet_localhost_id) and $USER->mnethostid != $CFG->mnet_localhost_id) { 3148 require_once($CFG->dirroot.'/mnet/peer.php'); 3149 $mnet_peer = new mnet_peer(); 3150 $mnet_peer->set_id($USER->mnethostid); 3151 } 3152 3153 $theme = ''; 3154 foreach ($themeorder as $themetype) { 3155 3156 if (!empty($theme)) continue; 3157 3158 switch ($themetype) { 3159 case 'page': // Page theme is for special page-only themes set by code 3160 if (!empty($CFG->pagetheme)) { 3161 $theme = $CFG->pagetheme; 3162 } 3163 break; 3164 case 'course': 3165 if (!empty($CFG->allowcoursethemes) and !empty($COURSE->theme)) { 3166 $theme = $COURSE->theme; 3167 } 3168 break; 3169 case 'category': 3170 if (!empty($CFG->allowcategorythemes)) { 3171 /// Nasty hack to check if we're in a category page 3172 if (stripos($FULLME, 'course/category.php') !== false) { 3173 global $id; 3174 if (!empty($id)) { 3175 $theme = current_category_theme($id); 3176 } 3177 /// Otherwise check if we're in a course that has a category theme set 3178 } else if (!empty($COURSE->category)) { 3179 $theme = current_category_theme($COURSE->category); 3180 } 3181 } 3182 break; 3183 case 'session': 3184 if (!empty($SESSION->theme)) { 3185 $theme = $SESSION->theme; 3186 } 3187 break; 3188 case 'user': 3189 if (!empty($CFG->allowuserthemes) and !empty($USER->theme)) { 3190 if (isloggedin() and $USER->mnethostid != $CFG->mnet_localhost_id && $mnet_peer->force_theme == 1 && $mnet_peer->theme != '') { 3191 $theme = $mnet_peer->theme; 3192 } else { 3193 $theme = $USER->theme; 3194 } 3195 } 3196 break; 3197 case 'site': 3198 if (isloggedin() and isset($CFG->mnet_localhost_id) and $USER->mnethostid != $CFG->mnet_localhost_id && $mnet_peer->force_theme == 1 && $mnet_peer->theme != '') { 3199 $theme = $mnet_peer->theme; 3200 } else { 3201 $theme = $CFG->theme; 3202 } 3203 break; 3204 default: 3205 /// do nothing 3206 } 3207 } 3208 3209 /// A final check in case 'site' was not included in $CFG->themeorder 3210 if (empty($theme)) { 3211 $theme = $CFG->theme; 3212 } 3213 3214 return $theme; 3215 } 3216 3217 /** 3218 * Retrieves the category theme if one exists, otherwise checks the parent categories. 3219 * Recursive function. 3220 * 3221 * @uses $COURSE 3222 * @param integer $categoryid id of the category to check 3223 * @return string theme name 3224 */ 3225 function current_category_theme($categoryid=0) { 3226 global $COURSE; 3227 3228 /// Use the COURSE global if the categoryid not set 3229 if (empty($categoryid)) { 3230 if (!empty($COURSE->category)) { 3231 $categoryid = $COURSE->category; 3232 } else { 3233 return false; 3234 } 3235 } 3236 3237 /// Retrieve the current category 3238 if ($category = get_record('course_categories', 'id', $categoryid)) { 3239 3240 /// Return the category theme if it exists 3241 if (!empty($category->theme)) { 3242 return $category->theme; 3243 3244 /// Otherwise try the parent category if one exists 3245 } else if (!empty($category->parent)) { 3246 return current_category_theme($category->parent); 3247 } 3248 3249 /// Return false if we can't find the category record 3250 } else { 3251 return false; 3252 } 3253 } 3254 3255 /** 3256 * This function is called by stylesheets to set up the header 3257 * approriately as well as the current path 3258 * 3259 * @uses $CFG 3260 * @param int $lastmodified ? 3261 * @param int $lifetime ? 3262 * @param string $thename ? 3263 */ 3264 function style_sheet_setup($lastmodified=0, $lifetime=300, $themename='', $forceconfig='', $lang='') { 3265 3266 global $CFG, $THEME; 3267 3268 // Fix for IE6 caching - we don't want the filemtime('styles.php'), instead use now. 3269 $lastmodified = time(); 3270 3271 header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastmodified) . ' GMT'); 3272 header('Expires: ' . gmdate("D, d M Y H:i:s", time() + $lifetime) . ' GMT'); 3273 header('Cache-Control: max-age='. $lifetime); 3274 header('Pragma: '); 3275 header('Content-type: text/css'); // Correct MIME type 3276 3277 $DEFAULT_SHEET_LIST = array('styles_layout', 'styles_fonts', 'styles_color'); 3278 3279 if (empty($themename)) { 3280 $themename = current_theme(); // So we have something. Normally not needed. 3281 } else { 3282 $themename = clean_param($themename, PARAM_SAFEDIR); 3283 } 3284 3285 if (!empty($forceconfig)) { // Page wants to use the config from this theme instead 3286 unset($THEME); 3287 include($CFG->themedir.'/'.$forceconfig.'/'.'config.php'); 3288 } 3289 3290 /// If this is the standard theme calling us, then find out what sheets we need 3291 3292 if ($themename == 'standard') { 3293 if (!isset($THEME->standardsheets) or $THEME->standardsheets === true) { // Use all the sheets we have 3294 $THEME->sheets = $DEFAULT_SHEET_LIST; 3295 } else if (empty($THEME->standardsheets)) { // We can stop right now! 3296 echo "/***** Nothing required from this stylesheet by main theme *****/\n\n"; 3297 exit; 3298 } else { // Use the provided subset only 3299 $THEME->sheets = $THEME->standardsheets; 3300 } 3301 3302 /// If we are a parent theme, then check for parent definitions 3303 3304 } else if (!empty($THEME->parent) && $themename == $THEME->parent) { 3305 if (!isset($THEME->parentsheets) or $THEME->parentsheets === true) { // Use all the sheets we have 3306 $THEME->sheets = $DEFAULT_SHEET_LIST; 3307 } else if (empty($THEME->parentsheets)) { // We can stop right now! 3308 echo "/***** Nothing required from this stylesheet by main theme *****/\n\n"; 3309 exit; 3310 } else { // Use the provided subset only 3311 $THEME->sheets = $THEME->parentsheets; 3312 } 3313 } 3314 3315 /// Work out the last modified date for this theme 3316 3317 foreach ($THEME->sheets as $sheet) { 3318 if (file_exists($CFG->themedir.'/'.$themename.'/'.$sheet.'.css')) { 3319 $sheetmodified = filemtime($CFG->themedir.'/'.$themename.'/'.$sheet.'.css'); 3320 if ($sheetmodified > $lastmodified) { 3321 $lastmodified = $sheetmodified; 3322 } 3323 } 3324 } 3325 3326 3327 /// Get a list of all the files we want to include 3328 $files = array(); 3329 3330 foreach ($THEME->sheets as $sheet) { 3331 $files[] = array($CFG->themedir, $themename.'/'.$sheet.'.css'); 3332 } 3333 3334 if ($themename == 'standard') { // Add any standard styles included in any modules 3335 if (!empty($THEME->modsheets)) { // Search for styles.php within activity modules 3336 if ($mods = get_list_of_plugins('mod')) { 3337 foreach ($mods as $mod) { 3338 if (file_exists($CFG->dirroot.'/mod/'.$mod.'/styles.php')) { 3339 $files[] = array($CFG->dirroot, '/mod/'.$mod.'/styles.php'); 3340 } 3341 } 3342 } 3343 } 3344 3345 if (!empty($THEME->blocksheets)) { // Search for styles.php within block modules 3346 if ($mods = get_list_of_plugins('blocks')) { 3347 foreach ($mods as $mod) { 3348 if (file_exists($CFG->dirroot.'/blocks/'.$mod.'/styles.php')) { 3349 $files[] = array($CFG->dirroot, '/blocks/'.$mod.'/styles.php'); 3350 } 3351 } 3352 } 3353 } 3354 3355 if (!isset($THEME->courseformatsheets) || $THEME->courseformatsheets) { // Search for styles.php in course formats 3356 if ($mods = get_list_of_plugins('format','',$CFG->dirroot.'/course')) { 3357 foreach ($mods as $mod) { 3358 if (file_exists($CFG->dirroot.'/course/format/'.$mod.'/styles.php')) { 3359 $files[] = array($CFG->dirroot, '/course/format/'.$mod.'/styles.php'); 3360 } 3361 } 3362 } 3363 } 3364 3365 if (!isset($THEME->gradereportsheets) || $THEME->gradereportsheets) { // Search for styles.php in grade reports 3366 if ($reports = get_list_of_plugins('grade/report')) { 3367 foreach ($reports as $report) { 3368 if (file_exists($CFG->dirroot.'/grade/report/'.$report.'/styles.php')) { 3369 $files[] = array($CFG->dirroot, '/grade/report/'.$report.'/styles.php'); 3370 } 3371 } 3372 } 3373 } 3374 3375 if (!empty($THEME->langsheets)) { // Search for styles.php within the current language 3376 if (file_exists($CFG->dirroot.'/lang/'.$lang.'/styles.php')) { 3377 $files[] = array($CFG->dirroot, '/lang/'.$lang.'/styles.php'); 3378 } 3379 } 3380 } 3381 3382 if ($files) { 3383 /// Produce a list of all the files first 3384 echo '/**************************************'."\n"; 3385 echo ' * THEME NAME: '.$themename."\n *\n"; 3386 echo ' * Files included in this sheet:'."\n *\n"; 3387 foreach ($files as $file) { 3388 echo ' * '.$file[1]."\n"; 3389 } 3390 echo ' **************************************/'."\n\n"; 3391 3392 3393 /// check if csscobstants is set 3394 if (!empty($THEME->cssconstants)) { 3395 require_once("$CFG->libdir/cssconstants.php"); 3396 /// Actually collect all the files in order. 3397 $css = ''; 3398 foreach ($files as $file) { 3399 $css .= '/***** '.$file[1].' start *****/'."\n\n"; 3400 $css .= file_get_contents($file[0].'/'.$file[1]); 3401 $ccs .= '/***** '.$file[1].' end *****/'."\n\n"; 3402 } 3403 /// replace css_constants with their values 3404 echo replace_cssconstants($css); 3405 } else { 3406 /// Actually output all the files in order. 3407 if (empty($CFG->CSSEdit) && empty($THEME->CSSEdit)) { 3408 foreach ($files as $file) { 3409 echo '/***** '.$file[1].' start *****/'."\n\n"; 3410 @include_once($file[0].'/'.$file[1]); 3411 echo '/***** '.$file[1].' end *****/'."\n\n"; 3412 } 3413 } else { 3414 foreach ($files as $file) { 3415 echo '/* @group '.$file[1].' */'."\n\n"; 3416 if (strstr($file[1], '.css') !== FALSE) { 3417 echo '@import url("'.$CFG->themewww.'/'.$file[1].'");'."\n\n"; 3418 } else { 3419 @include_once($file[0].'/'.$file[1]); 3420 } 3421 echo '/* @end */'."\n\n"; 3422 } 3423 } 3424 } 3425 } 3426 3427 return $CFG->themewww.'/'.$themename; // Only to help old themes (1.4 and earlier) 3428 } 3429 3430 3431 function theme_setup($theme = '', $params=NULL) { 3432 /// Sets up global variables related to themes 3433 3434 global $CFG, $THEME, $SESSION, $USER, $HTTPSPAGEREQUIRED; 3435 3436 /// Do not mess with THEME if header already printed - this would break all the extra stuff in global $THEME from print_header()!! 3437 if (defined('HEADER_PRINTED')) { 3438 return; 3439 } 3440 3441 if (empty($theme)) { 3442 $theme = current_theme(); 3443 } 3444 3445 /// If the theme doesn't exist for some reason then revert to standardwhite 3446 if (!file_exists($CFG->themedir .'/'. $theme .'/config.php')) { 3447 $CFG->theme = $theme = 'standardwhite'; 3448 } 3449 3450 /// Load up the theme config 3451 $THEME = NULL; // Just to be sure 3452 include($CFG->themedir .'/'. $theme .'/config.php'); // Main config for current theme 3453 3454 /// Put together the parameters 3455 if (!$params) { 3456 $params = array(); 3457 } 3458 3459 if ($theme != $CFG->theme) { 3460 $params[] = 'forceconfig='.$theme; 3461 } 3462 3463 /// Force language too if required 3464 if (!empty($THEME->langsheets)) { 3465 $params[] = 'lang='.current_language(); 3466 } 3467 3468 3469 /// Convert params to string 3470 if ($params) { 3471 $paramstring = '?'.implode('&', $params); 3472 } else { 3473 $paramstring = ''; 3474 } 3475 3476 /// Set up image paths 3477 if(isset($CFG->smartpix) && $CFG->smartpix==1) { 3478 if($CFG->slasharguments) { // Use this method if possible for better caching 3479 $extra=''; 3480 } else { 3481 $extra='?file='; 3482 } 3483 3484 $CFG->pixpath = $CFG->wwwroot. '/pix/smartpix.php'.$extra.'/'.$theme; 3485 $CFG->modpixpath = $CFG->wwwroot .'/pix/smartpix.php'.$extra.'/'.$theme.'/mod'; 3486 } else if (empty($THEME->custompix)) { // Could be set in the above file 3487 $CFG->pixpath = $CFG->wwwroot .'/pix'; 3488 $CFG->modpixpath = $CFG->wwwroot .'/mod'; 3489 } else { 3490 $CFG->pixpath = $CFG->themewww .'/'. $theme .'/pix'; 3491 $CFG->modpixpath = $CFG->themewww .'/'. $theme .'/pix/mod'; 3492 } 3493 3494 /// Header and footer paths 3495 $CFG->header = $CFG->themedir .'/'. $theme .'/header.html'; 3496 $CFG->footer = $CFG->themedir .'/'. $theme .'/footer.html'; 3497 3498 /// Define stylesheet loading order 3499 $CFG->stylesheets = array(); 3500 if ($theme != 'standard') { /// The standard sheet is always loaded first 3501 $CFG->stylesheets[] = $CFG->themewww.'/standard/styles.php'.$paramstring; 3502 } 3503 if (!empty($THEME->parent)) { /// Parent stylesheets are loaded next 3504 $CFG->stylesheets[] = $CFG->themewww.'/'.$THEME->parent.'/styles.php'.$paramstring; 3505 } 3506 $CFG->stylesheets[] = $CFG->themewww.'/'.$theme.'/styles.php'.$paramstring; 3507 3508 /// We have to change some URLs in styles if we are in a $HTTPSPAGEREQUIRED page 3509 if (!empty($HTTPSPAGEREQUIRED)) { 3510 $CFG->themewww = str_replace('http:', 'https:', $CFG->themewww); 3511 $CFG->pixpath = str_replace('http:', 'https:', $CFG->pixpath); 3512 $CFG->modpixpath = str_replace('http:', 'https:', $CFG->modpixpath); 3513 foreach ($CFG->stylesheets as $key => $stylesheet) { 3514 $CFG->stylesheets[$key] = str_replace('http:', 'https:', $stylesheet); 3515 } 3516 } 3517 3518 // RTL support - only for RTL languages, add RTL CSS 3519 if (get_string('thisdirection') == 'rtl') { 3520 $CFG->stylesheets[] = $CFG->themewww.'/standard/rtl.css'.$paramstring; 3521 $CFG->stylesheets[] = $CFG->themewww.'/'.$theme.'/rtl.css'.$paramstring; 3522 } 3523 } 3524 3525 3526 /** 3527 * Returns text to be displayed to the user which reflects their login status 3528 * 3529 * @uses $CFG 3530 * @uses $USER 3531 * @param course $course {@link $COURSE} object containing course information 3532 * @param user $user {@link $USER} object containing user information 3533 * @return string 3534 */ 3535 function user_login_string($course=NULL, $user=NULL) { 3536 global $USER, $CFG, $SITE; 3537 3538 if (empty($user) and !empty($USER->id)) { 3539 $user = $USER; 3540 } 3541 3542 if (empty($course)) { 3543 $course = $SITE; 3544 } 3545 3546 if (!empty($user->realuser)) { 3547 if ($realuser = get_record('user', 'id', $user->realuser)) { 3548 $fullname = fullname($realuser, true); 3549 $realuserinfo = " [<a $CFG->frametarget 3550 href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&return=1&sesskey=".sesskey()."\">$fullname</a>] "; 3551 } 3552 } else { 3553 $realuserinfo = ''; 3554 } 3555 3556 if (empty($CFG->loginhttps)) { 3557 $wwwroot = $CFG->wwwroot; 3558 } else { 3559 $wwwroot = str_replace('http:','https:',$CFG->wwwroot); 3560 } 3561 3562 if (empty($course->id)) { 3563 // $course->id is not defined during installation 3564 return ''; 3565 } else if (!empty($user->id)) { 3566 $context = get_context_instance(CONTEXT_COURSE, $course->id); 3567 3568 $fullname = fullname($user, true); 3569 $username = "<a $CFG->frametarget href=\"$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id\">$fullname</a>"; 3570 if (is_mnet_remote_user($user) and $idprovider = get_record('mnet_host', 'id', $user->mnethostid)) { 3571 $username .= " from <a $CFG->frametarget href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>"; 3572 } 3573 if (isset($user->username) && $user->username == 'guest') { 3574 $loggedinas = $realuserinfo.get_string('loggedinasguest'). 3575 " (<a $CFG->frametarget href=\"$wwwroot/login/index.php\">".get_string('login').'</a>)'; 3576 } else if (!empty($user->access['rsw'][$context->path])) { 3577 $rolename = ''; 3578 if ($role = get_record('role', 'id', $user->access['rsw'][$context->path])) { 3579 $rolename = join("", role_fix_names(array($role->id=>$role->name), $context)); 3580 $rolename = ': '.format_string($rolename); 3581 } 3582 $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename. 3583 " (<a $CFG->frametarget 3584 href=\"$CFG->wwwroot/course/view.php?id=$course->id&switchrole=0&sesskey=".sesskey()."\">".get_string('switchrolereturn').'</a>)'; 3585 } else { 3586 $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username).' '. 3587 " (<a $CFG->frametarget href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)'; 3588 } 3589 } else { 3590 $loggedinas = get_string('loggedinnot', 'moodle'). 3591 " (<a $CFG->frametarget href=\"$wwwroot/login/index.php\">".get_string('login').'</a>)'; 3592 } 3593 return '<div class="logininfo">'.$loggedinas.'</div>'; 3594 } 3595 3596 /** 3597 * Tests whether $THEME->rarrow, $THEME->larrow have been set (theme/-/config.php). 3598 * If not it applies sensible defaults. 3599 * 3600 * Accessibility: right and left arrow Unicode characters for breadcrumb, calendar, 3601 * search forum block, etc. Important: these are 'silent' in a screen-reader 3602 * (unlike > »), and must be accompanied by text. 3603 * @uses $THEME 3604 */ 3605 function check_theme_arrows() { 3606 global $THEME; 3607 3608 if (!isset($THEME->rarrow) and !isset($THEME->larrow)) { 3609 // Default, looks good in Win XP/IE 6, Win/Firefox 1.5, Win/Netscape 8... 3610 // Also OK in Win 9x/2K/IE 5.x 3611 $THEME->rarrow = '►'; 3612 $THEME->larrow = '◄'; 3613 if (empty($_SERVER['HTTP_USER_AGENT'])) { 3614 $uagent = ''; 3615 } else { 3616 $uagent = $_SERVER['HTTP_USER_AGENT']; 3617 } 3618 if (false !== strpos($uagent, 'Opera') 3619 || false !== strpos($uagent, 'Mac')) { 3620 // Looks good in Win XP/Mac/Opera 8/9, Mac/Firefox 2, Camino, Safari. 3621 // Not broken in Mac/IE 5, Mac/Netscape 7 (?). 3622 $THEME->rarrow = '▶'; 3623 $THEME->larrow = '◀'; 3624 } 3625 elseif (false !== strpos($uagent, 'Konqueror')) { 3626 $THEME->rarrow = '→'; 3627 $THEME->larrow = '←'; 3628 } 3629 elseif (isset($_SERVER['HTTP_ACCEPT_CHARSET']) 3630 && false === stripos($_SERVER['HTTP_ACCEPT_CHARSET'], 'utf-8')) { 3631 // (Win/IE 5 doesn't set ACCEPT_CHARSET, but handles Unicode.) 3632 // To be safe, non-Unicode browsers! 3633 $THEME->rarrow = '>'; 3634 $THEME->larrow = '<'; 3635 } 3636 3637 /// RTL support - in RTL languages, swap r and l arrows 3638 if (right_to_left()) { 3639 $t = $THEME->rarrow; 3640 $THEME->rarrow = $THEME->larrow; 3641 $THEME->larrow = $t; 3642 } 3643 } 3644 } 3645 3646 3647 /** 3648 * Return the right arrow with text ('next'), and optionally embedded in a link. 3649 * See function above, check_theme_arrows. 3650 * @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases). 3651 * @param string $url An optional link to use in a surrounding HTML anchor. 3652 * @param bool $accesshide True if text should be hidden (for screen readers only). 3653 * @param string $addclass Additional class names for the link, or the arrow character. 3654 * @return string HTML string. 3655 */ 3656 function link_arrow_right($text, $url='', $accesshide=false, $addclass='') { 3657 global $THEME; 3658 check_theme_arrows(); 3659 $arrowclass = 'arrow '; 3660 if (! $url) { 3661 $arrowclass .= $addclass; 3662 } 3663 $arrow = '<span class="'.$arrowclass.'">'.$THEME->rarrow.'</span>'; 3664 $htmltext = ''; 3665 if ($text) { 3666 $htmltext = $text.' '; 3667 if ($accesshide) { 3668 $htmltext = get_accesshide($htmltext); 3669 } 3670 } 3671 if ($url) { 3672 $class = ''; 3673 if ($addclass) { 3674 $class =" class=\"$addclass\""; 3675 } 3676 return '<a'.$class.' href="'.$url.'" title="'.preg_replace('/<.*?>/','',$text).'">'.$htmltext.$arrow.'</a>'; 3677 } 3678 return $htmltext.$arrow; 3679 } 3680 3681 /** 3682 * Return the left arrow with text ('previous'), and optionally embedded in a link. 3683 * See function above, check_theme_arrows. 3684 * @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases). 3685 * @param string $url An optional link to use in a surrounding HTML anchor. 3686 * @param bool $accesshide True if text should be hidden (for screen readers only). 3687 * @param string $addclass Additional class names for the link, or the arrow character. 3688 * @return string HTML string. 3689 */ 3690 function link_arrow_left($text, $url='', $accesshide=false, $addclass='') { 3691 global $THEME; 3692 check_theme_arrows(); 3693 $arrowclass = 'arrow '; 3694 if (! $url) { 3695 $arrowclass .= $addclass; 3696 } 3697 $arrow = '<span class="'.$arrowclass.'">'.$THEME->larrow.'</span>'; 3698 $htmltext = ''; 3699 if ($text) { 3700 $htmltext = ' '.$text; 3701 if ($accesshide) { 3702 $htmltext = get_accesshide($htmltext); 3703 } 3704 } 3705 if ($url) { 3706 $class = ''; 3707 if ($addclass) { 3708 $class =" class=\"$addclass\""; 3709 } 3710 return '<a'.$class.' href="'.$url.'" title="'.preg_replace('/<.*?>/','',$text).'">'.$arrow.$htmltext.'</a>'; 3711 } 3712 return $arrow.$htmltext; 3713 } 3714 3715 /** 3716 * Return a HTML element with the class "accesshide", for accessibility. 3717 * Please use cautiously - where possible, text should be visible! 3718 * @param string $text Plain text. 3719 * @param string $elem Lowercase element name, default "span". 3720 * @param string $class Additional classes for the element. 3721 * @param string $attrs Additional attributes string in the form, "name='value' name2='value2'" 3722 * @return string HTML string. 3723 */ 3724 function get_accesshide($text, $elem='span', $class='', $attrs='') { 3725 return "<$elem class=\"accesshide $class\" $attrs>$text</$elem>"; 3726 } 3727 3728 /** 3729 * Return the breadcrumb trail navigation separator. 3730 * @return string HTML string. 3731 */ 3732 function get_separator() { 3733 //Accessibility: the 'hidden' slash is preferred for screen readers. 3734 return ' '.link_arrow_right($text='/', $url='', $accesshide=true, 'sep').' '; 3735 } 3736 3737 /** 3738 * Prints breadcrumb trail of links, called in theme/-/header.html 3739 * 3740 * @uses $CFG 3741 * @param mixed $navigation The breadcrumb navigation string to be printed 3742 * @param string $separator OBSOLETE, mostly not used any more. See build_navigation instead. 3743 * @param boolean $return False to echo the breadcrumb string (default), true to return it. 3744 * @return string or null, depending on $return. 3745 */ 3746 function print_navigation ($navigation, $separator=0, $return=false) { 3747 global $CFG, $THEME; 3748 $output = ''; 3749 3750 if (0 === $separator) { 3751 $separator = get_separator(); 3752 } 3753 else { 3754 $separator = '<span class="sep">'. $separator .'</span>'; 3755 } 3756 3757 if ($navigation) { 3758 3759 if (is_newnav($navigation)) { 3760 if ($return) { 3761 return($navigation['navlinks']); 3762 } else { 3763 echo $navigation['navlinks']; 3764 return; 3765 } 3766 } else { 3767 debugging('Navigation needs to be updated to use build_navigation()', DEBUG_DEVELOPER); 3768 } 3769 3770 if (!is_array($navigation)) { 3771 $ar = explode('->', $navigation); 3772 $navigation = array(); 3773 3774 foreach ($ar as $a) { 3775 if (strpos($a, '</a>') === false) { 3776 $navigation[] = array('title' => $a, 'url' => ''); 3777 } else { 3778 if (preg_match('/<a.*href="([^"]*)">(.*)<\/a>/', $a, $matches)) { 3779 $navigation[] = array('title' => $matches[2], 'url' => $matches[1]); 3780 } 3781 } 3782 } 3783 } 3784 3785 if (! $site = get_site()) { 3786 $site = new object(); 3787 $site->shortname = get_string('home'); 3788 } 3789 3790 //Accessibility: breadcrumb links now in a list, » replaced with a 'silent' character. 3791 $output .= get_accesshide(get_string('youarehere','access'), 'h2')."<ul>\n"; 3792 3793 $output .= '<li class="first">'."\n".'<a '.$CFG->frametarget.' onclick="this.target=\''.$CFG->framename.'\'" href="' 3794 .$CFG->wwwroot.((!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)) 3795 && !empty($USER->id) && !empty($CFG->mymoodleredirect) && !isguest()) 3796 ? '/my' : '') .'/">'. format_string($site->shortname) ."</a>\n</li>\n"; 3797 3798 3799 foreach ($navigation as $navitem) { 3800 $title = trim(strip_tags(format_string($navitem['title'], false))); 3801 $url = $navitem['url']; 3802 3803 if (empty($url)) { 3804 $output .= '<li>'."$separator $title</li>\n"; 3805 } else { 3806 $output .= '<li>'."$separator\n<a ".$CFG->frametarget.' onclick="this.target=\''.$CFG->framename.'\'" href="' 3807 .$url.'">'."$title</a>\n</li>\n"; 3808 } 3809 } 3810 3811 $output .= "</ul>\n"; 3812 } 3813 3814 if ($return) { 3815 return $output; 3816 } else { 3817 echo $output; 3818 } 3819 } 3820 3821 /** 3822 * This function will build the navigation string to be used by print_header 3823 * and others. 3824 * 3825 * It automatically generates the site and course level (if appropriate) links. 3826 * 3827 * If you pass in a $cm object, the method will also generate the activity (e.g. 'Forums') 3828 * and activityinstances (e.g. 'General Developer Forum') navigation levels. 3829 * 3830 * If you want to add any further navigation links after the ones this function generates, 3831 * the pass an array of extra link arrays like this: 3832 * array( 3833 * array('name' => $linktext1, 'link' => $url1, 'type' => $linktype1), 3834 * array('name' => $linktext2, 'link' => $url2, 'type' => $linktype2) 3835 * ) 3836 * The normal case is to just add one further link, for example 'Editing forum' after 3837 * 'General Developer Forum', with no link. 3838 * To do that, you need to pass 3839 * array(array('name' => $linktext, 'link' => '', 'type' => 'title')) 3840 * However, becuase this is a very common case, you can use a shortcut syntax, and just 3841 * pass the string 'Editing forum', instead of an array as $extranavlinks. 3842 * 3843 * At the moment, the link types only have limited significance. Type 'activity' is 3844 * recognised in order to implement the $CFG->hideactivitytypenavlink feature. Types 3845 * that are known to appear are 'home', 'course', 'activity', 'activityinstance' and 'title'. 3846 * This really needs to be documented better. In the mean time, try to be consistent, it will 3847 * enable people to customise the navigation more in future. 3848 * 3849 * When passing a $cm object, the fields used are $cm->modname, $cm->name and $cm->course. 3850 * If you get the $cm object using the function get_coursemodule_from_instance or 3851 * get_coursemodule_from_id (as recommended) then this will be done for you automatically. 3852 * If you don't have $cm->modname or $cm->name, this fuction will attempt to find them using 3853 * the $cm->module and $cm->instance fields, but this takes extra database queries, so a 3854 * warning is printed in developer debug mode. 3855 * 3856 * @uses $CFG 3857 * @uses $THEME 3858 * 3859 * @param mixed $extranavlinks - Normally an array of arrays, keys: name, link, type. If you 3860 * only want one extra item with no link, you can pass a string instead. If you don't want 3861 * any extra links, pass an empty string. 3862 * @param mixed $cm - optionally the $cm object, if you want this function to generate the 3863 * activity and activityinstance levels of navigation too. 3864 * 3865 * @return $navigation as an object so it can be differentiated from old style 3866 * navigation strings. 3867 */ 3868 function build_navigation($extranavlinks, $cm = null) { 3869 global $CFG, $COURSE; 3870 3871 if (is_string($extranavlinks)) { 3872 if ($extranavlinks == '') { 3873 $extranavlinks = array(); 3874 } else { 3875 $extranavlinks = array(array('name' => $extranavlinks, 'link' => '', 'type' => 'title')); 3876 } 3877 } 3878 3879 $navlinks = array(); 3880 3881 //Site name 3882 if ($site = get_site()) { 3883 $navlinks[] = array( 3884 'name' => format_string($site->shortname), 3885 'link' => "$CFG->wwwroot/", 3886 'type' => 'home'); 3887 } 3888 3889 // Course name, if appropriate. 3890 if (isset($COURSE) && $COURSE->id != SITEID) { 3891 $navlinks[] = array( 3892 'name' => format_string($COURSE->shortname), 3893 'link' => "$CFG->wwwroot/course/view.php?id=$COURSE->id", 3894 'type' => 'course'); 3895 } 3896 3897 // Activity type and instance, if appropriate. 3898 if (is_object($cm)) { 3899 if (!isset($cm->modname)) { 3900 debugging('The field $cm->modname should be set if you call build_navigation with '. 3901 'a $cm parameter. If you get $cm using get_coursemodule_from_instance or '. 3902 'get_coursemodule_from_id, this will be done automatically.', DEBUG_DEVELOPER); 3903 if (!$cm->modname = get_field('modules', 'name', 'id', $cm->module)) { 3904 error('Cannot get the module type in build navigation.'); 3905 } 3906 } 3907 if (!isset($cm->name)) { 3908 debugging('The field $cm->name should be set if you call build_navigation with '. 3909 'a $cm parameter. If you get $cm using get_coursemodule_from_instance or '. 3910 'get_coursemodule_from_id, this will be done automatically.', DEBUG_DEVELOPER); 3911 if (!$cm->name = get_field($cm->modname, 'name', 'id', $cm->instance)) { 3912 error('Cannot get the module name in build navigation.'); 3913 } 3914 } 3915 $navlinks[] = array( 3916 'name' => get_string('modulenameplural', $cm->modname), 3917 'link' => $CFG->wwwroot . '/mod/' . $cm->modname . '/index.php?id=' . $cm->course, 3918 'type' => 'activity'); 3919 $navlinks[] = array( 3920 'name' => format_string($cm->name), 3921 'link' => $CFG->wwwroot . '/mod/' . $cm->modname . '/view.php?id=' . $cm->id, 3922 'type' => 'activityinstance'); 3923 } 3924 3925 //Merge in extra navigation links 3926 $navlinks = array_merge($navlinks, $extranavlinks); 3927 3928 // Work out whether we should be showing the activity (e.g. Forums) link. 3929 // Note: build_navigation() is called from many places -- 3930 // install & upgrade for example -- where we cannot count on the 3931 // roles infrastructure to be defined. Hence the $CFG->rolesactive check. 3932 if (!isset($CFG->hideactivitytypenavlink)) { 3933 $CFG->hideactivitytypenavlink = 0; 3934 } 3935 if ($CFG->hideactivitytypenavlink == 2) { 3936 $hideactivitylink = true; 3937 } else if ($CFG->hideactivitytypenavlink == 1 && $CFG->rolesactive && 3938 !empty($COURSE->id) && $COURSE->id != SITEID) { 3939 if (!isset($COURSE->context)) { 3940 $COURSE->context = get_context_instance(CONTEXT_COURSE, $COURSE->id); 3941 } 3942 $hideactivitylink = !has_capability('moodle/course:manageactivities', $COURSE->context); 3943 } else { 3944 $hideactivitylink = false; 3945 } 3946 3947 //Construct an unordered list from $navlinks 3948 //Accessibility: heading hidden from visual browsers by default. 3949 $navigation = get_accesshide(get_string('youarehere','access'), 'h2')." <ul>\n"; 3950 $lastindex = count($navlinks) - 1; 3951 $i = -1; // Used to count the times, so we know when we get to the last item. 3952 $first = true; 3953 foreach ($navlinks as $navlink) { 3954 $i++; 3955 $last = ($i == $lastindex); 3956 if (!is_array($navlink)) { 3957 continue; 3958 } 3959 if (!empty($navlink['type']) && $navlink['type'] == 'activity' && !$last && $hideactivitylink) { 3960 continue; 3961 } 3962 if ($first) { 3963 $navigation .= '<li class="first">'; 3964 } else { 3965 $navigation .= '<li>'; 3966 } 3967 if (!$first) { 3968 $navigation .= get_separator(); 3969 } 3970 if ((!empty($navlink['link'])) && !$last) { 3971 $navigation .= "<a $CFG->frametarget onclick=\"this.target='$CFG->framename'\" href=\"{$navlink['link']}\">"; 3972 } 3973 $navigation .= "{$navlink['name']}"; 3974 if ((!empty($navlink['link'])) && !$last) { 3975 $navigation .= "</a>"; 3976 } 3977 3978 $navigation .= "</li>"; 3979 $first = false; 3980 } 3981 $navigation .= "</ul>"; 3982 3983 return(array('newnav' => true, 'navlinks' => $navigation)); 3984 } 3985 3986 3987 /** 3988 * Prints a string in a specified size (retained for backward compatibility) 3989 * 3990 * @param string $text The text to be displayed 3991 * @param int $size The size to set the font for text display. 3992 */ 3993 function print_headline($text, $size=2, $return=false) { 3994 $output = print_heading($text, '', $size, true); 3995 if ($return) { 3996 return $output; 3997 } else { 3998 echo $output; 3999 } 4000 } 4001 4002 /** 4003 * Prints text in a format for use in headings. 4004 * 4005 * @param string $text The text to be displayed 4006 * @param string $align The alignment of the printed paragraph of text 4007 * @param int $size The size to set the font for text display. 4008 */ 4009 function print_heading($text, $align='', $size=2, $class='main', $return=false) { 4010 if ($align) { 4011 $align = ' style="text-align:'.$align.';"'; 4012 } 4013 if ($class) { 4014 $class = ' class="'.$class.'"'; 4015 } 4016 $output = "<h$size $align $class>".stripslashes_safe($text)."</h$size>"; 4017 4018 if ($return) { 4019 return $output; 4020 } else { 4021 echo $output; 4022 } 4023 } 4024 4025 /** 4026 * Centered heading with attached help button (same title text) 4027 * and optional icon attached 4028 * 4029 * @param string $text The text to be displayed 4030 * @param string $helppage The help page to link to 4031 * @param string $module The module whose help should be linked to 4032 * @param string $icon Image to display if needed 4033 */ 4034 function print_heading_with_help($text, $helppage, $module='moodle', $icon='', $return=false) { 4035 $output = ''; 4036 $output .= '<h2 class="main help">'.$icon.stripslashes_safe($text); 4037 $output .= helpbutton($helppage, $text, $module, true, false, '', true); 4038 $output .= '</h2>'; 4039 4040 if ($return) { 4041 return $output; 4042 } else { 4043 echo $output; 4044 } 4045 } 4046 4047 4048 function print_heading_block($heading, $class='', $return=false) { 4049 //Accessibility: 'headingblock' is now H1, see theme/standard/styles_*.css: ?? 4050 $output = '<h2 class="headingblock header '.$class.'">'.stripslashes($heading).'</h2>'; 4051 4052 if ($return) { 4053 return $output; 4054 } else { 4055 echo $output; 4056 } 4057 } 4058 4059 4060 /** 4061 * Print a link to continue on to another page. 4062 * 4063 * @uses $CFG 4064 * @param string $link The url to create a link to. 4065 */ 4066 function print_continue($link, $return=false) { 4067 4068 global $CFG; 4069 4070 // in case we are logging upgrade in admin/index.php stop it 4071 if (function_exists('upgrade_log_finish')) { 4072 upgrade_log_finish(); 4073 } 4074 4075 $output = ''; 4076 4077 if ($link == '') { 4078 if (!empty($_SERVER['HTTP_REFERER'])) { 4079 $link = $_SERVER['HTTP_REFERER']; 4080 $link = str_replace('&', '&', $link); // make it valid XHTML 4081 } else { 4082 $link = $CFG->wwwroot .'/'; 4083 } 4084 } 4085 4086 $options = array(); 4087 $linkparts = parse_url(str_replace('&', '&', $link)); 4088 if (isset($linkparts['query'])) { 4089 parse_str($linkparts['query'], $options); 4090 } 4091 4092 $output .= '<div class="continuebutton">'; 4093 4094 $output .= print_single_button($link, $options, get_string('continue'), 'get', $CFG->framename, true); 4095 $output .= '</div>'."\n"; 4096 4097 if ($return) { 4098 return $output; 4099 } else { 4100 echo $output; 4101 } 4102 } 4103 4104 4105 /** 4106 * Print a message in a standard themed box. 4107 * Replaces print_simple_box (see deprecatedlib.php) 4108 * 4109 * @param string $message, the content of the box 4110 * @param string $classes, space-separated class names. 4111 * @param string $idbase 4112 * @param boolean $return, return as string or just print it 4113 * @return mixed string or void 4114 */ 4115 function print_box($message, $classes='generalbox', $ids='', $return=false) { 4116 4117 $output = print_box_start($classes, $ids, true); 4118 $output .= stripslashes_safe($message); 4119 $output .= print_box_end(true); 4120 4121 if ($return) { 4122 return $output; 4123 } else { 4124 echo $output; 4125 } 4126 } 4127 4128 /** 4129 * Starts a box using divs 4130 * Replaces print_simple_box_start (see deprecatedlib.php) 4131 * 4132 * @param string $classes, space-separated class names. 4133 * @param string $idbase 4134 * @param boolean $return, return as string or just print it 4135 * @return mixed string or void 4136 */ 4137 function print_box_start($classes='generalbox', $ids='', $return=false) { 4138 global $THEME; 4139 4140 if (strpos($classes, 'clearfix') !== false) { 4141 $clearfix = true; 4142 $classes = trim(str_replace('clearfix', '', $classes)); 4143 } else { 4144 $clearfix = false; 4145 } 4146 4147 if (!empty($THEME->customcorners)) { 4148 $classes .= ' ccbox box'; 4149 } else { 4150 $classes .= ' box'; 4151 } 4152 4153 return print_container_start($clearfix, $classes, $ids, $return); 4154 } 4155 4156 /** 4157 * Simple function to end a box (see above) 4158 * Replaces print_simple_box_end (see deprecatedlib.php) 4159 * 4160 * @param boolean $return, return as string or just print it 4161 */ 4162 function print_box_end($return=false) { 4163 return print_container_end($return); 4164 } 4165 4166 /** 4167 * Print a message in a standard themed container. 4168 * 4169 * @param string $message, the content of the container 4170 * @param boolean $clearfix clear both sides 4171 * @param string $classes, space-separated class names. 4172 * @param string $idbase 4173 * @param boolean $return, return as string or just print it 4174 * @return string or void 4175 */ 4176 function print_container($message, $clearfix=false, $classes='', $idbase='', $return=false) { 4177 4178 $output = print_container_start($clearfix, $classes, $idbase, true); 4179 $output .= stripslashes_safe($message); 4180 $output .= print_container_end(true); 4181 4182 if ($return) { 4183 return $output; 4184 } else { 4185 echo $output; 4186 } 4187 } 4188 4189 /** 4190 * Starts a container using divs 4191 * 4192 * @param boolean $clearfix clear both sides 4193 * @param string $classes, space-separated class names. 4194 * @param string $idbase 4195 * @param boolean $return, return as string or just print it 4196 * @return mixed string or void 4197 */ 4198 function print_container_start($clearfix=false, $classes='', $idbase='', $return=false) { 4199 global $THEME; 4200 4201 if (!isset($THEME->open_containers)) { 4202 $THEME->open_containers = array(); 4203 } 4204 $THEME->open_containers[] = $idbase; 4205 4206 4207 if (!empty($THEME->customcorners)) { 4208 $output = _print_custom_corners_start($clearfix, $classes, $idbase); 4209 } else { 4210 if ($idbase) { 4211 $id = ' id="'.$idbase.'"'; 4212 } else { 4213 $id = ''; 4214 } 4215 if ($clearfix) { 4216 $clearfix = ' clearfix'; 4217 } else { 4218 $clearfix = ''; 4219 } 4220 if ($classes or $clearfix) { 4221 $class = ' class="'.$classes.$clearfix.'"'; 4222 } else { 4223 $class = ''; 4224 } 4225 $output = '<div'.$id.$class.'>'; 4226 } 4227 4228 if ($return) { 4229 return $output; 4230 } else { 4231 echo $output; 4232 } 4233 } 4234 4235 /** 4236 * Simple function to end a container (see above) 4237 * @param boolean $return, return as string or just print it 4238 * @return mixed string or void 4239 */ 4240 function print_container_end($return=false) { 4241 global $THEME; 4242 4243 if (empty($THEME->open_containers)) { 4244 debugging('Incorrect request to end container - no more open containers.', DEBUG_DEVELOPER); 4245 $idbase = ''; 4246 } else { 4247 $idbase = array_pop($THEME->open_containers); 4248 } 4249 4250 if (!empty($THEME->customcorners)) { 4251 $output = _print_custom_corners_end($idbase); 4252 } else { 4253 $output = '</div>'; 4254 } 4255 4256 if ($return) { 4257 return $output; 4258 } else { 4259 echo $output; 4260 } 4261 } 4262 4263 /** 4264 * Returns number of currently open containers 4265 * @return int number of open containers 4266 */ 4267 function open_containers() { 4268 global $THEME; 4269 4270 if (!isset($THEME->open_containers)) { 4271 $THEME->open_containers = array(); 4272 } 4273 4274 return count($THEME->open_containers); 4275 } 4276 4277 /** 4278 * Force closing of open containers 4279 * @param boolean $return, return as string or just print it 4280 * @param int $keep number of containers to be kept open - usually theme or page containers 4281 * @return mixed string or void 4282 */ 4283 function print_container_end_all($return=false, $keep=0) { 4284 $output = ''; 4285 while (open_containers() > $keep) { 4286 $output .= print_container_end($return); 4287 } 4288 4289 if ($return) { 4290 return $output; 4291 } else { 4292 echo $output; 4293 } 4294 } 4295 4296 /** 4297 * Internal function - do not use directly! 4298 * Starting part of the surrounding divs for custom corners 4299 * 4300 * @param boolean $clearfix, add CLASS "clearfix" to the inner div against collapsing 4301 * @param string $classes 4302 * @param mixed $idbase, optionally, define one idbase to be added to all the elements in the corners 4303 * @return string 4304 */ 4305 function _print_custom_corners_start($clearfix=false, $classes='', $idbase='') { 4306 /// Analise if we want ids for the custom corner elements 4307 $id = ''; 4308 $idbt = ''; 4309 $idi1 = ''; 4310 $idi2 = ''; 4311 $idi3 = ''; 4312 4313 if ($idbase) { 4314 $id = 'id="'.$idbase.'" '; 4315 $idbt = 'id="'.$idbase.'-bt" '; 4316 $idi1 = 'id="'.$idbase.'-i1" '; 4317 $idi2 = 'id="'.$idbase.'-i2" '; 4318 $idi3 = 'id="'.$idbase.'-i3" '; 4319 } 4320 4321 /// Calculate current level 4322 $level = open_containers(); 4323 4324 /// Output begins 4325 $output = '<div '.$id.'class="wrap wraplevel'.$level.' '.$classes.'">'."\n"; 4326 $output .= '<div '.$idbt.'class="bt"><div> </div></div>'; 4327 $output .= "\n"; 4328 $output .= '<div '.$idi1.'class="i1"><div '.$idi2.'class="i2">'; 4329 $output .= (!empty($clearfix)) ? '<div '.$idi3.'class="i3 clearfix">' : '<div '.$idi3.'class="i3">'; 4330 4331 return $output; 4332 } 4333 4334 4335 /** 4336 * Internal function - do not use directly! 4337 * Ending part of the surrounding divs for custom corners 4338 * @param string $idbase 4339 * @return string 4340 */ 4341 function _print_custom_corners_end($idbase) { 4342 /// Analise if we want ids for the custom corner elements 4343 $idbb = ''; 4344 4345 if ($idbase) { 4346 $idbb = 'id="' . $idbase . '-bb" '; 4347 } 4348 4349 /// Output begins 4350 $output = '</div></div></div>'; 4351 $output .= "\n"; 4352 $output .= '<div '.$idbb.'class="bb"><div> </div></div>'."\n"; 4353 $output .= '</div>'; 4354 4355 return $output; 4356 } 4357 4358 4359 /** 4360 * Print a self contained form with a single submit button. 4361 * 4362 * @param string $link used as the action attribute on the form, so the URL that will be hit if the button is clicked. 4363 * @param array $options these become hidden form fields, so these options get passed to the script at $link. 4364 * @param string $label the caption that appears on the button. 4365 * @param string $method HTTP method used on the request of the button is clicked. 'get' or 'post'. 4366 * @param string $target no longer used. 4367 * @param boolean $return if false, output the form directly, otherwise return the HTML as a string. 4368 * @param string $tooltip a tooltip to add to the button as a title attribute. 4369 * @param boolean $disabled if true, the button will be disabled. 4370 * @param string $jsconfirmmessage if not empty then display a confirm dialogue with this string as the question. 4371 * @return string / nothing depending on the $return paramter. 4372 */ 4373 function print_single_button($link, $options, $label='OK',