MDL-8096 - user edit form splitting, refactoring, cleanup and fixing - work in progre...
[moodle.git] / lib / weblib.php
1 <?php // $Id$
3 ///////////////////////////////////////////////////////////////////////////
4 //                                                                       //
5 // NOTICE OF COPYRIGHT                                                   //
6 //                                                                       //
7 // Moodle - Modular Object-Oriented Dynamic Learning Environment         //
8 //          http://moodle.com                                            //
9 //                                                                       //
10 // Copyright (C) 2001-2003  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 ///////////////////////////////////////////////////////////////////////////
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  */
41 /// We are going to uses filterlib functions here
42 require_once("$CFG->libdir/filterlib.php");
44 /// Constants
46 /// Define text formatting types ... eventually we can add Wiki, BBcode etc
48 /**
49  * Does all sorts of transformations and filtering
50  */
51 define('FORMAT_MOODLE',   '0');   // Does all sorts of transformations and filtering
53 /**
54  * Plain HTML (with some tags stripped)
55  */
56 define('FORMAT_HTML',     '1');   // Plain HTML (with some tags stripped)
58 /**
59  * Plain text (even tags are printed in full)
60  */
61 define('FORMAT_PLAIN',    '2');   // Plain text (even tags are printed in full)
63 /**
64  * Wiki-formatted text
65  * Deprecated: left here just to note that '3' is not used (at the moment)
66  * and to catch any latent wiki-like text (which generates an error)
67  */
68 define('FORMAT_WIKI',     '3');   // Wiki-formatted text
70 /**
71  * Markdown-formatted text http://daringfireball.net/projects/markdown/
72  */
73 define('FORMAT_MARKDOWN', '4');   // Markdown-formatted text http://daringfireball.net/projects/markdown/
75 /**
76  * TRUSTTEXT marker - if present in text, text cleaning should be bypassed
77  */
78 define('TRUSTTEXT', '#####TRUSTTEXT#####');
81 /**
82  * Allowed tags - string of html tags that can be tested against for safe html tags
83  * @global string $ALLOWED_TAGS
84  */
85 $ALLOWED_TAGS =
86 '<p><br><b><i><u><font><table><tbody><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>';
88 /**
89  * Allowed protocols - array of protocols that are safe to use in links and so on
90  * @global string $ALLOWED_PROTOCOLS
91  */
92 $ALLOWED_PROTOCOLS = array('http', 'https', 'ftp', 'news', 'mailto', 'rtsp', 'teamspeak', 'gopher', 'mms',
93                            'color', 'callto', 'cursor', 'text-align', 'font-size', 'font-weight', 'font-style',
94                            'border', 'margin', 'padding', 'background');   // CSS as well to get through kses
97 /// Functions
99 /**
100  * Add quotes to HTML characters
101  *
102  * Returns $var with HTML characters (like "<", ">", etc.) properly quoted.
103  * This function is very similar to {@link p()}
104  *
105  * @param string $var the string potentially containing HTML characters
106  * @param boolean $strip to decide if we want to strip slashes or no. Default to false.
107  *                true should be used to print data from forms and false for data from DB.
108  * @return string
109  */
110 function s($var, $strip=false) {
112     if ($var == '0') {  // for integer 0, boolean false, string '0'
113         return '0';
114     }
116     if ($strip) {
117         return preg_replace("/&amp;(#\d+);/i", "&$1;", htmlspecialchars(stripslashes_safe($var)));
118     } else {
119         return preg_replace("/&amp;(#\d+);/i", "&$1;", htmlspecialchars($var));
120     }
123 /**
124  * Add quotes to HTML characters
125  *
126  * Prints $var with HTML characters (like "<", ">", etc.) properly quoted.
127  * This function is very similar to {@link s()}
128  *
129  * @param string $var the string potentially containing HTML characters
130  * @param boolean $strip to decide if we want to strip slashes or no. Default to false.
131  *                true should be used to print data from forms and false for data from DB.
132  * @return string
133  */
134 function p($var, $strip=false) {
135     echo s($var, $strip);
138 /**
139  * Does proper javascript quoting.
140  * Do not use addslashes anymore, because it does not work when magic_quotes_sybase is enabled.
141  *
142  * @param mixed value
143  * @return mixed quoted result
144  */
145 function addslashes_js($var) {
146     if (is_string($var)) {
147         $var = str_replace('\\', '\\\\', $var);
148         $var = str_replace(array('\'', '"', "\n", "\r", "\0"), array('\\\'', '\\"', '\\n', '\\r', '\\0'), $var);
149         $var = str_replace('</', '<\/', $var);   // XHTML compliance
150     } else if (is_array($var)) {
151         $var = array_map('addslashes_js', $var);
152     } else if (is_object($var)) {
153         $a = get_object_vars($var);
154         foreach ($a as $key=>$value) {
155           $a[$key] = addslashes_js($value);
156         }
157         $var = (object)$a;
158     }
159     return $var;
162 /**
163  * Remove query string from url
164  *
165  * Takes in a URL and returns it without the querystring portion
166  *
167  * @param string $url the url which may have a query string attached
168  * @return string
169  */
170  function strip_querystring($url) {
172     if ($commapos = strpos($url, '?')) {
173         return substr($url, 0, $commapos);
174     } else {
175         return $url;
176     }
179 /**
180  * Returns the URL of the HTTP_REFERER, less the querystring portion
181  * @return string
182  */
183 function get_referer() {
184     if (isset($_SERVER['HTTP_REFERER'])) {
185         return strip_querystring($_SERVER['HTTP_REFERER']);
186     } else {
187         return '';
188     }
192 /**
193  * Returns the name of the current script, WITH the querystring portion.
194  * this function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
195  * return different things depending on a lot of things like your OS, Web
196  * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.)
197  * <b>NOTE:</b> This function returns false if the global variables needed are not set.
198  *
199  * @return string
200  */
201  function me() {
203     if (!empty($_SERVER['REQUEST_URI'])) {
204         return $_SERVER['REQUEST_URI'];
206     } else if (!empty($_SERVER['PHP_SELF'])) {
207         if (!empty($_SERVER['QUERY_STRING'])) {
208             return $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
209         }
210         return $_SERVER['PHP_SELF'];
212     } else if (!empty($_SERVER['SCRIPT_NAME'])) {
213         if (!empty($_SERVER['QUERY_STRING'])) {
214             return $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING'];
215         }
216         return $_SERVER['SCRIPT_NAME'];
218     } else if (!empty($_SERVER['URL'])) {     // May help IIS (not well tested)
219         if (!empty($_SERVER['QUERY_STRING'])) {
220             return $_SERVER['URL'] .'?'. $_SERVER['QUERY_STRING'];
221         }
222         return $_SERVER['URL'];
224     } else {
225         notify('Warning: Could not find any of these web server variables: $REQUEST_URI, $PHP_SELF, $SCRIPT_NAME or $URL');
226         return false;
227     }
230 /**
231  * Like {@link me()} but returns a full URL
232  * @see me()
233  * @return string
234  */
235 function qualified_me() {
237     global $CFG;
239     if (!empty($CFG->wwwroot)) {
240         $url = parse_url($CFG->wwwroot);
241     }
243     if (!empty($url['host'])) {
244         $hostname = $url['host'];
245     } else if (!empty($_SERVER['SERVER_NAME'])) {
246         $hostname = $_SERVER['SERVER_NAME'];
247     } else if (!empty($_ENV['SERVER_NAME'])) {
248         $hostname = $_ENV['SERVER_NAME'];
249     } else if (!empty($_SERVER['HTTP_HOST'])) {
250         $hostname = $_SERVER['HTTP_HOST'];
251     } else if (!empty($_ENV['HTTP_HOST'])) {
252         $hostname = $_ENV['HTTP_HOST'];
253     } else {
254         notify('Warning: could not find the name of this server!');
255         return false;
256     }
258     if (!empty($url['port'])) {
259         $hostname .= ':'.$url['port'];
260     } else if (!empty($_SERVER['SERVER_PORT'])) {
261         if ($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
262             $hostname .= ':'.$_SERVER['SERVER_PORT'];
263         }
264     }
266     if (isset($_SERVER['HTTPS'])) {
267         $protocol = ($_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
268     } else if (isset($_SERVER['SERVER_PORT'])) { # Apache2 does not export $_SERVER['HTTPS']
269         $protocol = ($_SERVER['SERVER_PORT'] == '443') ? 'https://' : 'http://';
270     } else {
271         $protocol = 'http://';
272     }
274     $url_prefix = $protocol.$hostname;
275     return $url_prefix . me();
278 /**
279  * Determine if there is data waiting to be processed from a form
280  *
281  * Used on most forms in Moodle to check for data
282  * Returns the data as an object, if it's found.
283  * This object can be used in foreach loops without
284  * casting because it's cast to (array) automatically
285  *
286  * Checks that submitted POST data exists and returns it as object.
287  *
288  * @param string $url not used anymore
289  * @return mixed false or object
290  */
291 function data_submitted($url='') {
293     if (empty($_POST)) {
294         return false;
295     } else {
296         return (object)$_POST;
297     }
300 /**
301  * Moodle replacement for php stripslashes() function,
302  * works also for objects and arrays.
303  *
304  * The standard php stripslashes() removes ALL backslashes
305  * even from strings - so  C:\temp becomes C:temp - this isn't good.
306  * This function should work as a fairly safe replacement
307  * to be called on quoted AND unquoted strings (to be sure)
308  *
309  * @param mixed something to remove unsafe slashes from
310  * @return mixed
311  */
312 function stripslashes_safe($mixed) {
313     // there is no need to remove slashes from int, float and bool types
314     if (empty($mixed)) {
315         //nothing to do...
316     } else if (is_string($mixed)) {
317         if (ini_get_bool('magic_quotes_sybase')) { //only unescape single quotes
318             $mixed = str_replace("''", "'", $mixed);
319         } else { //the rest, simple and double quotes and backslashes
320             $mixed = str_replace("\\'", "'", $mixed);
321             $mixed = str_replace('\\"', '"', $mixed);
322             $mixed = str_replace('\\\\', '\\', $mixed);
323         }
324     } else if (is_array($mixed)) {
325         foreach ($mixed as $key => $value) {
326             $mixed[$key] = stripslashes_safe($value);
327         }
328     } else if (is_object($mixed)) {
329         $vars = get_object_vars($mixed);
330         foreach ($vars as $key => $value) {
331             $mixed->$key = stripslashes_safe($value);
332         }
333     }
335     return $mixed;
338 /**
339  * Recursive implementation of stripslashes()
340  *
341  * This function will allow you to strip the slashes from a variable.
342  * If the variable is an array or object, slashes will be stripped
343  * from the items (or properties) it contains, even if they are arrays
344  * or objects themselves.
345  *
346  * @param mixed the variable to remove slashes from
347  * @return mixed
348  */
349 function stripslashes_recursive($var) {
350     if(is_object($var)) {
351         $properties = get_object_vars($var);
352         foreach($properties as $property => $value) {
353             $var->$property = stripslashes_recursive($value);
354         }
355     }
356     else if(is_array($var)) {
357         foreach($var as $property => $value) {
358             $var[$property] = stripslashes_recursive($value);
359         }
360     }
361     else if(is_string($var)) {
362         $var = stripslashes($var);
363     }
364     return $var;
367 /**
368  * Given some normal text this function will break up any
369  * long words to a given size by inserting the given character
370  *
371  * It's multibyte savvy and doesn't change anything inside html tags.
372  *
373  * @param string $string the string to be modified
374  * @param int $maxsize maximum length of the string to be returned
375  * @param string $cutchar the string used to represent word breaks
376  * @return string
377  */
378 function break_up_long_words($string, $maxsize=20, $cutchar=' ') {
380 /// Loading the textlib singleton instance. We are going to need it.
381     $textlib = textlib_get_instance();
383 /// First of all, save all the tags inside the text to skip them
384     $tags = array();
385     filter_save_tags($string,$tags);
387 /// Process the string adding the cut when necessary
388     $output = '';
389     $length = $textlib->strlen($string);
390     $wordlength = 0;
392     for ($i=0; $i<$length; $i++) {
393         $char = $textlib->substr($string, $i, 1);
394         if ($char == ' ' or $char == "\t" or $char == "\n" or $char == "\r" or $char == "<" or $char == ">") {
395             $wordlength = 0;
396         } else {
397             $wordlength++;
398             if ($wordlength > $maxsize) {
399                 $output .= $cutchar;
400                 $wordlength = 0;
401             }
402         }
403         $output .= $char;
404     }
406 /// Finally load the tags back again
407     if (!empty($tags)) {
408         $output = str_replace(array_keys($tags), $tags, $output);
409     }
411     return $output;
414 /**
415  * This does a search and replace, ignoring case
416  * This function is only used for versions of PHP older than version 5
417  * which do not have a native version of this function.
418  * Taken from the PHP manual, by bradhuizenga @ softhome.net
419  *
420  * @param string $find the string to search for
421  * @param string $replace the string to replace $find with
422  * @param string $string the string to search through
423  * return string
424  */
425 if (!function_exists('str_ireplace')) {    /// Only exists in PHP 5
426     function str_ireplace($find, $replace, $string) {
428         if (!is_array($find)) {
429             $find = array($find);
430         }
432         if(!is_array($replace)) {
433             if (!is_array($find)) {
434                 $replace = array($replace);
435             } else {
436                 // this will duplicate the string into an array the size of $find
437                 $c = count($find);
438                 $rString = $replace;
439                 unset($replace);
440                 for ($i = 0; $i < $c; $i++) {
441                     $replace[$i] = $rString;
442                 }
443             }
444         }
446         foreach ($find as $fKey => $fItem) {
447             $between = explode(strtolower($fItem),strtolower($string));
448             $pos = 0;
449             foreach($between as $bKey => $bItem) {
450                 $between[$bKey] = substr($string,$pos,strlen($bItem));
451                 $pos += strlen($bItem) + strlen($fItem);
452             }
453             $string = implode($replace[$fKey],$between);
454         }
455         return ($string);
456     }
459 /**
460  * Locate the position of a string in another string
461  *
462  * This function is only used for versions of PHP older than version 5
463  * which do not have a native version of this function.
464  * Taken from the PHP manual, by dmarsh @ spscc.ctc.edu
465  *
466  * @param string $haystack The string to be searched
467  * @param string $needle The string to search for
468  * @param int $offset The position in $haystack where the search should begin.
469  */
470 if (!function_exists('stripos')) {    /// Only exists in PHP 5
471     function stripos($haystack, $needle, $offset=0) {
473         return strpos(strtoupper($haystack), strtoupper($needle), $offset);
474     }
477 /**
478  * This function will create a HTML link that will work on both
479  * Javascript and non-javascript browsers.
480  * Relies on the Javascript function openpopup in javascript.php
481  *
482  * $url must be relative to home page  eg /mod/survey/stuff.php
483  * @param string $url Web link relative to home page
484  * @param string $name Name to be assigned to the popup window
485  * @param string $linkname Text to be displayed as web link
486  * @param int $height Height to assign to popup window
487  * @param int $width Height to assign to popup window
488  * @param string $title Text to be displayed as popup page title
489  * @param string $options List of additional options for popup window
490  * @todo Add code examples and list of some options that might be used.
491  * @param boolean $return Should the link to the popup window be returned as a string (true) or printed immediately (false)?
492  * @return string
493  * @uses $CFG
494  */
495 function link_to_popup_window ($url, $name='popup', $linkname='click here',
496                                $height=400, $width=500, $title='Popup window',
497                                $options='none', $return=false) {
499     global $CFG;
501     if ($options == 'none') {
502         $options = 'menubar=0,location=0,scrollbars,resizable,width='. $width .',height='. $height;
503     }
504     $fullscreen = 0;
506     if (!(strpos($url,$CFG->wwwroot) === false)) { // some log url entries contain _SERVER[HTTP_REFERRER] in which case wwwroot is already there.
507         $url = substr($url, strlen($CFG->wwwroot));
508     }
510     $link = '<a title="'. s($title) .'" href="'. $CFG->wwwroot . $url .'" '.
511            "onclick=\"this.target='$name'; return openpopup('$url', '$name', '$options', $fullscreen);\">$linkname</a>";
512     if ($return) {
513         return $link;
514     } else {
515         echo $link;
516     }
519 /**
520  * This function will print a button submit form element
521  * that will work on both Javascript and non-javascript browsers.
522  * Relies on the Javascript function openpopup in javascript.php
523  *
524  * $url must be relative to home page  eg /mod/survey/stuff.php
525  * @param string $url Web link relative to home page
526  * @param string $name Name to be assigned to the popup window
527  * @param string $linkname Text to be displayed as web link
528  * @param int $height Height to assign to popup window
529  * @param int $width Height to assign to popup window
530  * @param string $title Text to be displayed as popup page title
531  * @param string $options List of additional options for popup window
532  * @param string $return If true, return as a string, otherwise print
533  * @return string
534  * @uses $CFG
535  */
536 function button_to_popup_window ($url, $name='popup', $linkname='click here',
537                                  $height=400, $width=500, $title='Popup window', $options='none', $return=false,
538                                  $id='', $class='') {
540     global $CFG;
542     if ($options == 'none') {
543         $options = 'menubar=0,location=0,scrollbars,resizable,width='. $width .',height='. $height;
544     }
546     if ($id) {
547         $id = ' id="'.$id.'" ';
548     }
549     if ($class) {
550         $class = ' class="'.$class.'" ';
551     }
552     $fullscreen = 0;
554     $button = '<input type="button" name="'.$name.'" title="'. $title .'" value="'. $linkname .' ..." '.$id.$class.
555               "onclick=\"return openpopup('$url', '$name', '$options', $fullscreen);\" />\n";
556     if ($return) {
557         return $button;
558     } else {
559         echo $button;
560     }
564 /**
565  * Prints a simple button to close a window
566  */
567 function close_window_button($name='closewindow', $return=false) {
568     $output = '';
570     $output .= '<div class="closewindow">' . "\n";
571     $output .= '<script type="text/javascript">' . "\n";
572     $output .= '//<![CDATA[' . "\n";
573     $output .= "document.write('<form>');\n";
574     $output .= "document.write('<input type=\"button\" onclick=\"self.close();\" value=\"".get_string("closewindow")."\" />');\n";
575     $output .= "document.write('<\/form>');\n";
576     $output .= '//]]>' . "\n";
577     $output .= '</script>' . "\n";
578     $output .= '<noscript><p>' . "\n";
579     $output .= get_string($name);
580     $output .= '</p></noscript>' . "\n";
581     $output .= '</div>' . "\n";
583     if ($return) {
584         return $output;
585     } else {
586         echo $output;
587     }
590 /*
591  * Try and close the current window immediately using Javascript
592  */
593 function close_window($delay=0) {
594 ?>
595 <script type="text/javascript">
596 //<![CDATA[
597     function close_this_window() {
598         self.close();
599     }
600     setTimeout("close_this_window()", <?php echo $delay * 1000 ?>);
601 //]]>
602 </script>
603 <noscript><center>
604 <?php print_string('pleaseclose') ?>
605 </center></noscript>
606 <?php
607     die;
611 /**
612  * Given an array of value, creates a popup menu to be part of a form
613  * $options["value"]["label"]
614  *
615  * @param    type description
616  * @todo Finish documenting this function
617  */
618 function choose_from_menu ($options, $name, $selected='', $nothing='choose', $script='',
619                            $nothingvalue='0', $return=false, $disabled=false, $tabindex=0, $id='') {
621     if ($nothing == 'choose') {
622         $nothing = get_string('choose') .'...';
623     }
625     $attributes = ($script) ? 'onchange="'. $script .'"' : '';
626     if ($disabled) {
627         $attributes .= ' disabled="disabled"';
628     }
630     if ($tabindex) {
631         $attributes .= ' tabindex="'.$tabindex.'"';
632     }
634     if ($id ==='') {
635         $id = 'menu'.$name;
636          // name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading
637         $id = str_replace('[', '', $id);
638         $id = str_replace(']', '', $id);
639     }
641     $output = '<select id="'.$id.'" name="'. $name .'" '. $attributes .'>' . "\n";
642     if ($nothing) {
643         $output .= '   <option value="'. s($nothingvalue) .'"'. "\n";
644         if ($nothingvalue === $selected) {
645             $output .= ' selected="selected"';
646         }
647         $output .= '>'. $nothing .'</option>' . "\n";
648     }
649     if (!empty($options)) {
650         foreach ($options as $value => $label) {
651             $output .= '   <option value="'. s($value) .'"';
652             if ((string)$value == (string)$selected) {
653                 $output .= ' selected="selected"';
654             }
655             if ($label === '') {
656                 $output .= '>'. $value .'</option>' . "\n";
657             } else {
658                 $output .= '>'. $label .'</option>' . "\n";
659             }
660         }
661     }
662     $output .= '</select>' . "\n";
664     if ($return) {
665         return $output;
666     } else {
667         echo $output;
668     }
671 /**
672  * Choose value 0 or 1 from a menu with options 'No' and 'Yes'.
673  * Other options like choose_from_menu.
674  */
675 function choose_from_menu_yesno($name, $selected, $script = '',
676         $return = false, $disabled = false, $tabindex = 0) {
677     return choose_from_menu(array(get_string('no'), get_string('yes')), $name,
678             $selected, '', $script, '0', $return, $disabled, $tabindex);
681 /**
682  * Just like choose_from_menu, but takes a nested array (2 levels) and makes a dropdown menu
683  * including option headings with the first level.
684  */
685 function choose_from_menu_nested($options,$name,$selected='',$nothing='choose',$script = '',
686                                  $nothingvalue=0,$return=false,$disabled=false,$tabindex=0) {
688    if ($nothing == 'choose') {
689         $nothing = get_string('choose') .'...';
690     }
692     $attributes = ($script) ? 'onchange="'. $script .'"' : '';
693     if ($disabled) {
694         $attributes .= ' disabled="disabled"';
695     }
697     if ($tabindex) {
698         $attributes .= ' tabindex="'.$tabindex.'"';
699     }
701     $output = '<select id="menu'.$name.'" name="'. $name .'" '. $attributes .'>' . "\n";
702     if ($nothing) {
703         $output .= '   <option value="'. $nothingvalue .'"'. "\n";
704         if ($nothingvalue === $selected) {
705             $output .= ' selected="selected"';
706         }
707         $output .= '>'. $nothing .'</option>' . "\n";
708     }
709     if (!empty($options)) {
710         foreach ($options as $section => $values) {
711             $output .= '   <optgroup label="'.$section.'">'."\n";
712             foreach ($values as $value => $label) {
713                 $output .= '   <option value="'. $value .'"';
714                 if ((string)$value == (string)$selected) {
715                     $output .= ' selected="selected"';
716                 }
717                 if ($label === '') {
718                     $output .= '>'. $value .'</option>' . "\n";
719                 } else {
720                     $output .= '>'. $label .'</option>' . "\n";
721                 }
722             }
723             $output .= '   </optgroup>'."\n";
724         }
725     }
726     $output .= '</select>' . "\n";
728     if ($return) {
729         return $output;
730     } else {
731         echo $output;
732     }
736 /**
737  * Given an array of values, creates a group of radio buttons to be part of a form
738  *
739  * @param array  $options  An array of value-label pairs for the radio group (values as keys)
740  * @param string $name     Name of the radiogroup (unique in the form)
741  * @param string $checked  The value that is already checked
742  */
743 function choose_from_radio ($options, $name, $checked='', $return=false) {
745     static $idcounter = 0;
747     if (!$name) {
748         $name = 'unnamed';
749     }
751     $output = '<span class="radiogroup '.$name."\">\n";
753     if (!empty($options)) {
754         $currentradio = 0;
755         foreach ($options as $value => $label) {
756             $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter);
757             $output .= ' <span class="radioelement '.$name.' rb'.$currentradio."\">";
758             $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="radio" value="'.$value.'"';
759             if ($value == $checked) {
760                 $output .= ' checked="checked"';
761             }
762             if ($label === '') {
763                 $output .= ' /> <label for="'.$htmlid.'">'.  $value .'</label></span>' .  "\n";
764             } else {
765                 $output .= ' /> <label for="'.$htmlid.'">'.  $label .'</label></span>' .  "\n";
766             }
767             $currentradio = ($currentradio + 1) % 2;
768         }
769     }
771     $output .= '</span>' .  "\n";
773     if ($return) {
774         return $output;
775     } else {
776         echo $output;
777     }
780 /** Display an standard html checkbox with an optional label
781  *
782  * @param string  $name    The name of the checkbox
783  * @param string  $value   The valus that the checkbox will pass when checked
784  * @param boolean $checked The flag to tell the checkbox initial state
785  * @param string  $label   The label to be showed near the checkbox
786  * @param string  $alt     The info to be inserted in the alt tag
787  */
788 function print_checkbox ($name, $value, $checked = true, $label = '', $alt = '', $script='',$return=false) {
790     static $idcounter = 0;
792     if (!$name) {
793         $name = 'unnamed';
794     }
796     if ($alt) {
797         $alt = strip_tags($alt);
798     } else {
799         $alt = 'checkbox';
800     }
802     if ($checked) {
803         $strchecked = ' checked="checked"';
804     } else {
805         $strchecked = '';
806     }
808     $htmlid = 'auto-cb'.sprintf('%04d', ++$idcounter);
809     $output  = '<span class="checkbox '.$name."\">";
810     $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="checkbox" value="'.$value.'" alt="'.$alt.'"'.$strchecked.' '.((!empty($script)) ? ' onClick="'.$script.'" ' : '').' />';
811     if(!empty($label)) {
812         $output .= ' <label for="'.$htmlid.'">'.$label.'</label>';
813     }
814     $output .= '</span>'."\n";
816     if (empty($return)) {
817         echo $output;
818     } else {
819         return $output;
820     }
824 /** Display an standard html text field with an optional label
825  *
826  * @param string  $name    The name of the text field
827  * @param string  $value   The value of the text field
828  * @param string  $label   The label to be showed near the text field
829  * @param string  $alt     The info to be inserted in the alt tag
830  */
831 function print_textfield ($name, $value, $alt = '',$size=50,$maxlength=0, $return=false) {
833     static $idcounter = 0;
835     if (empty($name)) {
836         $name = 'unnamed';
837     }
839     if (empty($alt)) {
840         $alt = 'textfield';
841     }
843     if (!empty($maxlength)) {
844         $maxlength = ' maxlength="'.$maxlength.'" ';
845     }
847     $htmlid = 'auto-tf'.sprintf('%04d', ++$idcounter);
848     $output  = '<span class="textfield '.$name."\">";
849     $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="text" value="'.$value.'" size="'.$size.'" '.$maxlength.' alt="'.$alt.'" />';
851     $output .= '</span>'."\n";
853     if (empty($return)) {
854         echo $output;
855     } else {
856         return $output;
857     }
862 /**
863  * Implements a complete little popup form
864  *
865  * @uses $CFG
866  * @param string $common  The URL up to the point of the variable that changes
867  * @param array $options  Alist of value-label pairs for the popup list
868  * @param string $formid Id must be unique on the page (originaly $formname)
869  * @param string $selected The option that is already selected
870  * @param string $nothing The label for the "no choice" option
871  * @param string $help The name of a help page if help is required
872  * @param string $helptext The name of the label for the help button
873  * @param boolean $return Indicates whether the function should return the text
874  *         as a string or echo it directly to the page being rendered
875  * @param string $targetwindow The name of the target page to open the linked page in.
876  * @return string If $return is true then the entire form is returned as a string.
877  * @todo Finish documenting this function<br>
878  */
879 function popup_form($common, $options, $formid, $selected='', $nothing='choose', $help='', $helptext='', $return=false, $targetwindow='self', $selectlabel='') {
881     global $CFG;
882     static $go, $choose;   /// Locally cached, in case there's lots on a page
884     if (empty($options)) {
885         return '';
886     }
888     if (!isset($go)) {
889         $go = get_string('go');
890     }
892     if ($nothing == 'choose') {
893         if (!isset($choose)) {
894             $choose = get_string('choose');
895         }
896         $nothing = $choose.'...';
897     }
899     // changed reference to document.getElementById('id_abc') instead of document.abc
900     // MDL-7861
901     $output = '<form action="'.$CFG->wwwroot.'/course/jumpto.php"'.
902                         ' method="get" '.
903                          $CFG->frametarget.
904                         ' id="'.$formid.'"'.
905                         ' class="popupform">';
906     if ($help) {
907         $button = helpbutton($help, $helptext, 'moodle', true, false, '', true);
908     } else {
909         $button = '';
910     }
912     if ($selectlabel) {
913         $selectlabel = '<label for="'.$formid.'_jump">'.$selectlabel.'</label>';
914     }
916     $output .= '<fieldset class="invisiblefieldset">'.$selectlabel.$button.'<select id="'.$formid.'_jump" name="jump" onchange="'.$targetwindow.'.location=document.getElementById(\''.$formid.'\').jump.options[document.getElementById(\''.$formid.'\').jump.selectedIndex].value;">'."\n";
918     if ($nothing != '') {
919         $output .= "   <option value=\"javascript:void(0)\">$nothing</option>\n";
920     }
922     $inoptgroup = false;
924     foreach ($options as $value => $label) {
926         if ($label == '--') { /// we are ending previous optgroup
927             /// Check to see if we already have a valid open optgroup
928             /// XHTML demands that there be at least 1 option within an optgroup
929             if ($inoptgroup and (count($optgr) > 1) ) {
930                 $output .= implode('', $optgr);
931                 $output .= '   </optgroup>';
932             }
933             $optgr = array();
934             $inoptgroup = false;
935             continue;
936         } else if (substr($label,0,2) == '--') { /// we are starting a new optgroup
938             /// Check to see if we already have a valid open optgroup
939             /// XHTML demands that there be at least 1 option within an optgroup
940             if ($inoptgroup and (count($optgr) > 1) ) {
941                 $output .= implode('', $optgr);
942                 $output .= '   </optgroup>';
943             }
945             unset($optgr);
946             $optgr = array();
948             $optgr[]  = '   <optgroup label="'. substr($label,2) .'">';   // Plain labels
950             $inoptgroup = true; /// everything following will be in an optgroup
951             continue;
953         } else {
954            if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()]))
955             {
956                 $url=sid_process_url( $common . $value );
957             } else
958             {
959                 $url=$common . $value;
960             }
961             $optstr = '   <option value="' . $url . '"';
963             if ($value == $selected) {
964                 $optstr .= ' selected="selected"';
965             }
967             if ($label) {
968                 $optstr .= '>'. $label .'</option>' . "\n";
969             } else {
970                 $optstr .= '>'. $value .'</option>' . "\n";
971             }
973             if ($inoptgroup) {
974                 $optgr[] = $optstr;
975             } else {
976                 $output .= $optstr;
977             }
978         }
980     }
982     /// catch the final group if not closed
983     if ($inoptgroup and count($optgr) > 1) {
984         $output .= implode('', $optgr);
985         $output .= '    </optgroup>';
986     }
988     $output .= '</select>';
989     $output .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
990     $output .= '<div id="noscript'.$formid.'" style="display: inline;">';
991     $output .= '<input type="submit" value="'.$go.'" /></div>';
992     $output .= '<script type="text/javascript">'.
993                "\n//<![CDATA[\n".
994                'document.getElementById("noscript'.$formid.'").style.display = "none";'.
995                "\n//]]>\n".'</script>';
996     $output .= '</fieldset>';
997     $output .= '</form>';
999     if ($return) {
1000         return $output;
1001     } else {
1002         echo $output;
1003     }
1007 /**
1008  * Prints some red text
1009  *
1010  * @param string $error The text to be displayed in red
1011  */
1012 function formerr($error) {
1014     if (!empty($error)) {
1015         echo '<span class="error">'. $error .'</span>';
1016     }
1019 /**
1020  * Validates an email to make sure it makes sense.
1021  *
1022  * @param string $address The email address to validate.
1023  * @return boolean
1024  */
1025 function validate_email($address) {
1027     return (ereg('^[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+'.
1028                  '(\.[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+)*'.
1029                   '@'.
1030                   '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.
1031                   '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$',
1032                   $address));
1035 /**
1036  * Extracts file argument either from file parameter or PATH_INFO
1037  *
1038  * @param string $scriptname name of the calling script
1039  * @return string file path (only safe characters)
1040  */
1041 function get_file_argument($scriptname) {
1042     global $_SERVER;
1044     $relativepath = FALSE;
1046     // first try normal parameter (compatible method == no relative links!)
1047     $relativepath = optional_param('file', FALSE, PARAM_PATH);
1048     if ($relativepath === '/testslasharguments') {
1049         echo 'test -1      : Incorrect use - try "file.php/testslasharguments" instead'; //indicate fopen/fread works for health center
1050         die;
1051     }
1053     // then try extract file from PATH_INFO (slasharguments method)
1054     if (!$relativepath and !empty($_SERVER['PATH_INFO'])) {
1055         $path_info = $_SERVER['PATH_INFO'];
1056         // check that PATH_INFO works == must not contain the script name
1057         if (!strpos($path_info, $scriptname)) {
1058             $relativepath = clean_param(rawurldecode($path_info), PARAM_PATH);
1059             if ($relativepath === '/testslasharguments') {
1060                 echo 'test 1      : Slasharguments test passed. Server confguration is compatible with file.php/1/pic.jpg slashargument setting.'; //indicate ok for health center
1061                 die;
1062             }
1063         }
1064     }
1066     // now if both fail try the old way
1067     // (for compatibility with misconfigured or older buggy php implementations)
1068     if (!$relativepath) {
1069         $arr = explode($scriptname, me());
1070         if (!empty($arr[1])) {
1071             $path_info = strip_querystring($arr[1]);
1072             $relativepath = clean_param(rawurldecode($path_info), PARAM_PATH);
1073             if ($relativepath === '/testslasharguments') {
1074                 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
1075                 die;
1076             }
1077         }
1078     }
1080     return $relativepath;
1083 /**
1084  * Searches the current environment variables for some slash arguments
1085  *
1086  * @param string $file ?
1087  * @todo Finish documenting this function
1088  */
1089 function get_slash_arguments($file='file.php') {
1091     if (!$string = me()) {
1092         return false;
1093     }
1095     $pathinfo = explode($file, $string);
1097     if (!empty($pathinfo[1])) {
1098         return addslashes($pathinfo[1]);
1099     } else {
1100         return false;
1101     }
1104 /**
1105  * Extracts arguments from "/foo/bar/something"
1106  * eg http://mysite.com/script.php/foo/bar/something
1107  *
1108  * @param string $string ?
1109  * @param int $i ?
1110  * @return array|string
1111  * @todo Finish documenting this function
1112  */
1113 function parse_slash_arguments($string, $i=0) {
1115     if (detect_munged_arguments($string)) {
1116         return false;
1117     }
1118     $args = explode('/', $string);
1120     if ($i) {     // return just the required argument
1121         return $args[$i];
1123     } else {      // return the whole array
1124         array_shift($args);  // get rid of the empty first one
1125         return $args;
1126     }
1129 /**
1130  * Just returns an array of text formats suitable for a popup menu
1131  *
1132  * @uses FORMAT_MOODLE
1133  * @uses FORMAT_HTML
1134  * @uses FORMAT_PLAIN
1135  * @uses FORMAT_MARKDOWN
1136  * @return array
1137  */
1138 function format_text_menu() {
1140     return array (FORMAT_MOODLE => get_string('formattext'),
1141                   FORMAT_HTML   => get_string('formathtml'),
1142                   FORMAT_PLAIN  => get_string('formatplain'),
1143                   FORMAT_MARKDOWN  => get_string('formatmarkdown'));
1146 /**
1147  * Given text in a variety of format codings, this function returns
1148  * the text as safe HTML.
1149  *
1150  * @uses $CFG
1151  * @uses FORMAT_MOODLE
1152  * @uses FORMAT_HTML
1153  * @uses FORMAT_PLAIN
1154  * @uses FORMAT_WIKI
1155  * @uses FORMAT_MARKDOWN
1156  * @param string $text The text to be formatted. This is raw text originally from user input.
1157  * @param int $format Identifier of the text format to be used
1158  *            (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN)
1159  * @param  array $options ?
1160  * @param int $courseid ?
1161  * @return string
1162  * @todo Finish documenting this function
1163  */
1164 function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
1166     global $CFG, $course;
1168     if ($text === '') {
1169         return ''; // no need to do any filters and cleaning
1170     }
1172     if (!isset($options->trusttext)) {
1173         $options->trusttext = false;
1174     }
1176     if (!isset($options->noclean)) {
1177         $options->noclean=false;
1178     }
1179     if (!isset($options->nocache)) {
1180         $options->nocache=false;
1181     }
1182     if (!isset($options->smiley)) {
1183         $options->smiley=true;
1184     }
1185     if (!isset($options->filter)) {
1186         $options->filter=true;
1187     }
1188     if (!isset($options->para)) {
1189         $options->para=true;
1190     }
1191     if (!isset($options->newlines)) {
1192         $options->newlines=true;
1193     }
1195     if (empty($courseid)) {
1196         if (!empty($course->id)) {         // An ugly hack for better compatibility
1197             $courseid = $course->id;
1198         }
1199     }
1201     if (!empty($CFG->cachetext) and empty($options->nocache)) {
1202         $time = time() - $CFG->cachetext;
1203         $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);
1204         if ($oldcacheitem = get_record_sql('SELECT * FROM '.$CFG->prefix.'cache_text WHERE md5key = \''.$md5key.'\'', true)) {
1205             if ($oldcacheitem->timemodified >= $time) {
1206                 return $oldcacheitem->formattedtext;
1207             }
1208         }
1209     }
1211     // trusttext overrides the noclean option!
1212     if ($options->trusttext) {
1213         if (trusttext_present($text)) {
1214             $text = trusttext_strip($text);
1215             if (!empty($CFG->enabletrusttext)) {
1216                 $options->noclean = true;
1217             } else {
1218                 $options->noclean = false;
1219             }
1220         } else {
1221             $options->noclean = false;
1222         }
1223     } else if (!debugging('', DEBUG_DEVELOPER)) {
1224         // strip any forgotten trusttext in non-developer mode
1225         // do not forget to disable text cache when debugging trusttext!!
1226         $text = trusttext_strip($text);
1227     }
1229     $CFG->currenttextiscacheable = true;   // Default status - can be changed by any filter
1231     switch ($format) {
1232         case FORMAT_HTML:
1233             if ($options->smiley) {
1234                 replace_smilies($text);
1235             }
1236             if (!$options->noclean) {
1237                 $text = clean_text($text, FORMAT_HTML);
1238             }
1239             if ($options->filter) {
1240                 $text = filter_text($text, $courseid);
1241             }
1242             break;
1244         case FORMAT_PLAIN:
1245             $text = s($text); // cleans dangerous JS
1246             $text = rebuildnolinktag($text);
1247             $text = str_replace('  ', '&nbsp; ', $text);
1248             $text = nl2br($text);
1249             break;
1251         case FORMAT_WIKI:
1252             // this format is deprecated
1253             $text = '<p>NOTICE: Wiki-like formatting has been removed from Moodle.  You should not be seeing
1254                      this message as all texts should have been converted to Markdown format instead.
1255                      Please post a bug report to http://moodle.org/bugs with information about where you
1256                      saw this message.</p>'.s($text);
1257             break;
1259         case FORMAT_MARKDOWN:
1260             $text = markdown_to_html($text);
1261             if ($options->smiley) {
1262                 replace_smilies($text);
1263             }
1264             if (!$options->noclean) {
1265                 $text = clean_text($text, FORMAT_HTML);
1266             }
1268             if ($options->filter) {
1269                 $text = filter_text($text, $courseid);
1270             }
1271             break;
1273         default:  // FORMAT_MOODLE or anything else
1274             $text = text_to_html($text, $options->smiley, $options->para, $options->newlines);
1275             if (!$options->noclean) {
1276                 $text = clean_text($text, FORMAT_HTML);
1277             }
1279             if ($options->filter) {
1280                 $text = filter_text($text, $courseid);
1281             }
1282             break;
1283     }
1285     if (empty($options->nocache) and !empty($CFG->cachetext) and $CFG->currenttextiscacheable) {
1286         $newcacheitem = new object();
1287         $newcacheitem->md5key = $md5key;
1288         $newcacheitem->formattedtext = addslashes($text);
1289         $newcacheitem->timemodified = time();
1290         if ($oldcacheitem) {                               // See bug 4677 for discussion
1291             $newcacheitem->id = $oldcacheitem->id;
1292             @update_record('cache_text', $newcacheitem);   // Update existing record in the cache table
1293                                                            // It's unlikely that the cron cache cleaner could have
1294                                                            // deleted this entry in the meantime, as it allows
1295                                                            // some extra time to cover these cases.
1296         } else {
1297             @insert_record('cache_text', $newcacheitem);   // Insert a new record in the cache table
1298                                                            // Again, it's possible that another user has caused this
1299                                                            // record to be created already in the time that it took
1300                                                            // to traverse this function.  That's OK too, as the
1301                                                            // call above handles duplicate entries, and eventually
1302                                                            // the cron cleaner will delete them.
1303         }
1304     }
1306     return $text;
1309 /** Converts the text format from the value to the 'internal'
1310  *  name or vice versa. $key can either be the value or the name
1311  *  and you get the other back.
1312  *
1313  *  @param mixed int 0-4 or string one of 'moodle','html','plain','markdown'
1314  *  @return mixed as above but the other way around!
1315  */
1316 function text_format_name( $key ) {
1317   $lookup = array();
1318   $lookup[FORMAT_MOODLE] = 'moodle';
1319   $lookup[FORMAT_HTML] = 'html';
1320   $lookup[FORMAT_PLAIN] = 'plain';
1321   $lookup[FORMAT_MARKDOWN] = 'markdown';
1322   $value = "error";
1323   if (!is_numeric($key)) {
1324     $key = strtolower( $key );
1325     $value = array_search( $key, $lookup );
1326   }
1327   else {
1328     if (isset( $lookup[$key] )) {
1329       $value =  $lookup[ $key ];
1330     }
1331   }
1332   return $value;
1335 /** Given a simple string, this function returns the string
1336  *  processed by enabled filters if $CFG->filterall is enabled
1337  *
1338  *  @param string  $string     The string to be filtered.
1339  *  @param boolean $striplinks To strip any link in the result text.
1340  *  @param int     $courseid   Current course as filters can, potentially, use it
1341  *  @return string
1342  */
1343 function format_string ($string, $striplinks = false, $courseid=NULL ) {
1345     global $CFG, $COURSE;
1347     //We'll use a in-memory cache here to speed up repeated strings
1348     static $strcache;
1350     //Calculate md5
1351     $md5 = md5($string.'<+>'.$striplinks);
1353     //Fetch from cache if possible
1354     if(isset($strcache[$md5])) {
1355         return $strcache[$md5];
1356     }
1358     if (empty($courseid)) {
1359         if (!empty($COURSE->id)) {         // An ugly hack for better compatibility
1360             $courseid = $COURSE->id;       // (copied from format_text)
1361         }
1362     }
1364     if (!empty($CFG->filterall)) {
1365         $string = filter_text($string, $courseid);
1366     }
1368     if ($striplinks) {  //strip links in string
1369         $string = preg_replace('/(<a[^>]+?>)(.+?)(<\/a>)/is','$2',$string);
1370     }
1372     //Store to cache
1373     $strcache[$md5] = $string;
1375     return $string;
1378 /**
1379  * Given text in a variety of format codings, this function returns
1380  * the text as plain text suitable for plain email.
1381  *
1382  * @uses FORMAT_MOODLE
1383  * @uses FORMAT_HTML
1384  * @uses FORMAT_PLAIN
1385  * @uses FORMAT_WIKI
1386  * @uses FORMAT_MARKDOWN
1387  * @param string $text The text to be formatted. This is raw text originally from user input.
1388  * @param int $format Identifier of the text format to be used
1389  *            (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN)
1390  * @return string
1391  */
1392 function format_text_email($text, $format) {
1394     switch ($format) {
1396         case FORMAT_PLAIN:
1397             return $text;
1398             break;
1400         case FORMAT_WIKI:
1401             $text = wiki_to_html($text);
1402         /// This expression turns links into something nice in a text format. (Russell Jungwirth)
1403         /// From: http://php.net/manual/en/function.eregi-replace.php and simplified
1404             $text = eregi_replace('(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)','\\3 [ \\2 ]', $text);
1405             return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
1406             break;
1408         case FORMAT_HTML:
1409             return html_to_text($text);
1410             break;
1412         case FORMAT_MOODLE:
1413         case FORMAT_MARKDOWN:
1414         default:
1415             $text = eregi_replace('(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)','\\3 [ \\2 ]', $text);
1416             return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
1417             break;
1418     }
1421 /**
1422  * Given some text in HTML format, this function will pass it
1423  * through any filters that have been defined in $CFG->textfilterx
1424  * The variable defines a filepath to a file containing the
1425  * filter function.  The file must contain a variable called
1426  * $textfilter_function which contains the name of the function
1427  * with $courseid and $text parameters
1428  *
1429  * @param string $text The text to be passed through format filters
1430  * @param int $courseid ?
1431  * @return string
1432  * @todo Finish documenting this function
1433  */
1434 function filter_text($text, $courseid=NULL) {
1435     global $CFG;
1437     require_once($CFG->libdir.'/filterlib.php');
1438     if (!empty($CFG->textfilters)) {
1439         $textfilters = explode(',', $CFG->textfilters);
1440         foreach ($textfilters as $textfilter) {
1441             if (is_readable($CFG->dirroot .'/'. $textfilter .'/filter.php')) {
1442                 include_once($CFG->dirroot .'/'. $textfilter .'/filter.php');
1443                 $functionname = basename($textfilter).'_filter';
1444                 if (function_exists($functionname)) {
1445                     $text = $functionname($courseid, $text);
1446                 }
1447             }
1448         }
1449     }
1451     /// <nolink> tags removed for XHTML compatibility
1452     $text = str_replace('<nolink>', '', $text);
1453     $text = str_replace('</nolink>', '', $text);
1455     return $text;
1458 /**
1459  * Is the text marked as trusted?
1460  *
1461  * @param string $text text to be searched for TRUSTTEXT marker
1462  * @return boolean
1463  */
1464 function trusttext_present($text) {
1465     if (strpos($text, TRUSTTEXT) !== FALSE) {
1466         return true;
1467     } else {
1468         return false;
1469     }
1472 /**
1473  * This funtion MUST be called before the cleaning or any other
1474  * function that modifies the data! We do not know the origin of trusttext
1475  * in database, if it gets there in tweaked form we must not convert it
1476  * to supported form!!!
1477  *
1478  * Please be carefull not to use stripslashes on data from database
1479  * or twice stripslashes when processing data recieved from user.
1480  *
1481  * @param string $text text that may contain TRUSTTEXT marker
1482  * @return text without any TRUSTTEXT marker
1483  */
1484 function trusttext_strip($text) {
1485     global $CFG;
1487     while (true) { //removing nested TRUSTTEXT
1488         $orig = $text;
1489         $text = str_replace(TRUSTTEXT, '', $text);
1490         if (strcmp($orig, $text) === 0) {
1491             return $text;
1492         }
1493     }
1496 /**
1497  * Mark text as trusted, such text may contain any HTML tags because the
1498  * normal text cleaning will be bypassed.
1499  * Please make sure that the text comes from trusted user before storing
1500  * it into database!
1501  */
1502 function trusttext_mark($text) {
1503     global $CFG;
1504     if (!empty($CFG->enabletrusttext) and (strpos($text, TRUSTTEXT) === FALSE)) {
1505         return TRUSTTEXT.$text;
1506     } else {
1507         return $text;
1508     }
1510 function trusttext_after_edit(&$text, $context) {
1511     if (has_capability('moodle/site:trustcontent', $context)) {
1512         $text = trusttext_strip($text);
1513         $text = trusttext_mark($text);
1514     } else {
1515         $text = trusttext_strip($text);
1516     }
1519 function trusttext_prepare_edit(&$text, &$format, $usehtmleditor, $context) {
1520     global $CFG;
1522     $options = new object();
1523     $options->smiley = false;
1524     $options->filter = false;
1525     if (!empty($CFG->enabletrusttext)
1526          and has_capability('moodle/site:trustcontent', $context)
1527          and trusttext_present($text)) {
1528         $options->noclean = true;
1529     } else {
1530         $options->noclean = false;
1531     }
1532     $text = trusttext_strip($text);
1533     if ($usehtmleditor) {
1534         $text = format_text($text, $format, $options);
1535         $format = FORMAT_HTML;
1536     } else if (!$options->noclean){
1537         $text = clean_text($text, $format);
1538     }
1541 /**
1542  * Given raw text (eg typed in by a user), this function cleans it up
1543  * and removes any nasty tags that could mess up Moodle pages.
1544  *
1545  * @uses FORMAT_MOODLE
1546  * @uses FORMAT_PLAIN
1547  * @uses ALLOWED_TAGS
1548  * @param string $text The text to be cleaned
1549  * @param int $format Identifier of the text format to be used
1550  *            (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN)
1551  * @return string The cleaned up text
1552  */
1553 function clean_text($text, $format=FORMAT_MOODLE) {
1555     global $ALLOWED_TAGS;
1557     switch ($format) {
1558         case FORMAT_PLAIN:
1559             return $text;
1561         default:
1563         /// Fix non standard entity notations
1564             $text = preg_replace('/(&#[0-9]+)(;?)/', "\\1;", $text);
1565             $text = preg_replace('/(&#x[0-9a-fA-F]+)(;?)/', "\\1;", $text);
1567         /// Remove tags that are not allowed
1568             $text = strip_tags($text, $ALLOWED_TAGS);
1570         /// Clean up embedded scripts and , using kses
1571             $text = cleanAttributes($text);
1573         /// Remove script events
1574             $text = eregi_replace("([^a-z])language([[:space:]]*)=", "\\1Xlanguage=", $text);
1575             $text = eregi_replace("([^a-z])on([a-z]+)([[:space:]]*)=", "\\1Xon\\2=", $text);
1577             return $text;
1578     }
1581 /**
1582  * This function takes a string and examines it for HTML tags.
1583  * If tags are detected it passes the string to a helper function {@link cleanAttributes2()}
1584  *  which checks for attributes and filters them for malicious content
1585  *         17/08/2004              ::          Eamon DOT Costello AT dcu DOT ie
1586  *
1587  * @param string $str The string to be examined for html tags
1588  * @return string
1589  */
1590 function cleanAttributes($str){
1591     $result = preg_replace_callback(
1592             '%(<[^>]*(>|$)|>)%m', #search for html tags
1593             "cleanAttributes2",
1594             $str
1595             );
1596     return  $result;
1599 /**
1600  * This function takes a string with an html tag and strips out any unallowed
1601  * protocols e.g. javascript:
1602  * It calls ancillary functions in kses which are prefixed by kses
1603 *        17/08/2004              ::          Eamon DOT Costello AT dcu DOT ie
1604  *
1605  * @param array $htmlArray An array from {@link cleanAttributes()}, containing in its 1st
1606  *              element the html to be cleared
1607  * @return string
1608  */
1609 function cleanAttributes2($htmlArray){
1611     global $CFG, $ALLOWED_PROTOCOLS;
1612     require_once($CFG->libdir .'/kses.php');
1614     $htmlTag = $htmlArray[1];
1615     if (substr($htmlTag, 0, 1) != '<') {
1616         return '&gt;';  //a single character ">" detected
1617     }
1618     if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $htmlTag, $matches)) {
1619         return ''; // It's seriously malformed
1620     }
1621     $slash = trim($matches[1]); //trailing xhtml slash
1622     $elem = $matches[2];    //the element name
1623     $attrlist = $matches[3]; // the list of attributes as a string
1625     $attrArray = kses_hair($attrlist, $ALLOWED_PROTOCOLS);
1627     $attStr = '';
1628     foreach ($attrArray as $arreach) {
1629         $arreach['name'] = strtolower($arreach['name']);
1630         if ($arreach['name'] == 'style') {
1631             $value = $arreach['value'];
1632             while (true) {
1633                 $prevvalue = $value;
1634                 $value = kses_no_null($value);
1635                 $value = preg_replace("/\/\*.*\*\//Us", '', $value);
1636                 $value = kses_decode_entities($value);
1637                 $value = preg_replace('/(&#[0-9]+)(;?)/', "\\1;", $value);
1638                 $value = preg_replace('/(&#x[0-9a-fA-F]+)(;?)/', "\\1;", $value);
1639                 if ($value === $prevvalue) {
1640                     $arreach['value'] = $value;
1641                     break;
1642                 }
1643             }
1644             $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']);
1645             $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']);
1646         } else if ($arreach['name'] == 'href') {
1647             //Adobe Acrobat Reader XSS protection
1648             $arreach['value'] = preg_replace('/(\.(pdf|fdf|xfdf|xdp|xfd))[^a-z0-9_\.\-].*$/i', '$1', $arreach['value']);
1649         }
1650         $attStr .=  ' '.$arreach['name'].'="'.$arreach['value'].'"';
1651     }
1653     $xhtml_slash = '';
1654     if (preg_match('%/\s*$%', $attrlist)) {
1655         $xhtml_slash = ' /';
1656     }
1657     return '<'. $slash . $elem . $attStr . $xhtml_slash .'>';
1660 /**
1661  * Replaces all known smileys in the text with image equivalents
1662  *
1663  * @uses $CFG
1664  * @param string $text Passed by reference. The string to search for smily strings.
1665  * @return string
1666  */
1667 function replace_smilies(&$text) {
1668 ///
1669     global $CFG;
1671 /// this builds the mapping array only once
1672     static $runonce = false;
1673     static $e = array();
1674     static $img = array();
1675     static $emoticons = array(
1676         ':-)'  => 'smiley',
1677         ':)'   => 'smiley',
1678         ':-D'  => 'biggrin',
1679         ';-)'  => 'wink',
1680         ':-/'  => 'mixed',
1681         'V-.'  => 'thoughtful',
1682         ':-P'  => 'tongueout',
1683         'B-)'  => 'cool',
1684         '^-)'  => 'approve',
1685         '8-)'  => 'wideeyes',
1686         ':o)'  => 'clown',
1687         ':-('  => 'sad',
1688         ':('   => 'sad',
1689         '8-.'  => 'shy',
1690         ':-I'  => 'blush',
1691         ':-X'  => 'kiss',
1692         '8-o'  => 'surprise',
1693         'P-|'  => 'blackeye',
1694         '8-['  => 'angry',
1695         'xx-P' => 'dead',
1696         '|-.'  => 'sleepy',
1697         '}-]'  => 'evil',
1698         '(h)'  => 'heart',
1699         '(heart)'  => 'heart',
1700         '(y)'  => 'yes',
1701         '(n)'  => 'no',
1702         '(martin)'  => 'martin',
1703         '( )'  => 'egg'
1704         );
1706     if ($runonce == false) {  /// After the first time this is not run again
1707         foreach ($emoticons as $emoticon => $image){
1708             $alttext = get_string($image, 'pix');
1710             $e[] = $emoticon;
1711             $img[] = '<img alt="'. $alttext .'" width="15" height="15" src="'. $CFG->pixpath .'/s/'. $image .'.gif" />';
1712         }
1713         $runonce = true;
1714     }
1716     // Exclude from transformations all the code inside <script> tags
1717     // Needed to solve Bug 1185. Thanks to jouse 2001 detecting it. :-)
1718     // Based on code from glossary fiter by Williams Castillo.
1719     //       - Eloy
1721     // Detect all the <script> zones to take out
1722     $excludes = array();
1723     preg_match_all('/<script language(.+?)<\/script>/is',$text,$list_of_excludes);
1725     // Take out all the <script> zones from text
1726     foreach (array_unique($list_of_excludes[0]) as $key=>$value) {
1727         $excludes['<+'.$key.'+>'] = $value;
1728     }
1729     if ($excludes) {
1730         $text = str_replace($excludes,array_keys($excludes),$text);
1731     }
1733 /// this is the meat of the code - this is run every time
1734     $text = str_replace($e, $img, $text);
1736     // Recover all the <script> zones to text
1737     if ($excludes) {
1738         $text = str_replace(array_keys($excludes),$excludes,$text);
1739     }
1742 /**
1743  * Given plain text, makes it into HTML as nicely as possible.
1744  * May contain HTML tags already
1745  *
1746  * @uses $CFG
1747  * @param string $text The string to convert.
1748  * @param boolean $smiley Convert any smiley characters to smiley images?
1749  * @param boolean $para If true then the returned string will be wrapped in paragraph tags
1750  * @param boolean $newlines If true then lines newline breaks will be converted to HTML newline breaks.
1751  * @return string
1752  */
1754 function text_to_html($text, $smiley=true, $para=true, $newlines=true) {
1755 ///
1757     global $CFG;
1759 /// Remove any whitespace that may be between HTML tags
1760     $text = eregi_replace(">([[:space:]]+)<", "><", $text);
1762 /// Remove any returns that precede or follow HTML tags
1763     $text = eregi_replace("([\n\r])<", " <", $text);
1764     $text = eregi_replace(">([\n\r])", "> ", $text);
1766     convert_urls_into_links($text);
1768 /// Make returns into HTML newlines.
1769     if ($newlines) {
1770         $text = nl2br($text);
1771     }
1773 /// Turn smileys into images.
1774     if ($smiley) {
1775         replace_smilies($text);
1776     }
1778 /// Wrap the whole thing in a paragraph tag if required
1779     if ($para) {
1780         return '<p>'.$text.'</p>';
1781     } else {
1782         return $text;
1783     }
1786 /**
1787  * Given Markdown formatted text, make it into XHTML using external function
1788  *
1789  * @uses $CFG
1790  * @param string $text The markdown formatted text to be converted.
1791  * @return string Converted text
1792  */
1793 function markdown_to_html($text) {
1794     global $CFG;
1796     require_once($CFG->libdir .'/markdown.php');
1798     return Markdown($text);
1801 /**
1802  * Given HTML text, make it into plain text using external function
1803  *
1804  * @uses $CFG
1805  * @param string $html The text to be converted.
1806  * @return string
1807  */
1808 function html_to_text($html) {
1810     global $CFG;
1812     require_once($CFG->libdir .'/html2text.php');
1814     return html2text($html);
1817 /**
1818  * Given some text this function converts any URLs it finds into HTML links
1819  *
1820  * @param string $text Passed in by reference. The string to be searched for urls.
1821  */
1822 function convert_urls_into_links(&$text) {
1823 /// Make lone URLs into links.   eg http://moodle.com/
1824     $text = eregi_replace("([[:space:]]|^|\(|\[)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])",
1825                           "\\1<a href=\"\\2://\\3\\4\" target=\"_blank\">\\2://\\3\\4</a>", $text);
1827 /// eg www.moodle.com
1828     $text = eregi_replace("([[:space:]]|^|\(|\[)www\.([^[:space:]]*)([[:alnum:]#?/&=])",
1829                           "\\1<a href=\"http://www.\\2\\3\" target=\"_blank\">www.\\2\\3</a>", $text);
1832 /**
1833  * This function will highlight search words in a given string
1834  * It cares about HTML and will not ruin links.  It's best to use
1835  * this function after performing any conversions to HTML.
1836  * Function found here: http://forums.devshed.com/t67822/scdaa2d1c3d4bacb4671d075ad41f0854.html
1837  *
1838  * @param string $needle The string to search for
1839  * @param string $haystack The string to search for $needle in
1840  * @param int $case ?
1841  * @return string
1842  * @todo Finish documenting this function
1843  */
1844 function highlight($needle, $haystack, $case=0,
1845                     $left_string='<span class="highlight">', $right_string='</span>') {
1846     if (empty($needle)) {
1847         return $haystack;
1848     }
1850     //$list_of_words = eregi_replace("[^-a-zA-Z0-9&.']", " ", $needle);  // bug 3101
1851     $list_of_words = $needle;
1852     $list_array = explode(' ', $list_of_words);
1853     for ($i=0; $i<sizeof($list_array); $i++) {
1854         if (strlen($list_array[$i]) == 1) {
1855             $list_array[$i] = '';
1856         }
1857     }
1858     $list_of_words = implode(' ', $list_array);
1859     $list_of_words_cp = $list_of_words;
1860     $final = array();
1861     preg_match_all('/<(.+?)>/is',$haystack,$list_of_words);
1863     foreach (array_unique($list_of_words[0]) as $key=>$value) {
1864         $final['<|'.$key.'|>'] = $value;
1865     }
1867     $haystack = str_replace($final,array_keys($final),$haystack);
1868     $list_of_words_cp = eregi_replace(' +', '|', $list_of_words_cp);
1870     if ($list_of_words_cp{0}=='|') {
1871         $list_of_words_cp{0} = '';
1872     }
1873     if ($list_of_words_cp{strlen($list_of_words_cp)-1}=='|') {
1874         $list_of_words_cp{strlen($list_of_words_cp)-1}='';
1875     }
1877     $list_of_words_cp = trim($list_of_words_cp);
1879     if ($list_of_words_cp) {
1881       $list_of_words_cp = "(". $list_of_words_cp .")";
1883       if (!$case){
1884         $haystack = eregi_replace("$list_of_words_cp", "$left_string"."\\1"."$right_string", $haystack);
1885       } else {
1886         $haystack = ereg_replace("$list_of_words_cp", "$left_string"."\\1"."$right_string", $haystack);
1887       }
1888     }
1889     $haystack = str_replace(array_keys($final),$final,$haystack);
1891     return $haystack;
1894 /**
1895  * This function will highlight instances of $needle in $haystack
1896  * It's faster that the above function and doesn't care about
1897  * HTML or anything.
1898  *
1899  * @param string $needle The string to search for
1900  * @param string $haystack The string to search for $needle in
1901  * @return string
1902  */
1903 function highlightfast($needle, $haystack) {
1905     $parts = explode(moodle_strtolower($needle), moodle_strtolower($haystack));
1907     $pos = 0;
1909     foreach ($parts as $key => $part) {
1910         $parts[$key] = substr($haystack, $pos, strlen($part));
1911         $pos += strlen($part);
1913         $parts[$key] .= '<span class="highlight">'.substr($haystack, $pos, strlen($needle)).'</span>';
1914         $pos += strlen($needle);
1915     }
1917     return (join('', $parts));
1921 /// STANDARD WEB PAGE PARTS ///////////////////////////////////////////////////
1923 /**
1924  * Print a standard header
1925  *
1926  * @uses $USER
1927  * @uses $CFG
1928  * @uses $SESSION
1929  * @param string $title Appears at the top of the window
1930  * @param string $heading Appears at the top of the page
1931  * @param string $navigation Premade navigation string (for use as breadcrumbs links)
1932  * @param string $focus Indicates form element to get cursor focus on load eg  inputform.password
1933  * @param string $meta Meta tags to be added to the header
1934  * @param boolean $cache Should this page be cacheable?
1935  * @param string $button HTML code for a button (usually for module editing)
1936  * @param string $menu HTML code for a popup menu
1937  * @param boolean $usexml use XML for this page
1938  * @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc)
1939  * @param bool   $return If true, return the visible elements of the header instead of echoing them.
1940  */
1941 function print_header ($title='', $heading='', $navigation='', $focus='',
1942                        $meta='', $cache=true, $button='&nbsp;', $menu='',
1943                        $usexml=false, $bodytags='', $return=false) {
1945     global $USER, $CFG, $THEME, $SESSION, $ME, $SITE, $HTTPSPAGEREQUIRED;
1947 /// This makes sure that the header is never repeated twice on a page
1948     if (defined('HEADER_PRINTED')) {
1949         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().');
1950         return;
1951     }
1952     define('HEADER_PRINTED', 'true');
1955     global $COURSE;
1956     if (!empty($COURSE->lang)) {
1957         $CFG->courselang = $COURSE->lang;
1958         moodle_setlocale();
1959     }
1960     if (!empty($COURSE->theme)) {
1961         if (!empty($CFG->allowcoursethemes)) {
1962             $CFG->coursetheme = $COURSE->theme;
1963             theme_setup();
1964         }
1965     }
1967 /// We have to change some URLs in styles if we are in a $HTTPSPAGEREQUIRED page
1968     if (!empty($HTTPSPAGEREQUIRED)) {
1969         $CFG->themewww = str_replace('http:', 'https:', $CFG->themewww);
1970         $CFG->pixpath = str_replace('http:', 'https:', $CFG->pixpath);
1971         $CFG->modpixpath = str_replace('http:', 'https:', $CFG->modpixpath);
1972         foreach ($CFG->stylesheets as $key => $stylesheet) {
1973             $CFG->stylesheets[$key] = str_replace('http:', 'https:', $stylesheet);
1974         }
1975     }
1977 /// Add the required stylesheets
1978     $stylesheetshtml = '';
1979     foreach ($CFG->stylesheets as $stylesheet) {
1980         $stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1981     }
1982     $meta = $stylesheetshtml.$meta;
1984         
1985 /// Add the meta page from the themes if any were requested
1987     $metapage = '';
1989     if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) {
1990         ob_start();
1991         include_once($CFG->dirroot.'/theme/standard/meta.php');
1992         $metapage .= ob_get_contents();
1993         ob_end_clean();
1994     }
1996     if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) {
1997         ob_start();
1998         include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php');
1999         $metapage .= ob_get_contents();
2000         ob_end_clean();
2001     }
2003     if (!isset($THEME->metainclude) || $THEME->metainclude) {
2004         ob_start();
2005         include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php');
2006         $metapage .= ob_get_contents();
2007         ob_end_clean();
2008     }
2010     $meta = $meta."\n".$metapage;
2014     if ($navigation == 'home') {
2015         $home = true;
2016         $navigation = '';
2017     } else {
2018         $home = false;
2019     }
2021 /// This is another ugly hack to make navigation elements available to print_footer later
2022     $THEME->title      = $title;
2023     $THEME->heading    = $heading;
2024     $THEME->navigation = $navigation;
2025     $THEME->button     = $button;
2026     $THEME->menu       = $menu;
2027     $navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : '';
2029     if ($button == '') {
2030         $button = '&nbsp;';
2031     }
2033     if (!$menu and $navigation) {
2034         if (empty($CFG->loginhttps)) {
2035             $wwwroot = $CFG->wwwroot;
2036         } else {
2037             $wwwroot = str_replace('http:','https:',$CFG->wwwroot);
2038         }
2039         $menu = user_login_string($COURSE);
2040     }
2042     if (isset($SESSION->justloggedin)) {
2043         unset($SESSION->justloggedin);
2044         if (!empty($CFG->displayloginfailures)) {
2045             if (!empty($USER->username) and $USER->username != 'guest') {
2046                 if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) {
2047                     $menu .= '&nbsp;<font size="1">';
2048                     if (empty($count->accounts)) {
2049                         $menu .= get_string('failedloginattempts', '', $count);
2050                     } else {
2051                         $menu .= get_string('failedloginattemptsall', '', $count);
2052                     }
2053                     if (has_capability('moodle/site:viewreports', get_context_instance(CONTEXT_SYSTEM, SITEID))) {
2054                         $menu .= ' (<a href="'.$CFG->wwwroot.'/course/report/log/index.php'.
2055                                              '?chooselog=1&amp;id=1&amp;modid=site_errors">'.get_string('logs').'</a>)';
2056                     }
2057                     $menu .= '</font>';
2058                 }
2059             }
2060         }
2061     }
2064     $meta = '<meta http-equiv="content-type" content="text/html; charset=utf-8" />'. "\n". $meta ."\n";
2065     if (!$usexml) {
2066         @header('Content-type: text/html; charset=utf-8');
2067     }
2069     if ( get_string('thisdirection') == 'rtl' ) {
2070         $direction = ' dir="rtl"';
2071     } else {
2072         $direction = ' dir="ltr"';
2073     }
2074     //Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag.
2075     $language = str_replace('_utf8','',$CFG->lang);
2076     $direction .= ' lang="'.$language.'" xml:lang="'.$language.'"';
2078     if ($cache) {  // Allow caching on "back" (but not on normal clicks)
2079         @header('Cache-Control: private, pre-check=0, post-check=0, max-age=0');
2080         @header('Pragma: no-cache');
2081         @header('Expires: ');
2082     } else {       // Do everything we can to always prevent clients and proxies caching
2083         @header('Cache-Control: no-store, no-cache, must-revalidate');
2084         @header('Cache-Control: post-check=0, pre-check=0', false);
2085         @header('Pragma: no-cache');
2086         @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
2087         @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
2089         $meta .= "\n<meta http-equiv=\"pragma\" content=\"no-cache\" />";
2090         $meta .= "\n<meta http-equiv=\"expires\" content=\"0\" />";
2091     }
2092     @header('Accept-Ranges: none');
2094     if (empty($usexml)) {
2095         $direction =  ' xmlns="http://www.w3.org/1999/xhtml"'. $direction;  // See debug_header
2096     } else {
2097         $currentlanguage = current_language();
2098         $mathplayer = preg_match("/MathPlayer/i", $_SERVER['HTTP_USER_AGENT']);
2099         if(!$mathplayer) {
2100             header('Content-Type: application/xhtml+xml');
2101         }
2102         echo '<?xml version="1.0" ?>'."\n";
2103         if (!empty($CFG->xml_stylesheets)) {
2104             $stylesheets = explode(';', $CFG->xml_stylesheets);
2105             foreach ($stylesheets as $stylesheet) {
2106                 echo '<?xml-stylesheet type="text/xsl" href="'. $CFG->wwwroot .'/'. $stylesheet .'" ?>' . "\n";
2107             }
2108         }
2109         echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1';
2110         if (!empty($CFG->xml_doctype_extra)) {
2111             echo ' plus '. $CFG->xml_doctype_extra;
2112         }
2113         echo '//' . strtoupper($currentlanguage) . '" "'. $CFG->xml_dtd .'">'."\n";
2114         $direction = " xmlns=\"http://www.w3.org/1999/xhtml\"
2115                        xmlns:math=\"http://www.w3.org/1998/Math/MathML\"
2116                        xmlns:xlink=\"http://www.w3.org/1999/xlink\"
2117                        $direction";
2118         if($mathplayer) {
2119             $meta .= '<object id="mathplayer" classid="clsid:32F66A20-7614-11D4-BD11-00104BD3F987">' . "\n";
2120             $meta .= '<!--comment required to prevent this becoming an empty tag-->'."\n";
2121             $meta .= '</object>'."\n";
2122             $meta .= '<?import namespace="math" implementation="#mathplayer" ?>' . "\n";
2123         }
2124     }
2126     // Clean up the title
2128     $title = str_replace('"', '&quot;', $title);
2129     $title = strip_tags($title);
2131     // Create class and id for this page
2133     page_id_and_class($pageid, $pageclass);
2135     $pageclass .= ' course-'.$COURSE->id;
2137     if (($pageid != 'site-index') && ($pageid != 'course-view') && 
2138         ($pageid != 'admin-settings')) {
2139         $pageclass .= ' nocoursepage';
2140     }
2142     if (!isloggedin()) {
2143         $pageclass .= ' notloggedin';
2144     }
2146     if (!empty($USER->editing)) {
2147         $pageclass .= ' editing';
2148     }
2150     if (!empty($CFG->blocksdrag)) {
2151         $pageclass .= ' drag';
2152     }
2154     $bodytags .= ' class="'.$pageclass.'" id="'.$pageid.'"';
2156     ob_start();
2157     include($CFG->header);
2158     $output = ob_get_contents();
2159     ob_end_clean();
2161     $output = force_strict_header($output);
2163     if (!empty($CFG->messaging)) {
2164         $output .= message_popup_window();
2165     }
2167     if ($return) {
2168         return $output;
2169     } else {
2170         echo $output;
2171     }
2174 /**
2175  * Debugging aid: serve page as 'application/xhtml+xml' where possible,
2176  *     and substitute the XHTML strict document type.
2177  *     Note, requires the 'xmlns' fix in function print_header above.
2178  *     See:  http://tracker.moodle.org/browse/MDL-7883
2179  * TODO:
2180  */
2181 function force_strict_header($output) {
2182     global $CFG;
2183     $strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
2184     $xsl = '/lib/xhtml.xsl';
2186     if (!headers_sent() && debugging(NULL, DEBUG_DEVELOPER)) {   // In developer debugging, the browser will barf
2187         $ctype = 'Content-Type: ';
2188         $prolog= "<?xml version='1.0' encoding='utf-8'?>\n";
2190         if (isset($_SERVER['HTTP_ACCEPT'])
2191             && false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml')) {
2192             //|| false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') //Safari "Entity 'copy' not defined".
2193             // Firefox et al.
2194             $ctype .= 'application/xhtml+xml';
2195             $prolog .= "<!--\n  DEBUG: $ctype \n-->\n";
2197         } else if (file_exists($CFG->dirroot.$xsl)
2198             && preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
2199             // XSL hack for IE 5+ on Windows.
2200             //$www_xsl = preg_replace('/(http:\/\/.+?\/).*/', '', $CFG->wwwroot) .$xsl;
2201             $www_xsl = $CFG->wwwroot .$xsl;
2202             $ctype .= 'application/xml';
2203             $prolog .= "<?xml-stylesheet type='text/xsl' href='$www_xsl'?>\n";
2204             $prolog .= "<!--\n  DEBUG: $ctype \n-->\n";
2206         } else {
2207             //ELSE: Mac/IE, old/non-XML browsers.
2208             $ctype .= 'text/html';
2209             $prolog = '';
2210         }
2211         @header($ctype.'; charset=utf-8');
2212         $output = $prolog . $output;
2214         // Test parser error-handling.
2215         if (isset($_GET['error'])) {
2216             $output .= "__ TEST: XML well-formed error < __\n";
2217         }
2218     }
2220     $output = preg_replace('/(<!DOCTYPE.+?>)/s', $strict, $output);   // Always change the DOCTYPE to Strict 1.0
2222     return $output;
2227 /**
2228  * This version of print_header is simpler because the course name does not have to be
2229  * provided explicitly in the strings. It can be used on the site page as in courses
2230  * Eventually all print_header could be replaced by print_header_simple
2231  *
2232  * @param string $title Appears at the top of the window
2233  * @param string $heading Appears at the top of the page
2234  * @param string $navigation Premade navigation string (for use as breadcrumbs links)
2235  * @param string $focus Indicates form element to get cursor focus on load eg  inputform.password
2236  * @param string $meta Meta tags to be added to the header
2237  * @param boolean $cache Should this page be cacheable?
2238  * @param string $button HTML code for a button (usually for module editing)
2239  * @param string $menu HTML code for a popup menu
2240  * @param boolean $usexml use XML for this page
2241  * @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc)
2242  * @param bool   $return If true, return the visible elements of the header instead of echoing them.
2243  */
2244 function print_header_simple($title='', $heading='', $navigation='', $focus='', $meta='',
2245                        $cache=true, $button='&nbsp;', $menu='', $usexml=false, $bodytags='', $return=false) {
2247     global $COURSE, $CFG;
2249     $shortname ='';
2250     if ($COURSE->id != SITEID) {
2251         $shortname = '<a href="'.$CFG->wwwroot.'/course/view.php?id='. $COURSE->id .'">'. $COURSE->shortname .'</a> ->';
2252     }
2254     $output = print_header($COURSE->shortname .': '. $title, $COURSE->fullname .' '. $heading, $shortname .' '. $navigation, $focus, $meta,
2255                            $cache, $button, $menu, $usexml, $bodytags, true);
2257     if ($return) {
2258         return $output;
2259     } else {
2260         echo $output;
2261     }
2265 /**
2266  * Can provide a course object to make the footer contain a link to
2267  * to the course home page, otherwise the link will go to the site home
2268  *
2269  * @uses $CFG
2270  * @uses $USER
2271  * @param course $course {@link $COURSE} object containing course information
2272  * @param ? $usercourse ?
2273  * @todo Finish documenting this function
2274  */
2275 function print_footer($course=NULL, $usercourse=NULL, $return=false) {
2276     global $USER, $CFG, $THEME;
2278 /// Course links
2279     if ($course) {
2280         if (is_string($course) && $course == 'none') {          // Don't print any links etc
2281             $homelink = '';
2282             $loggedinas = '';
2283             $home  = false;
2284         } else if (is_string($course) && $course == 'home') {   // special case for site home page - please do not remove
2285             $course = get_site();
2286             $homelink  = '<div class="sitelink">'.
2287                '<a title="moodle '. $CFG->release .' ('. $CFG->version .')" href="http://moodle.org/">'.
2288                '<br /><img style="width:100px;height:30px" src="pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
2289             $home  = true;
2290         } else {
2291             $homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot.
2292                         '/course/view.php?id='.$course->id.'">'.$course->shortname.'</a></div>';
2293             $home  = false;
2294         }
2295     } else {
2296         $course = get_site();  // Set course as site course by default
2297         $homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot.'/">'.get_string('home').'</a></div>';
2298         $home  = false;
2299     }
2301 /// Set up some other navigation links (passed from print_header by ugly hack)
2302     $menu        = isset($THEME->menu) ? str_replace('navmenu', 'navmenufooter', $THEME->menu) : '';
2303     $title       = isset($THEME->title) ? $THEME->title : '';
2304     $button      = isset($THEME->button) ? $THEME->button : '';
2305     $heading     = isset($THEME->heading) ? $THEME->heading : '';
2306     $navigation  = isset($THEME->navigation) ? $THEME->navigation : '';
2307     $navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : '';
2310 /// Set the user link if necessary
2311     if (!$usercourse and is_object($course)) {
2312         $usercourse = $course;
2313     }
2315     if (!isset($loggedinas)) {
2316         $loggedinas = user_login_string($usercourse, $USER);
2317     }
2319     if ($loggedinas == $menu) {
2320         $menu = '';
2321     }
2323 /// Provide some performance info if required
2324     $performanceinfo = '';
2325     if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
2326         $perf = get_performance_info();
2327         if (defined('MDL_PERFTOLOG')) {
2328             error_log("PERF: " . $perf['txt']);
2329         }
2330         if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
2331             $performanceinfo = $perf['html'];
2332         }
2333     }
2336 /// Include the actual footer file
2338     ob_start();
2339     include($CFG->footer);
2340     $output = ob_get_contents();
2341     ob_end_clean();
2343     if ($return) {
2344         return $output;
2345     } else {
2346         echo $output;
2347     }
2350 /**
2351  * Returns the name of the current theme
2352  *
2353  * @uses $CFG
2354  * @param $USER
2355  * @param $SESSION
2356  * @return string
2357  */
2358 function current_theme() {
2359     global $CFG, $USER, $SESSION, $course;
2361     if (!empty($CFG->pagetheme)) {  // Page theme is for special page-only themes set by code
2362         return $CFG->pagetheme;
2364     } else if (!empty($CFG->coursetheme) and !empty($CFG->allowcoursethemes)) {  // Course themes override others
2365         return $CFG->coursetheme;
2367     } else if (!empty($SESSION->theme)) {    // Session theme can override other settings
2368         return $SESSION->theme;
2370     } else if (!empty($USER->theme) and !empty($CFG->allowuserthemes)) {    // User theme can override site theme
2371         return $USER->theme;
2373     } else {
2374         return $CFG->theme;
2375     }
2379 /**
2380  * This function is called by stylesheets to set up the header
2381  * approriately as well as the current path
2382  *
2383  * @uses $CFG
2384  * @param int $lastmodified ?
2385  * @param int $lifetime ?
2386  * @param string $thename ?
2387  */
2388 function style_sheet_setup($lastmodified=0, $lifetime=300, $themename='', $forceconfig='', $lang='') {
2390     global $CFG, $THEME;
2392     // Fix for IE6 caching - we don't want the filemtime('styles.php'), instead use now.
2393     $lastmodified = time();
2395     header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastmodified) . ' GMT');
2396     header('Expires: ' . gmdate("D, d M Y H:i:s", time() + $lifetime) . ' GMT');
2397     header('Cache-Control: max-age='. $lifetime);
2398     header('Pragma: ');
2399     header('Content-type: text/css');  // Correct MIME type
2401     $DEFAULT_SHEET_LIST = array('styles_layout', 'styles_fonts', 'styles_color');
2403     if (empty($themename)) {
2404         $themename = current_theme();  // So we have something.  Normally not needed.
2405     } else {
2406         $themename = clean_param($themename, PARAM_SAFEDIR);
2407     }
2409     if (!empty($forceconfig)) {        // Page wants to use the config from this theme instead
2410         unset($THEME);
2411         include($CFG->themedir.'/'.$forceconfig.'/'.'config.php');
2412     }
2414 /// If this is the standard theme calling us, then find out what sheets we need
2416     if ($themename == 'standard') {
2417         if (!isset($THEME->standardsheets) or $THEME->standardsheets === true) { // Use all the sheets we have
2418             $THEME->sheets = $DEFAULT_SHEET_LIST;
2419         } else if (empty($THEME->standardsheets)) {                              // We can stop right now!
2420             echo "/***** Nothing required from this stylesheet by main theme *****/\n\n";
2421             exit;
2422         } else {                                                                 // Use the provided subset only
2423             $THEME->sheets = $THEME->standardsheets;
2424         }
2426 /// If we are a parent theme, then check for parent definitions
2428     } else if (!empty($THEME->parent) && $themename == $THEME->parent) {
2429         if (!isset($THEME->parentsheets) or $THEME->parentsheets === true) {     // Use all the sheets we have
2430             $THEME->sheets = $DEFAULT_SHEET_LIST;
2431         } else if (empty($THEME->parentsheets)) {                                // We can stop right now!
2432             echo "/***** Nothing required from this stylesheet by main theme *****/\n\n";
2433             exit;
2434         } else {                                                                 // Use the provided subset only
2435             $THEME->sheets = $THEME->parentsheets;
2436         }
2437     }
2439 /// Work out the last modified date for this theme
2441     foreach ($THEME->sheets as $sheet) {
2442         if (file_exists($CFG->themedir.'/'.$themename.'/'.$sheet.'.css')) {
2443             $sheetmodified = filemtime($CFG->themedir.'/'.$themename.'/'.$sheet.'.css');
2444             if ($sheetmodified > $lastmodified) {
2445                 $lastmodified = $sheetmodified;
2446             }
2447         }
2448     }
2451 /// Get a list of all the files we want to include
2452     $files = array();
2454     foreach ($THEME->sheets as $sheet) {
2455         $files[] = array($CFG->themedir, $themename.'/'.$sheet.'.css');
2456     }
2458     if ($themename == 'standard') {          // Add any standard styles included in any modules
2459         if (!empty($THEME->modsheets)) {     // Search for styles.php within activity modules
2460             if ($mods = get_list_of_plugins('mod')) {
2461                 foreach ($mods as $mod) {
2462                     if (file_exists($CFG->dirroot.'/mod/'.$mod.'/styles.php')) {
2463                         $files[] = array($CFG->dirroot, '/mod/'.$mod.'/styles.php');
2464                     }
2465                 }
2466             }
2467         }
2469         if (!empty($THEME->blocksheets)) {     // Search for styles.php within block modules
2470             if ($mods = get_list_of_plugins('blocks')) {
2471                 foreach ($mods as $mod) {
2472                     if (file_exists($CFG->dirroot.'/blocks/'.$mod.'/styles.php')) {
2473                         $files[] = array($CFG->dirroot, '/blocks/'.$mod.'/styles.php');
2474                     }
2475                 }
2476             }
2477         }
2479         if (!isset($THEME->courseformatsheets) || $THEME->courseformatsheets) { // Search for styles.php in course formats
2480             if ($mods = get_list_of_plugins('format','',$CFG->dirroot.'/course')) {
2481                 foreach ($mods as $mod) {
2482                     if (file_exists($CFG->dirroot.'/course/format/'.$mod.'/styles.php')) {
2483                         $files[] = array($CFG->dirroot, '/course/format/'.$mod.'/styles.php');
2484                     }
2485                 }
2486             }
2487         }
2489         if (!empty($THEME->langsheets)) {     // Search for styles.php within the current language
2490             if (file_exists($CFG->dirroot.'/lang/'.$lang.'/styles.php')) {
2491                 $files[] = array($CFG->dirroot, '/lang/'.$lang.'/styles.php');
2492             }
2493         }
2494     }
2497     if ($files) {
2498     /// Produce a list of all the files first
2499         echo '/**************************************'."\n";
2500         echo ' * THEME NAME: '.$themename."\n *\n";
2501         echo ' * Files included in this sheet:'."\n *\n";
2502         foreach ($files as $file) {
2503             echo ' *   '.$file[1]."\n";
2504         }
2505         echo ' **************************************/'."\n\n";
2508         /// check if csscobstants is set
2509         if (!empty($THEME->cssconstants)) {
2510             require_once("$CFG->libdir/cssconstants.php");
2511             /// Actually collect all the files in order.
2512             $css = '';
2513             foreach ($files as $file) {
2514                 $css .= '/***** '.$file[1].' start *****/'."\n\n";
2515                 $css .= file_get_contents($file[0].'/'.$file[1]);
2516                 $ccs .= '/***** '.$file[1].' end *****/'."\n\n";
2517             }
2518             /// replace css_constants with their values
2519             echo replace_cssconstants($css);
2520         } else {
2521         /// Actually output all the files in order.
2522             foreach ($files as $file) {
2523                 echo '/***** '.$file[1].' start *****/'."\n\n";
2524                 @include_once($file[0].'/'.$file[1]);
2525                 echo '/***** '.$file[1].' end *****/'."\n\n";
2526             }
2527         }
2528     }
2530     return $CFG->themewww.'/'.$themename;   // Only to help old themes (1.4 and earlier)
2534 function theme_setup($theme = '', $params=NULL) {
2535 /// Sets up global variables related to themes
2537     global $CFG, $THEME, $SESSION, $USER;
2539     if (empty($theme)) {
2540         $theme = current_theme();
2541     }
2543 /// If the theme doesn't exist for some reason then revert to standardwhite
2544     if (!file_exists($CFG->themedir .'/'. $theme .'/config.php')) {
2545         $CFG->theme = $theme = 'standardwhite';
2546     }
2548 /// Load up the theme config
2549     $THEME = NULL;   // Just to be sure
2550     include($CFG->themedir .'/'. $theme .'/config.php');  // Main config for current theme
2552 /// Put together the parameters
2553     if (!$params) {
2554         $params = array();
2555     }
2556     if ($theme != $CFG->theme) {
2557         $params[] = 'forceconfig='.$theme;
2558     }
2560 /// Force language too if required
2561     if (!empty($THEME->langsheets)) {
2562         $params[] = 'lang='.current_language();
2563     }
2565 /// Convert params to string
2566     if ($params) {
2567         $paramstring = '?'.implode('&', $params);
2568     } else {
2569         $paramstring = '';
2570     }
2572 /// Set up image paths
2573     if (empty($THEME->custompix)) {    // Could be set in the above file
2574         $CFG->pixpath = $CFG->wwwroot .'/pix';
2575         $CFG->modpixpath = $CFG->wwwroot .'/mod';
2576     } else {
2577         $CFG->pixpath = $CFG->themewww .'/'. $theme .'/pix';
2578         $CFG->modpixpath = $CFG->themewww .'/'. $theme .'/pix/mod';
2579     }
2581 /// Header and footer paths
2582     $CFG->header = $CFG->themedir .'/'. $theme .'/header.html';
2583     $CFG->footer = $CFG->themedir .'/'. $theme .'/footer.html';
2585 /// Define stylesheet loading order
2586     $CFG->stylesheets = array();
2587     if ($theme != 'standard') {    /// The standard sheet is always loaded first
2588         $CFG->stylesheets[] = $CFG->themewww.'/standard/styles.php'.$paramstring;
2589     }
2590     if (!empty($THEME->parent)) {  /// Parent stylesheets are loaded next
2591         $CFG->stylesheets[] = $CFG->themewww.'/'.$THEME->parent.'/styles.php'.$paramstring;
2592     }
2593     $CFG->stylesheets[] = $CFG->themewww.'/'.$theme.'/styles.php'.$paramstring;
2598 /**
2599  * Returns text to be displayed to the user which reflects their login status
2600  *
2601  * @uses $CFG
2602  * @uses $USER
2603  * @param course $course {@link $COURSE} object containing course information
2604  * @param user $user {@link $USER} object containing user information
2605  * @return string
2606  */
2607 function user_login_string($course=NULL, $user=NULL) {
2608     global $USER, $CFG, $SITE;
2610     if (empty($user) and !empty($USER->id)) {
2611         $user = $USER;
2612     }
2614     if (empty($course)) {
2615         $course = $SITE;
2616     }
2618     if (isset($user->realuser)) {
2619         if ($realuser = get_record('user', 'id', $user->realuser)) {
2620             $fullname = fullname($realuser, true);
2621             $realuserinfo = " [<a $CFG->frametarget
2622             href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&amp;return=1\">$fullname</a>] ";
2623         }
2624     } else {
2625         $realuserinfo = '';
2626     }
2628     if (empty($CFG->loginhttps)) {
2629         $wwwroot = $CFG->wwwroot;
2630     } else {
2631         $wwwroot = str_replace('http:','https:',$CFG->wwwroot);
2632     }
2634     if (empty($course->id)) {
2635         // $course->id is not defined during installation
2636         return '';
2637     } else if (isset($user->id) and $user->id) {
2638         $context = get_context_instance(CONTEXT_COURSE, $course->id);
2640         $fullname = fullname($user, true);
2641         $username = "<a $CFG->frametarget href=\"$CFG->wwwroot/user/view.php?id=$user->id&amp;course=$course->id\">$fullname</a>";
2642         if (is_mnet_remote_user($user) and $idprovider = get_record('mnet_host', 'id', $user->mnethostid)) {
2643             $username .= " from <a $CFG->frametarget href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>";
2644         }
2645         if (isset($user->username) && $user->username == 'guest') {
2646             $loggedinas = $realuserinfo.get_string('loggedinasguest').
2647                       " (<a $CFG->frametarget href=\"$wwwroot/login/index.php\">".get_string('login').'</a>)';
2648         } else if (!empty($user->switchrole[$context->id])) {
2649             $rolename = '';
2650             if ($role = get_record('role', 'id', $user->switchrole[$context->id])) {
2651                 $rolename = ': '.format_string($role->name);
2652             }
2653             $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename.
2654                       " (<a $CFG->frametarget
2655                       href=\"$CFG->wwwroot/course/view.php?id=$course->id&amp;switchrole=0&amp;sesskey=".sesskey()."\">".get_string('switchrolereturn').'</a>)';
2656         } else {
2657             $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username).' '.
2658                       " (<a $CFG->frametarget href=\"$CFG->wwwroot/login/logout.php\">".get_string('logout').'</a>)';
2659         }
2660     } else {
2661         $loggedinas = get_string('loggedinnot', 'moodle').
2662                       " (<a $CFG->frametarget href=\"$wwwroot/login/index.php\">".get_string('login').'</a>)';
2663     }
2664     return '<div class="logininfo">'.$loggedinas.'</div>';
2667 /**
2668  * Tests whether $THEME->rarrow, $THEME->larrow have been set (theme/-/config.php).
2669  * If not it applies sensible defaults.
2670  *
2671  * Accessibility: right and left arrow Unicode characters for breadcrumb, calendar,
2672  * search forum block, etc. Important: these are 'silent' in a screen-reader
2673  * (unlike &gt; &raquo;), and must be accompanied by text.
2674  * @uses $THEME
2675  */
2676 function check_theme_arrows() {
2677     global $THEME;
2679     if (!isset($THEME->rarrow) and !isset($THEME->larrow)) {
2680         // Default, looks good in Win XP/IE 6, Win/Firefox 1.5, Win/Netscape 8...
2681         // Also OK in Win 9x/2K/IE 5.x
2682         $THEME->rarrow = '&#x25BA;';
2683         $THEME->larrow = '&#x25C4;';
2684         $uagent = $_SERVER['HTTP_USER_AGENT'];
2685         if (false !== strpos($uagent, 'Opera')
2686             || false !== strpos($uagent, 'Mac')) {
2687             // Looks good in Win XP/Mac/Opera 8/9, Mac/Firefox 2, Camino, Safari.
2688             // Not broken in Mac/IE 5, Mac/Netscape 7 (?).
2689             $THEME->rarrow = '&#x25B6;';
2690             $THEME->larrow = '&#x25C0;';
2691         }
2692         elseif (false !== strpos($uagent, 'Konqueror')) {
2693             $THEME->rarrow = '&rarr;';
2694             $THEME->larrow = '&larr;';
2695         }
2696         elseif (isset($_SERVER['HTTP_ACCEPT_CHARSET'])
2697             && false === stripos($_SERVER['HTTP_ACCEPT_CHARSET'], 'utf-8')) {
2698             // (Win/IE 5 doesn't set ACCEPT_CHARSET, but handles Unicode.)
2699             // To be safe, non-Unicode browsers!
2700             $THEME->rarrow = '&gt;';
2701             $THEME->larrow = '&lt;';
2702         }
2703     }
2706 /**
2707  * Prints breadcrumb trail of links, called in theme/-/header.html
2708  *
2709  * @uses $CFG
2710  * @param string $navigation The breadcrumb navigation string to be printed
2711  * @param string $separator The breadcrumb trail separator. The default 0 leads to the use
2712  *  of $THEME->rarrow, themes could use '&rarr;', '/', or '' for a style-sheet solution.
2713  * @param boolean $return False to echo the breadcrumb string (default), true to return it.
2714  */
2715 function print_navigation ($navigation, $separator=0, $return=false) {
2716     global $CFG, $THEME;
2717     $output = '';
2719     check_theme_arrows();
2720     if (0 === $separator) {
2721         $separator = $THEME->rarrow;
2722     }
2723     if (!empty($separator)) {
2724         $separator = '<span class="sep">'. $separator .'</span>';
2725     }
2727     if ($navigation) {
2728         //Accessibility: breadcrumb links now in a list, &raquo; replaced with a 'silent' character.
2729         $nav_text = get_string('youarehere','access');
2730         $output .= '<h2 class="accesshide">'.$nav_text."</h2><ul>\n";
2731         if (! $site = get_site()) {
2732             $site->shortname = get_string('home');
2733         }
2734         $navigation = "<li>$separator ". str_replace('->', "</li>\n<li>$separator", $navigation) ."</li>\n";
2735         $output .= '<li class="first"><a '.$CFG->frametarget.' href="'. $CFG->wwwroot.((!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM, SITEID)) && !empty($USER->id) && !empty($CFG->mymoodleredirect) && !isguest())
2736                                                                        ? '/my' : '') .'/">'. $site->shortname ."</a></li>\n". $navigation;
2737         $output .= "</ul>\n";
2738     }
2740     if ($return) {
2741         return $output;
2742     } else {
2743         echo $output;
2744     }
2747 /**
2748  * Prints a string in a specified size  (retained for backward compatibility)
2749  *
2750  * @param string $text The text to be displayed
2751  * @param int $size The size to set the font for text display.
2752  */
2753 function print_headline($text, $size=2, $return=false) {
2754     $output = print_heading($text, '', $size, true);
2755     if ($return) {
2756         return $output;
2757     } else {
2758         echo $output;
2759     }
2762 /**
2763  * Prints text in a format for use in headings.
2764  *
2765  * @param string $text The text to be displayed
2766  * @param string $align The alignment of the printed paragraph of text
2767  * @param int $size The size to set the font for text display.
2768  */
2769 function print_heading($text, $align='', $size=2, $class='main', $return=false) {
2770     if ($align) {
2771         $align = ' style="text-align:'.$align.';"';
2772     }
2773     if ($class) {
2774         $class = ' class="'.$class.'"';
2775     }
2776     $output = "<h$size $align $class>".stripslashes_safe($text)."</h$size>";
2778     if ($return) {
2779         return $output;
2780     } else {
2781         echo $output;
2782     }
2785 /**
2786  * Centered heading with attached help button (same title text)
2787  * and optional icon attached
2788  *
2789  * @param string $text The text to be displayed
2790  * @param string $helppage The help page to link to
2791  * @param string $module The module whose help should be linked to
2792  * @param string $icon Image to display if needed
2793  */
2794 function print_heading_with_help($text, $helppage, $module='moodle', $icon='', $return=false) {
2795     $output = '';
2796     $output .= '<h2 class="main help">'.$icon.stripslashes_safe($text);
2797     $output .= helpbutton($helppage, $text, $module, true, false, '', true);
2798     $output .= '</h2>';
2800     if ($return) {
2801         return $output;
2802     } else {
2803         echo $output;
2804     }
2808 function print_heading_block($heading, $class='', $return=false) {
2809     //Accessibility: 'headingblock' is now H1, see theme/standard/styles_*.css: ??
2810     $output = '<h2 class="headingblock header '.$class.'">'.stripslashes($heading).'</h2>';
2812     if ($return) {
2813         return $output;
2814     } else {
2815         echo $output;
2816     }
2820 /**
2821  * Print a link to continue on to another page.
2822  *
2823  * @uses $CFG
2824  * @param string $link The url to create a link to.
2825  */
2826 function print_continue($link, $return=false) {
2828     global $CFG;
2830     // in case we are logging upgrade in admin/index.php stop it
2831     if (function_exists('upgrade_log_finish')) {
2832         upgrade_log_finish();
2833     }
2835     $output = '';
2837     if ($link == '') {
2838         if (!empty($_SERVER['HTTP_REFERER'])) {
2839             $link = $_SERVER['HTTP_REFERER'];
2840             $link = str_replace('&', '&amp;', $link); // make it valid XHTML 
2841         } else {
2842             $link = $CFG->wwwroot .'/';
2843         }
2844     }
2846     $output .= '<div class="continuebutton">';
2848     $output .= print_single_button($link, NULL, get_string('continue'), 'post', $CFG->framename, true);
2849     $output .= '</div>'."\n";
2851     if ($return) {
2852         return $output;
2853     } else {
2854         echo $output;
2855     }
2859 /**
2860  * Print a message in a standard themed box.
2861  * Replaces print_simple_box (see deprecatedlib.php)
2862  *
2863  * @param string $message, the content of the box
2864  * @param string $classes, space-separated class names.
2865  * @param string $ids, space-separated id names.
2866  * @param boolean $return, return as string or just print it
2867  */
2868 function print_box($message, $classes='generalbox', $ids='', $return=false) {
2870     $output  = print_box_start($classes, $ids, true);
2871     $output .= stripslashes_safe($message);
2872     $output .= print_box_end(true);
2874     if ($return) {
2875         return $output;
2876     } else {
2877         echo $output;
2878     }
2881 /**
2882  * Starts a box using divs
2883  * Replaces print_simple_box_start (see deprecatedlib.php)
2884  *
2885  * @param string $classes, space-separated class names.
2886  * @param string $ids, space-separated id names.
2887  * @param boolean $return, return as string or just print it
2888  */
2889 function print_box_start($classes='generalbox', $ids='', $return=false) {
2890     $output = '';
2892     if ($ids) {
2893         $ids = ' id="'.$ids.'"';
2894     }
2896     $output .= '<div'.$ids.' class="box '.$classes.'">';
2898     if ($return) {
2899         return $output;
2900     } else {
2901         echo $output;
2902     }
2906 /**
2907  * Simple function to end a box (see above)
2908  * Replaces print_simple_box_end (see deprecatedlib.php)
2909  *
2910  * @param boolean $return, return as string or just print it
2911  */
2912 function print_box_end($return=false) {
2913     $output = '</div>';
2914     if ($return) {
2915         return $output;
2916     } else {
2917         echo $output;
2918     }
2922 /**
2923  * Print a self contained form with a single submit button.
2924  *
2925  * @param string $link ?
2926  * @param array $options ?
2927  * @param string $label ?
2928  * @param string $method ?
2929  * @todo Finish documenting this function
2930  */
2931 function print_single_button($link, $options, $label='OK', $method='get', $target='_self', $return=false) {
2932     $output = '';
2933     $link = str_replace('"', '&quot;', $link); //basic XSS protection
2934     $output .= '<div class="singlebutton">';
2935     // taking target out, will need to add later target="'.$target.'"
2936     $output .= '<form action="'. $link .'" method="'. $method .'">';   
2937     $output .= '<fieldset class="invisiblefieldset">';
2938     if ($options) {
2939         foreach ($options as $name => $value) {
2940             $output .= '<input type="hidden" name="'. $name .'" value="'. s($value) .'" />';
2941         }
2942     }
2943     $output .= '<input type="submit" value="'. s($label) .'" /></fieldset></form></div>';
2945     if ($return) {
2946         return $output;
2947     } else {
2948         echo $output;
2949     }
2953 /**
2954  * Print a spacer image with the option of including a line break.
2955  *
2956  * @param int $height ?
2957  * @param int $width ?
2958  * @param boolean $br ?
2959  * @todo Finish documenting this function
2960  */
2961 function print_spacer($height=1, $width=1, $br=true, $return=false) {
2962     global $CFG;
2963     $output = '';
2965     $output .= '<img class="spacer" height="'. $height .'" width="'. $width .'" src="'. $CFG->wwwroot .'/pix/spacer.gif" alt="" />';
2966     if ($br) {
2967         $output .= '<br />'."\n";
2968     }
2970     if ($return) {
2971         return $output;
2972     } else {
2973         echo $output;
2974     }
2977 /**
2978  * Given the path to a picture file in a course, or a URL,
2979  * this function includes the picture in the page.
2980  *
2981  * @param string $path ?
2982  * @param int $courseid ?
2983  * @param int $height ?
2984  * @param int $width ?
2985  * @param string $link ?
2986  * @todo Finish documenting this function
2987  */
2988 function print_file_picture($path, $courseid=0, $height='', $width='', $link='', $return=false) {
2989     global $CFG;
2990     $output = '';
2992     if ($height) {
2993         $height = 'height="'. $height .'"';
2994     }
2995     if ($width) {
2996         $width = 'width="'. $width .'"';
2997     }
2998     if ($link) {
2999         $output .= '<a href="'. $link .'">';
3000     }
3001     if (substr(strtolower($path), 0, 7) == 'http://') {
3002         $output .= '<img style="height:'.$height.'px;width:'.$width.'px;" src="'. $path .'" />';
3004     } else if ($courseid) {
3005         $output .= '<img style="height:'.$height.'px;width:'.$width.'px;" src="';
3006         if ($CFG->slasharguments) {        // Use this method if possible for better caching
3007             $output .= $CFG->wwwroot .'/file.php/'. $courseid .'/'. $path;
3008         } else {
3009             $output .= $CFG->wwwroot .'/file.php?file=/'. $courseid .'/'. $path;
3010         }
3011         $output .= '" />';
3012     } else {
3013         $output .= 'Error: must pass URL or course';
3014     }
3015     if ($link) {
3016         $output .= '</a>';
3017     }
3019     if ($return) {
3020         return $output;
3021     } else {
3022         echo $output;
3023     }
3026 /**
3027  * Print the specified user's avatar.
3028  *
3029  * @param int $userid ?
3030  * @param int $courseid ?
3031  * @param boolean $picture Print the user picture?
3032  * @param int $size Size in pixels.  Special values are (true/1 = 100px) and (false/0 = 35px) for backward compatability
3033  * @param boolean $return If false print picture to current page, otherwise return the output as string
3034  * @param boolean $link Enclose printed image in a link to view specified course?
3035  * @param string $target link target attribute
3036  * @param boolean $alttext use username or userspecified text in image alt attribute
3037  * return string
3038  * @todo Finish documenting this function
3039  */
3040 function print_user_picture($userid, $courseid, $picture, $size=0, $return=false, $link=true, $target='', $alttext=true) {
3041     global $CFG;
3043     if ($link) {
3044         if ($target) {
3045             $target=' target="_blank"';
3046         }
3047         $output = '<a '.$target.' href="'. $CFG->wwwroot .'/user/view.php?id='. $userid .'&amp;course='. $courseid .'">';
3048     } else {
3049         $output = '';
3050     }
3051     if (empty($size)) {
3052         $file = 'f2';
3053         $size = 35;
3054     } else if ($size === true or $size == 1) {
3055         $file = 'f1';
3056         $size = 100;
3057     } else if ($size >= 50) {
3058         $file = 'f1';
3059     } else {
3060         $file = 'f2';
3061     }
3062     $class = "userpicture";
3063     if ($picture) {  // Print custom user picture
3064         if ($CFG->slasharguments) {        // Use this method if possible for better caching
3065             $src =  $CFG->wwwroot .'/user/pix.php/'. $userid .'/'. $file .'.jpg';
3066         } else {
3067             $src =  $CFG->wwwroot .'/user/pix.php?file=/'. $userid .'/'. $file .'.jpg';
3068         }
3069     } else {         // Print default user pictures (use theme version if available)
3070         $class .= " defaultuserpic";
3071         $src =  "$CFG->pixpath/u/$file.png";
3072     }
3073     $imagealt = '';
3074     if ($alttext and $user = get_record('user','id',$userid)) {
3075         if (!empty($user->imagealt)) {
3076             $imagealt = $user->imagealt;
3077         } else {
3078             $imagealt = get_string('pictureof','',fullname($user));
3079         }
3080     }
3082     $output .= '<img class="'.$class.'" src="'.$src.'" alt="'.s($imagealt).'" />';
3083     if ($link) {
3084         $output .= '</a>';
3085     }
3087     if ($return) {
3088         return $output;
3089     } else {
3090         echo $output;
3091     }
3094 /**
3095  * Prints a summary of a user in a nice little box.
3096  *
3097  * @uses $CFG
3098  * @uses $USER
3099  * @param user $user A {@link $USER} object representing a user
3100  * @param course $course A {@link $COURSE} object representing a course
3101  */
3102 function print_user($user, $course, $messageselect=false, $return=false) {
3104     global $CFG, $USER;
3106     $output = '';
3108     static $string;
3109     static $datestring;
3110     static $countries;
3112     $context = get_context_instance(CONTEXT_COURSE, $course->id);
3114     if (empty($string)) {     // Cache all the strings for the rest of the page
3116         $string->email       = get_string('email');
3117         $string->location    = get_string('location');
3118         $string->lastaccess  = get_string('lastaccess');
3119         $string->activity    = get_string('activity');
3120         $string->unenrol     = get_string('unenrol');
3121         $string->loginas     = get_string('loginas');
3122         $string->fullprofile = get_string('fullprofile');
3123         $string->role        = get_string('role');
3124         $string->name        = get_string('name');
3125         $string->never       = get_string('never');
3127         $datestring->day     = get_string('day');
3128         $datestring->days    = get_string('days');
3129         $datestring->hour    = get_string('hour');
3130         $datestring->hours   = get_string('hours');
3131         $datestring->min     = get_string('min');
3132         $datestring->mins    = get_string('mins');
3133         $datestring->sec     = get_string('sec');
3134         $datestring->secs    = get_string('secs');
3136         $countries = get_list_of_countries();
3137     }
3139 /// Get the hidden field list
3140     if (has_capability('moodle/course:viewhiddenuserfields', $context)) {
3141         $hiddenfields = array();
3142     } else {
3143         $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
3144     }
3146     $output .= '<table class="userinfobox">';
3147     $output .= '<tr>';
3148     $output .= '<td class="left side">';
3149     $output .= print_user_picture($user->id, $course->id, $user->picture, true, true);
3150     $output .= '</td>';
3151     $output .= '<td class="content">';
3152     $output .= '<div class="username">'.fullname($user, has_capability('moodle/site:viewfullnames', $context)).'</div>';
3153     $output .= '<div class="info">';
3154     if (!empty($user->role) and ($user->role <> $course->teacher)) {
3155         $output .= $string->role .': '. $user->role .'<br />';
3156     }
3157     if ($user->maildisplay == 1 or ($user->maildisplay == 2 and ($course->id != SITEID) and !isguest()) or
3158 has_capability('moodle/course:viewhiddenuserfields', $context)) {
3159         $output .= $string->email .': <a href="mailto:'. $user->email .'">'. $user->email .'</a><br />';
3160     }
3161     if (($user->city or $user->country) and (!isset($hiddenfields['city']) or !isset($hiddenfields['country']))) {
3162         $output .= $string->location .': ';
3163         if ($user->city && !isset($hiddenfields['city'])) {
3164             $output .= $user->city;
3165         }
3166         if (!empty($countries[$user->country]) && !isset($hiddenfields['country'])) {
3167             if ($user->city && !isset($hiddenfields['city'])) {
3168                 $output .= ', ';
3169             }
3170             $output .= $countries[$user->country];
3171         }
3172         $output .= '<br />';
3173     }
3175     if (!isset($hiddenfields['lastaccess'])) {
3176         if ($user->lastaccess) {
3177             $output .= $string->lastaccess .': '. userdate($user->lastaccess);
3178             $output .= '&nbsp; ('. format_time(time() - $user->lastaccess, $datestring) .')';
3179         } else {
3180             $output .= $string->lastaccess .': '. $string->never;
3181         }
3182     }
3183     $output .= '</div></td><td class="links">';
3184     //link to blogs
3185     if ($CFG->bloglevel > 0) {
3186         $output .= '<a href="'.$CFG->wwwroot.'/blog/index.php?userid='.$user->id.'">'.get_string('blogs','blog').'</a><br />';
3187     }
3189     if (has_capability('moodle/site:viewreports', $context)) {
3190         $timemidnight = usergetmidnight(time());
3191         $output .= '<a href="'. $CFG->wwwroot .'/course/user.php?id='. $course->id .'&amp;user='. $user->id .'">'. $string->activity .'</a><br />';
3192     }
3193     if (has_capability('moodle/role:assign', $context, NULL)) {  // Includes admins
3194         $output .= '<a href="'. $CFG->wwwroot .'/course/unenrol.php?id='. $course->id .'&amp;user='. $user->id .'">'. $string->unenrol .'</a><br />';
3195     }
3196     if ($USER->id != $user->id && has_capability('moodle/user:loginas', $context))  {
3197         $output .= '<a href="'. $CFG->wwwroot .'/course/loginas.php?id='. $course->id .'&amp;user='. $user->id .'">'. $string->loginas .'</a><br />';
3198     }
3199     $output .= '<a href="'. $CFG->wwwroot .'/user/view.php?id='. $user->id .'&amp;course='. $course->id .'">'. $string->fullprofile .'...</a>';
3201     if (!empty($messageselect)) {
3202         $output .= '<br /><input type="checkbox" name="user'.$user->id.'" /> ';
3203     }
3205     $output .= '</td></tr></table>';
3207     if ($return) {
3208         return $output;
3209     } else {
3210         echo $output;
3211     }
3214 /**
3215  * Print a specified group's avatar.
3216  *
3217  * @param group $group A {@link group} object representing a group or array of groups
3218  * @param int $courseid ?
3219  * @param boolean $large ?
3220  * @param boolean $return ?
3221  * @param boolean $link ?
3222  * @return string
3223  * @todo Finish documenting this function
3224  */
3225 function print_group_picture($group, $courseid, $large=false, $return=false, $link=true) {
3226     global $CFG;
3228     if (is_array($group)) {
3229         $output = '';
3230         foreach($group as $g) {
3231             $output .= print_group_picture($g, $courseid, $large, true, $link);
3232         }
3233         if ($return) {
3234             return $output;
3235         } else {
3236             echo $output;
3237             return;
3238         }
3239     }
3241     $context = get_context_instance(CONTEXT_COURSE, $courseid);
3243     if ($group->hidepicture and !has_capability('moodle/course:managegroups', $context)) {
3244         return '';
3245     }
3247     if ($link or has_capability('moodle/site:accessallgroups', $context)) {
3248         $output = '<a href="'. $CFG->wwwroot .'/user/index.php?id='. $courseid .'&amp;group='. $group->id .'">';
3249     } else {
3250         $output = '';
3251     }
3252     if ($large) {
3253         $file = 'f1';
3254         $size = 100;
3255     } else {
3256         $file = 'f2';
3257         $size = 35;
3258     }
3259     if ($group->picture) {  // Print custom group picture
3260         if ($CFG->slasharguments) {        // Use this method if possible for better caching
3261             $output .= '<img class="grouppicture" align="middle" src="'.$CFG->wwwroot.'/user/pixgroup.php/'.$group->id.'/'.$file.'.jpg"'.
3262                        ' style="width:'.$size.'px;height:'.$size.'px;" alt="'.s(get_string('group').' '.$group->name).'" title="'.s($group->name).'"/>';
3263         } else {
3264             $output .= '<img class="grouppicture" align="middle" src="'.$CFG->wwwroot.'/user/pixgroup.php?file=/'.$group->id.'/'.$file.'.jpg"'.
3265                        ' style="width:'.$size.'px;height:'.$size.'px;" alt="'.s(get_string('group').' '.$group->name).'" title="'.s($group->name).'"/>';
3266         }
3267     }
3268     if ($link or has_capability('moodle/site:accessallgroups', $context)) {
3269         $output .= '</a>';
3270     }
3272     if ($return) {
3273         return $output;
3274     } else {
3275         echo $output;
3276     }
3279 /**
3280  * Print a png image.
3281  *
3282  * @param string $url ?
3283  * @param int $sizex ?
3284  * @param int $sizey ?
3285  * @param boolean $return ?
3286  * @param string $parameters ?
3287  * @todo Finish documenting this function
3288  */
3289 function print_png($url, $sizex, $sizey, $return, $parameters='alt=""') {
3290     global $CFG;
3291     static $recentIE;
3293     if (!isset($recentIE)) {
3294         $recentIE = check_browser_version('MSIE', '5.0');
3295     }
3297     if ($recentIE) {  // work around the HORRIBLE bug IE has with alpha transparencies
3298         $output .= '<img src="'. $CFG->pixpath .'/spacer.gif" width="'. $sizex .'" height="'. $sizey .'"'.
3299                    ' class="png" style="width: '. $sizex .'px; height: '. $sizey .'px; '.
3300                    ' filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='.
3301                    "'$url', sizingMethod='scale') ".
3302                    ' '. $parameters .' />';
3303     } else {
3304         $output .= '<img src="'. $url .'" style="width: '. $sizex .'px; height: '. $sizey .'px; '. $parameters .' />';
3305     }
3307     if ($return) {
3308         return $output;
3309     } else {
3310         echo $output;
3311     }
3314 /**
3315  * Print a nicely formatted table.
3316  *
3317  * @param array $table is an object with several properties.
3318  *     <ul<li>$table->head - An array of heading names.
3319  *     <li>$table->align - An array of column alignments
3320  *     <li>$table->size  - An array of column sizes
3321  *     <li>$table->wrap - An array of "nowrap"s or nothing
3322  *     <li>$table->data[] - An array of arrays containing the data.
3323  *     <li>$table->width  - A percentage of the page
3324  *     <li>$table->tablealign  - Align the whole table
3325  *     <li>$table->cellpadding  - Padding on each cell
3326  *     <li>$table->cellspacing  - Spacing between cells
3327  * </ul>
3328  * @param bool $return whether to return an output string or echo now
3329  * @return boolean or $string
3330  * @todo Finish documenting this function
3331  */
3332 function print_table($table, $return=false) {
3333     $output = '';
3335     if (isset($table->align)) {
3336         foreach ($table->align as $key => $aa) {
3337             if ($aa) {
3338                 $align[$key] = ' text-align:'. $aa.';';
3339             } else {
3340                 $align[$key] = '';
3341             }
3342         }
3343     }
3344     if (isset($table->size)) {
3345         foreach ($table->size as $key => $ss) {
3346             if ($ss) {
3347                 $size[$key] = ' width:'. $ss .';';
3348             } else {
3349                 $size[$key] = '';
3350             }
3351         }
3352     }
3353     if (isset($table->wrap)) {
3354         foreach ($table->wrap as $key => $ww) {
3355             if ($ww) {
3356                 $wrap[$key] = ' white-space:nowrap;';
3357             } else {
3358                 $wrap[$key] = '';
3359             }
3360         }
3361     }
3363     if (empty($table->width)) {
3364         $table->width = '80%';
3365     }
3367     if (empty($table->tablealign)) {
3368         $table->tablealign = 'center';
3369     }
3371     if (empty($table->cellpadding)) {
3372         $table->cellpadding = '5';
3373     }
3375     if (empty($table->cellspacing)) {
3376         $table->cellspacing = '1';
3377     }
3379     if (empty($table->class)) {
3380         $table->class = 'generaltable';
3381     }
3383     $tableid = empty($table->id) ? '' : 'id="'.$table->id.'"';
3385     $output .= '<table width="'.$table->width.'" ';
3386     $output .= " cellpadding=\"$table->cellpadding\" cellspacing=\"$table->cellspacing\" class=\"$table->class boxalign$table->tablealign\" $tableid>\n";
3388     $countcols = 0;
3390     if (!empty($table->head)) {
3391         $countcols = count($table->head);
3392         $output .= '<tr>';
3393         foreach ($table->head as $key => $heading) {
3395             if (!isset($size[$key])) {
3396                 $size[$key] = '';
3397             }
3398             if (!isset($align[$key])) {
3399                 $align[$key] = '';
3400             }
3401             
3402             $output .= '<th class="header c'.$key.'" scope="col">'. $heading .'</th>';
3403             // commenting the following code out as <th style does not validate MDL-7861
3404             //$output .= '<th sytle="vertical-align:top;'. $align[$key].$size[$key] .';white-space:nowrap;" class="header c'.$key.'" scope="col">'. $heading .'</th>';
3405         }
3406         $output .= '</tr>'."\n";
3407     }
3409     if (!empty($table->data)) {
3410         $oddeven = 1;
3411         foreach ($table->data as $key => $row) {
3412             $oddeven = $oddeven ? 0 : 1;
3413             $output .= '<tr class="r'.$oddeven.'">'."\n";
3414             if ($row == 'hr' and $countcols) {
3415                 $output .= '<td colspan="'. $countcols .'"><div class="tabledivider"></div></td>';
3416             } else {  /// it's a normal row of data
3417                 foreach ($row as $key => $item) {
3418                     if (!isset($size[$key])) {
3419                         $size[$key] = '';
3420                     }
3421                     if (!isset($align[$key])) {
3422                         $align[$key] = '';
3423                     }
3424                     if (!isset($wrap[$key])) {
3425                         $wrap[$key] = '';
3426                     }
3427                     $output .= '<td style="'. $align[$key].$size[$key].$wrap[$key] .'" class="cell c'.$key.'">'. $item .'</td>';
3428                 }
3429             }
3430             $output .= '</tr>'."\n";
3431         }
3432     }
3433     $output .= '</table>'."\n";
3435     if ($return) {
3436         return $output;
3437     }
3439     echo $output;
3440     return true;
3443 /**
3444  * Creates a nicely formatted table and returns it.
3445  *
3446  * @param array $table is an object with several properties.
3447  *     <ul<li>$table->head - An array of heading names.
3448  *     <li>$table->align - An array of column alignments
3449  *     <li>$table->size  - An array of column sizes
3450  *     <li>$table->wrap - An array of "nowrap"s or nothing
3451  *     <li>$table->data[] - An array of arrays containing the data.
3452  *     <li>$table->class -  A css class name
3453  *     <li>$table->fontsize - Is the size of all the text
3454  *     <li>$table->tablealign  - Align the whole table
3455  *     <li>$table->width  - A percentage of the page
3456  *     <li>$table->cellpadding  - Padding on each cell
3457  *     <li>$table->cellspacing  - Spacing between cells
3458  * </ul>
3459  * @return string
3460  * @todo Finish documenting this function
3461  */
3462 function make_table($table) {
3464     if (isset($table->align)) {
3465         foreach ($table->align as $key => $aa) {
3466             if ($aa) {
3467                 $align[$key] = ' align="'. $aa .'"';
3468             } else {
3469                 $align[$key] = '';
3470             }
3471         }
3472     }
3473     if (isset($table->size)) {
3474         foreach ($table->size as $key => $ss) {
3475             if ($ss) {
3476                 $size[$key] = ' width="'. $ss .'"';
3477             } else {
3478                 $size[$key] = '';
3479             }
3480         }
3481     }
3482     if (isset($table->wrap)) {
3483         foreach ($table->wrap as $key => $ww) {
3484             if ($ww) {
3485                 $wrap[$key] = ' style="white-space:nowrap;" ';
3486             } else {
3487                 $wrap[$key] = '';
3488             }
3489         }
3490     }
3492     if (empty($table->width)) {
3493         $table->width = '80%';
3494     }
3496     if (empty($table->tablealign)) {
3497         $table->tablealign = 'center';
3498     }
3500     if (empty($table->cellpadding)) {
3501         $table->cellpadding = '5';
3502     }
3504     if (empty($table->cellspacing)) {
3505         $table->cellspacing = '1';
3506     }
3508     if (empty($table->class)) {
3509         $table->class = 'generaltable';
3510     }
3512     if (empty($table->fontsize)) {
3513         $fontsize = '';
3514     } else {
3515         $fontsize = '<font size="'. $table->fontsize .'">';
3516     }
3518     $output =  '<table width="'. $table->width .'" align="'. $table->tablealign .'" ';
3519     $output .= ' cellpadding="'. $table->cellpadding .'" cellspacing="'. $table->cellspacing .'" class="'. $table->class .'">'."\n";
3521     if (!empty($table->head)) {
3522         $output .= '<tr valign="top">';
3523         foreach ($table->head as $key => $heading) {
3524             if (!isset($size[$key])) {
3525                 $size[$key] = '';
3526             }
3527             if (!isset($align[$key])) {
3528                 $align[$key] = '';
3529             }
3530             $output .= '<th valign="top" '. $align[$key].$size[$key] .' style="white-space:nowrap;" class="'. $table->class .'header" scope="col">'.$fontsize.$heading.'</th>';
3531         }
3532         $output .= '</tr>'."\n";
3533     }
3535     foreach ($table->data as $row) {
3536         $output .= '<tr valign="top">';
3537         foreach ($row as $key => $item) {
3538             if (!isset($size[$key])) {
3539                 $size[$key] = '';
3540             }
3541             if (!isset($align[$key])) {
3542                 $align[$key] = '';
3543             }
3544             if (!isset($wrap[$key])) {
3545                 $wrap[$key] = '';
3546             }
3547             $output .= '<td '. $align[$key].$size[$key].$wrap[$key] .' class="'. $table->class .'cell">'. $fontsize . $item .'</td>';
3548         }
3549         $output .= '</tr>'."\n";
3550     }
3551     $output .= '</table>'."\n";
3553     return $output;
3556 function print_recent_activity_note($time, $user, $text, $link, $return=false) {
3557     static $strftimerecent;
3558     $output = '';
3560     $context = get_context_instance(CONTEXT_SYSTEM, SITEID);
3561     $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
3563     if (empty($strftimerecent)) {
3564         $strftimerecent = get_string('strftimerecent');
3565     }
3567     $date = userdate($time, $strftimerecent);
3568     $name = fullname($user, $viewfullnames);
3570     $output .= '<div class="head">';
3571     $output .= '<div class="date">'.$date.'</div> '.
3572          '<div class="name">'.fullname($user, $viewfullnames).'</div>';
3573     $output .= '</div>';
3574     $output .= '<div class="info"><a href="'.$link.'">'.format_string($text,true).'</a></div>';
3576     if ($return) {
3577         return $output;
3578     } else {
3579         echo $output;
3580     }
3584 /**
3585  * Prints a basic textarea field.
3586  *
3587  * @uses $CFG
3588  * @param boolean $usehtmleditor ?
3589  * @param int $rows ?
3590  * @param int $cols ?
3591  * @param null $width <b>Legacy field no longer used!</b>  Set to zero to get control over mincols
3592  * @param null $height <b>Legacy field no longer used!</b>  Set to zero to get control over minrows
3593  * @param string $name ?
3594  * @param string $value ?
3595  * @param int $courseid ?
3596  * @todo Finish documenting this function
3597  */
3598 function print_textarea($usehtmleditor, $rows, $cols, $width, $height, $name, $value='', $courseid=0, $return=false, $id='') {
3599 /// $width and height are legacy fields and no longer used as pixels like they used to be.
3600 /// However, you can set them to zero to override the mincols and minrows values below.
3602     global $CFG, $course;
3603     static $scriptcount; // For loading the htmlarea script only once.
3605     $mincols = 65;
3606     $minrows = 10;
3607     $str = '';
3609     if ($id === '') {
3610         $id = 'edit-'.$name;
3611     }
3613     if ( empty($CFG->editorsrc) ) { // for backward compatibility.
3614         if (empty($courseid)) {
3615             if (!empty($course->id)) {  // search for it in global context
3616                 $courseid = $course->id;
3617             }
3618         }
3620         if (empty($scriptcount)) {
3621             $scriptcount = 0;
3622         }
3624         if ($usehtmleditor) {
3625             if (!empty($courseid) and has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $courseid))) {
3626                 // needed for course file area browsing in image insert plugin
3627                 $str .= ($scriptcount < 1) ? '<script type="text/javascript" src="'.
3628                 $CFG->wwwroot .'/lib/editor/htmlarea/htmlarea.php?id='. $courseid .'"></script>'."\n" : '';
3629             } else {
3630                 $str .= ($scriptcount < 1) ? '<script type="text/javascript" src="'.
3631                 $CFG->wwwroot .'/lib/editor/htmlarea/htmlarea.php"></script>'."\n" : '';
3632             }
3633             $str .= ($scriptcount < 1) ? '<script type="text/javascript" src="'.
3634             $CFG->wwwroot .'/lib/editor/htmlarea/lang/en.php"></script>'."\n" : '';
3635             $scriptcount++;
3637             if ($height) {    // Usually with legacy calls
3638                 if ($rows < $minrows) {
3639                     $rows = $minrows;
3640                 }
3641             }
3642             if ($width) {    // Usually with legacy calls
3643                 if ($cols < $mincols) {
3644                     $cols = $mincols;
3645                 }
3646             }
3647         }
3648     }
3649     $str .= '<textarea class="form-textarea" id="'. $id .'" name="'. $name .'" rows="'. $rows .'" cols="'. $cols .'">';
3650     if ($usehtmleditor) {
3651         $str .= htmlspecialchars($value); // needed for editing of cleaned text!
3652     } else {
3653         $str .= s($value);
3654     }
3655     $str .= '</textarea>'."\n";
3657         if ($usehtmleditor) {
3658                 $str .= editorshortcutshelpbutton();
3659         }
3661     if ($return) {
3662         return $str;
3663     }
3664     echo $str;
3667 /**
3668  * Sets up the HTML editor on textareas in the current page.
3669  * If a field name is provided, then it will only be
3670  * applied to that field - otherwise it will be used
3671  * on every textarea in the page.
3672  *
3673  * In most cases no arguments need to be supplied
3674  *
3675  * @param string $name Form element to replace with HTMl editor by name
3676  */
3677 function use_html_editor($name='', $editorhidebuttons='', $id='') {
3678     $editor = 'editor_'.md5($name); //name might contain illegal characters
3679     if ($id === '') {
3680         $id = 'edit-'.$name;
3681     }
3682     echo "\n".'<script type="text/javascript" defer="defer">'."\n";
3683     echo '//<![CDATA['."\n\n"; // Extra \n is to fix odd wiki problem, MDL-8185
3684     echo "$editor = new HTMLArea('$id');\n";
3685     echo "var config = $editor.config;\n";