Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

7157 lines (6201 sloc) 250.281 kb
<?php // $Id$
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.com //
// //
// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation; either version 2 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License for more details: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
/**
* Library of functions for web output
*
* Library of all general-purpose Moodle PHP functions and constants
* that produce HTML output
*
* Other main libraries:
* - datalib.php - functions that access the database.
* - moodlelib.php - general-purpose Moodle functions.
* @author Martin Dougiamas
* @version $Id$
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package moodlecore
*/
/// We are going to uses filterlib functions here
require_once("$CFG->libdir/filterlib.php");
require_once("$CFG->libdir/ajax/ajaxlib.php");
/// Constants
/// Define text formatting types ... eventually we can add Wiki, BBcode etc
/**
* Does all sorts of transformations and filtering
*/
define('FORMAT_MOODLE', '0'); // Does all sorts of transformations and filtering
/**
* Plain HTML (with some tags stripped)
*/
define('FORMAT_HTML', '1'); // Plain HTML (with some tags stripped)
/**
* Plain text (even tags are printed in full)
*/
define('FORMAT_PLAIN', '2'); // Plain text (even tags are printed in full)
/**
* Wiki-formatted text
* Deprecated: left here just to note that '3' is not used (at the moment)
* and to catch any latent wiki-like text (which generates an error)
*/
define('FORMAT_WIKI', '3'); // Wiki-formatted text
/**
* Markdown-formatted text http://daringfireball.net/projects/markdown/
*/
define('FORMAT_MARKDOWN', '4'); // Markdown-formatted text http://daringfireball.net/projects/markdown/
/**
* TRUSTTEXT marker - if present in text, text cleaning should be bypassed
*/
define('TRUSTTEXT', '#####TRUSTTEXT#####');
/**
* Javascript related defines
*/
define('REQUIREJS_BEFOREHEADER', 0);
define('REQUIREJS_INHEADER', 1);
define('REQUIREJS_AFTERHEADER', 2);
/**
* Allowed tags - string of html tags that can be tested against for safe html tags
* @global string $ALLOWED_TAGS
*/
global $ALLOWED_TAGS;
$ALLOWED_TAGS =
'<p><br><b><i><u><font><table><tbody><thead><tfoot><span><div><tr><td><th><ol><ul><dl><li><dt><dd><h1><h2><h3><h4><h5><h6><hr><img><a><strong><emphasis><em><sup><sub><address><cite><blockquote><pre><strike><param><acronym><nolink><lang><tex><algebra><math><mi><mn><mo><mtext><mspace><ms><mrow><mfrac><msqrt><mroot><mstyle><merror><mpadded><mphantom><mfenced><msub><msup><msubsup><munder><mover><munderover><mmultiscripts><mtable><mtr><mtd><maligngroup><malignmark><maction><cn><ci><apply><reln><fn><interval><inverse><sep><condition><declare><lambda><compose><ident><quotient><exp><factorial><divide><max><min><minus><plus><power><rem><times><root><gcd><and><or><xor><not><implies><forall><exists><abs><conjugate><eq><neq><gt><lt><geq><leq><ln><log><int><diff><partialdiff><lowlimit><uplimit><bvar><degree><set><list><union><intersect><in><notin><subset><prsubset><notsubset><notprsubset><setdiff><sum><product><limit><tendsto><mean><sdev><variance><median><mode><moment><vector><matrix><matrixrow><determinant><transpose><selector><annotation><semantics><annotation-xml><tt><code>';
/**
* Allowed protocols - array of protocols that are safe to use in links and so on
* @global string $ALLOWED_PROTOCOLS
*/
$ALLOWED_PROTOCOLS = array('http', 'https', 'ftp', 'news', 'mailto', 'rtsp', 'teamspeak', 'gopher', 'mms',
'color', 'callto', 'cursor', 'text-align', 'font-size', 'font-weight', 'font-style', 'font-family',
'border', 'margin', 'padding', 'background', 'background-color', 'text-decoration'); // CSS as well to get through kses
/// Functions
/**
* Add quotes to HTML characters
*
* Returns $var with HTML characters (like "<", ">", etc.) properly quoted.
* This function is very similar to {@link p()}
*
* @param string $var the string potentially containing HTML characters
* @param boolean $strip to decide if we want to strip slashes or no. Default to false.
* true should be used to print data from forms and false for data from DB.
* @return string
*/
function s($var, $strip=false) {
if ($var == '0') { // for integer 0, boolean false, string '0'
return '0';
}
if ($strip) {
return preg_replace("/&amp;(#\d+);/i", "&$1;", htmlspecialchars(stripslashes_safe($var)));
} else {
return preg_replace("/&amp;(#\d+);/i", "&$1;", htmlspecialchars($var));
}
}
/**
* Add quotes to HTML characters
*
* Prints $var with HTML characters (like "<", ">", etc.) properly quoted.
* This function is very similar to {@link s()}
*
* @param string $var the string potentially containing HTML characters
* @param boolean $strip to decide if we want to strip slashes or no. Default to false.
* true should be used to print data from forms and false for data from DB.
* @return string
*/
function p($var, $strip=false) {
echo s($var, $strip);
}
/**
* Does proper javascript quoting.
* Do not use addslashes anymore, because it does not work when magic_quotes_sybase is enabled.
*
* @since 1.8 - 22/02/2007
* @param mixed value
* @return mixed quoted result
*/
function addslashes_js($var) {
if (is_string($var)) {
$var = str_replace('\\', '\\\\', $var);
$var = str_replace(array('\'', '"', "\n", "\r", "\0"), array('\\\'', '\\"', '\\n', '\\r', '\\0'), $var);
$var = str_replace('</', '<\/', $var); // XHTML compliance
} else if (is_array($var)) {
$var = array_map('addslashes_js', $var);
} else if (is_object($var)) {
$a = get_object_vars($var);
foreach ($a as $key=>$value) {
$a[$key] = addslashes_js($value);
}
$var = (object)$a;
}
return $var;
}
/**
* Remove query string from url
*
* Takes in a URL and returns it without the querystring portion
*
* @param string $url the url which may have a query string attached
* @return string
*/
function strip_querystring($url) {
if ($commapos = strpos($url, '?')) {
return substr($url, 0, $commapos);
} else {
return $url;
}
}
/**
* Returns the URL of the HTTP_REFERER, less the querystring portion if required
* @param boolean $stripquery if true, also removes the query part of the url.
* @return string
*/
function get_referer($stripquery=true) {
if (isset($_SERVER['HTTP_REFERER'])) {
if ($stripquery) {
return strip_querystring($_SERVER['HTTP_REFERER']);
} else {
return $_SERVER['HTTP_REFERER'];
}
} else {
return '';
}
}
/**
* Returns the name of the current script, WITH the querystring portion.
* this function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
* return different things depending on a lot of things like your OS, Web
* server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.)
* <b>NOTE:</b> This function returns false if the global variables needed are not set.
*
* @return string
*/
function me() {
if (!empty($_SERVER['REQUEST_URI'])) {
return $_SERVER['REQUEST_URI'];
} else if (!empty($_SERVER['PHP_SELF'])) {
if (!empty($_SERVER['QUERY_STRING'])) {
return $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
}
return $_SERVER['PHP_SELF'];
} else if (!empty($_SERVER['SCRIPT_NAME'])) {
if (!empty($_SERVER['QUERY_STRING'])) {
return $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING'];
}
return $_SERVER['SCRIPT_NAME'];
} else if (!empty($_SERVER['URL'])) { // May help IIS (not well tested)
if (!empty($_SERVER['QUERY_STRING'])) {
return $_SERVER['URL'] .'?'. $_SERVER['QUERY_STRING'];
}
return $_SERVER['URL'];
} else {
notify('Warning: Could not find any of these web server variables: $REQUEST_URI, $PHP_SELF, $SCRIPT_NAME or $URL');
return false;
}
}
/**
* Like {@link me()} but returns a full URL
* @see me()
* @return string
*/
function qualified_me() {
global $CFG;
if (!empty($CFG->wwwroot)) {
$url = parse_url($CFG->wwwroot);
}
if (!empty($url['host'])) {
$hostname = $url['host'];
} else if (!empty($_SERVER['SERVER_NAME'])) {
$hostname = $_SERVER['SERVER_NAME'];
} else if (!empty($_ENV['SERVER_NAME'])) {
$hostname = $_ENV['SERVER_NAME'];
} else if (!empty($_SERVER['HTTP_HOST'])) {
$hostname = $_SERVER['HTTP_HOST'];
} else if (!empty($_ENV['HTTP_HOST'])) {
$hostname = $_ENV['HTTP_HOST'];
} else {
notify('Warning: could not find the name of this server!');
return false;
}
if (!empty($url['port'])) {
$hostname .= ':'.$url['port'];
} else if (!empty($_SERVER['SERVER_PORT'])) {
if ($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
$hostname .= ':'.$_SERVER['SERVER_PORT'];
}
}
// TODO, this does not work in the situation described in MDL-11061, but
// I don't know how to fix it. Possibly believe $CFG->wwwroot ahead of what
// the server reports.
if (isset($_SERVER['HTTPS'])) {
$protocol = ($_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
} else if (isset($_SERVER['SERVER_PORT'])) { # Apache2 does not export $_SERVER['HTTPS']
$protocol = ($_SERVER['SERVER_PORT'] == '443') ? 'https://' : 'http://';
} else {
$protocol = 'http://';
}
$url_prefix = $protocol.$hostname;
return $url_prefix . me();
}
/**
* Class for creating and manipulating urls.
*
* See short write up here http://docs.moodle.org/en/Development:lib/weblib.php_moodle_url
*/
class moodle_url {
var $scheme = '';// e.g. http
var $host = '';
var $port = '';
var $user = '';
var $pass = '';
var $path = '';
var $fragment = '';
var $params = array(); //associative array of query string params
/**
* Pass no arguments to create a url that refers to this page. Use empty string to create empty url.
*
* @param string $url url default null means use this page url with no query string
* empty string means empty url.
* if you pass any other type of url it will be parsed into it's bits, including query string
* @param array $params these params override anything in the query string where params have the same name.
*/
function moodle_url($url = null, $params = array()){
global $FULLME;
if ($url !== ''){
if ($url === null){
$url = strip_querystring($FULLME);
}
$parts = parse_url($url);
if ($parts === FALSE){
error('invalidurl');
}
if (isset($parts['query'])){
parse_str(str_replace('&amp;', '&', $parts['query']), $this->params);
}
unset($parts['query']);
foreach ($parts as $key => $value){
$this->$key = $value;
}
$this->params($params);
}
}
/**
* Add an array of params to the params for this page.
*
* The added params override existing ones if they have the same name.
*
* @param array $params Defaults to null. If null then return value of param 'name'.
* @return array Array of Params for url.
*/
function params($params = null) {
if (!is_null($params)) {
return $this->params = $params + $this->params;
} else {
return $this->params;
}
}
/**
* Remove all params if no arguments passed. Or else remove param $arg1, $arg2, etc.
*
* @param string $arg1
* @param string $arg2
* @param string $arg3
*/
function remove_params(){
if ($thisargs = func_get_args()){
foreach ($thisargs as $arg){
if (isset($this->params[$arg])){
unset($this->params[$arg]);
}
}
} else { // no args
$this->params = array();
}
}
/**
* Add a param to the params for this page. The added param overrides existing one if they
* have the same name.
*
* @param string $paramname name
* @param string $param value
*/
function param($paramname, $param){
$this->params = array($paramname => $param) + $this->params;
}
function get_query_string($overrideparams = array()){
$arr = array();
$params = $overrideparams + $this->params;
foreach ($params as $key => $val){
$arr[] = urlencode($key)."=".urlencode($val);
}
return implode($arr, "&amp;");
}
/**
* Outputs params as hidden form elements.
*
* @param array $exclude params to ignore
* @param integer $indent indentation
* @param array $overrideparams params to add to the output params, these
* override existing ones with the same name.
* @return string html for form elements.
*/
function hidden_params_out($exclude = array(), $indent = 0, $overrideparams=array()){
$tabindent = str_repeat("\t", $indent);
$str = '';
$params = $overrideparams + $this->params;
foreach ($params as $key => $val){
if (FALSE === array_search($key, $exclude)) {
$val = s($val);
$str.= "$tabindent<input type=\"hidden\" name=\"$key\" value=\"$val\" />\n";
}
}
return $str;
}
/**
* Output url
*
* @param boolean $noquerystring whether to output page params as a query string in the url.
* @param array $overrideparams params to add to the output url, these override existing ones with the same name.
* @return string url
*/
function out($noquerystring = false, $overrideparams = array()) {
$uri = $this->scheme ? $this->scheme.':'.((strtolower($this->scheme) == 'mailto') ? '':'//'): '';
$uri .= $this->user ? $this->user.($this->pass? ':'.$this->pass:'').'@':'';
$uri .= $this->host ? $this->host : '';
$uri .= $this->port ? ':'.$this->port : '';
$uri .= $this->path ? $this->path : '';
if (!$noquerystring){
$uri .= (count($this->params)||count($overrideparams)) ? '?'.$this->get_query_string($overrideparams) : '';
}
$uri .= $this->fragment ? '#'.$this->fragment : '';
return $uri;
}
/**
* Output action url with sesskey
*
* @param boolean $noquerystring whether to output page params as a query string in the url.
* @return string url
*/
function out_action($overrideparams = array()) {
$overrideparams = array('sesskey'=> sesskey()) + $overrideparams;
return $this->out(false, $overrideparams);
}
}
/**
* Determine if there is data waiting to be processed from a form
*
* Used on most forms in Moodle to check for data
* Returns the data as an object, if it's found.
* This object can be used in foreach loops without
* casting because it's cast to (array) automatically
*
* Checks that submitted POST data exists and returns it as object.
*
* @param string $url not used anymore
* @return mixed false or object
*/
function data_submitted($url='') {
if (empty($_POST)) {
return false;
} else {
return (object)$_POST;
}
}
/**
* Moodle replacement for php stripslashes() function,
* works also for objects and arrays.
*
* The standard php stripslashes() removes ALL backslashes
* even from strings - so C:\temp becomes C:temp - this isn't good.
* This function should work as a fairly safe replacement
* to be called on quoted AND unquoted strings (to be sure)
*
* @param mixed something to remove unsafe slashes from
* @return mixed
*/
function stripslashes_safe($mixed) {
// there is no need to remove slashes from int, float and bool types
if (empty($mixed)) {
//nothing to do...
} else if (is_string($mixed)) {
if (ini_get_bool('magic_quotes_sybase')) { //only unescape single quotes
$mixed = str_replace("''", "'", $mixed);
} else { //the rest, simple and double quotes and backslashes
$mixed = str_replace("\\'", "'", $mixed);
$mixed = str_replace('\\"', '"', $mixed);
$mixed = str_replace('\\\\', '\\', $mixed);
}
} else if (is_array($mixed)) {
foreach ($mixed as $key => $value) {
$mixed[$key] = stripslashes_safe($value);
}
} else if (is_object($mixed)) {
$vars = get_object_vars($mixed);
foreach ($vars as $key => $value) {
$mixed->$key = stripslashes_safe($value);
}
}
return $mixed;
}
/**
* Recursive implementation of stripslashes()
*
* This function will allow you to strip the slashes from a variable.
* If the variable is an array or object, slashes will be stripped
* from the items (or properties) it contains, even if they are arrays
* or objects themselves.
*
* @param mixed the variable to remove slashes from
* @return mixed
*/
function stripslashes_recursive($var) {
if (is_object($var)) {
$new_var = new object();
$properties = get_object_vars($var);
foreach($properties as $property => $value) {
$new_var->$property = stripslashes_recursive($value);
}
} else if(is_array($var)) {
$new_var = array();
foreach($var as $property => $value) {
$new_var[$property] = stripslashes_recursive($value);
}
} else if(is_string($var)) {
$new_var = stripslashes($var);
} else {
$new_var = $var;
}
return $new_var;
}
/**
* Recursive implementation of addslashes()
*
* This function will allow you to add the slashes from a variable.
* If the variable is an array or object, slashes will be added
* to the items (or properties) it contains, even if they are arrays
* or objects themselves.
*
* @param mixed the variable to add slashes from
* @return mixed
*/
function addslashes_recursive($var) {
if (is_object($var)) {
$new_var = new object();
$properties = get_object_vars($var);
foreach($properties as $property => $value) {
$new_var->$property = addslashes_recursive($value);
}
} else if (is_array($var)) {
$new_var = array();
foreach($var as $property => $value) {
$new_var[$property] = addslashes_recursive($value);
}
} else if (is_string($var)) {
$new_var = addslashes($var);
} else { // nulls, integers, etc.
$new_var = $var;
}
return $new_var;
}
/**
* Given some normal text this function will break up any
* long words to a given size by inserting the given character
*
* It's multibyte savvy and doesn't change anything inside html tags.
*
* @param string $string the string to be modified
* @param int $maxsize maximum length of the string to be returned
* @param string $cutchar the string used to represent word breaks
* @return string
*/
function break_up_long_words($string, $maxsize=20, $cutchar=' ') {
/// Loading the textlib singleton instance. We are going to need it.
$textlib = textlib_get_instance();
/// First of all, save all the tags inside the text to skip them
$tags = array();
filter_save_tags($string,$tags);
/// Process the string adding the cut when necessary
$output = '';
$length = $textlib->strlen($string);
$wordlength = 0;
for ($i=0; $i<$length; $i++) {
$char = $textlib->substr($string, $i, 1);
if ($char == ' ' or $char == "\t" or $char == "\n" or $char == "\r" or $char == "<" or $char == ">") {
$wordlength = 0;
} else {
$wordlength++;
if ($wordlength > $maxsize) {
$output .= $cutchar;
$wordlength = 0;
}
}
$output .= $char;
}
/// Finally load the tags back again
if (!empty($tags)) {
$output = str_replace(array_keys($tags), $tags, $output);
}
return $output;
}
/**
* This does a search and replace, ignoring case
* This function is only used for versions of PHP older than version 5
* which do not have a native version of this function.
* Taken from the PHP manual, by bradhuizenga @ softhome.net
*
* @param string $find the string to search for
* @param string $replace the string to replace $find with
* @param string $string the string to search through
* return string
*/
if (!function_exists('str_ireplace')) { /// Only exists in PHP 5
function str_ireplace($find, $replace, $string) {
if (!is_array($find)) {
$find = array($find);
}
if(!is_array($replace)) {
if (!is_array($find)) {
$replace = array($replace);
} else {
// this will duplicate the string into an array the size of $find
$c = count($find);
$rString = $replace;
unset($replace);
for ($i = 0; $i < $c; $i++) {
$replace[$i] = $rString;
}
}
}
foreach ($find as $fKey => $fItem) {
$between = explode(strtolower($fItem),strtolower($string));
$pos = 0;
foreach($between as $bKey => $bItem) {
$between[$bKey] = substr($string,$pos,strlen($bItem));
$pos += strlen($bItem) + strlen($fItem);
}
$string = implode($replace[$fKey],$between);
}
return ($string);
}
}
/**
* Locate the position of a string in another string
*
* This function is only used for versions of PHP older than version 5
* which do not have a native version of this function.
* Taken from the PHP manual, by dmarsh @ spscc.ctc.edu
*
* @param string $haystack The string to be searched
* @param string $needle The string to search for
* @param int $offset The position in $haystack where the search should begin.
*/
if (!function_exists('stripos')) { /// Only exists in PHP 5
function stripos($haystack, $needle, $offset=0) {
return strpos(strtoupper($haystack), strtoupper($needle), $offset);
}
}
/**
* This function will print a button/link/etc. form element
* that will work on both Javascript and non-javascript browsers.
* Relies on the Javascript function openpopup in javascript.php
*
* All parameters default to null, only $type and $url are mandatory.
*
* $url must be relative to home page eg /mod/survey/stuff.php
* @param string $url Web link relative to home page
* @param string $name Name to be assigned to the popup window (this is used by
* client-side scripts to "talk" to the popup window)
* @param string $linkname Text to be displayed as web link
* @param int $height Height to assign to popup window
* @param int $width Height to assign to popup window
* @param string $title Text to be displayed as popup page title
* @param string $options List of additional options for popup window
* @param string $return If true, return as a string, otherwise print
* @param string $id id added to the element
* @param string $class class added to the element
* @return string
* @uses $CFG
*/
function element_to_popup_window ($type=null, $url=null, $name=null, $linkname=null,
$height=400, $width=500, $title=null,
$options=null, $return=false, $id=null, $class=null) {
if (is_null($url)) {
debugging('You must give the url to display in the popup. URL is missing - can\'t create popup window.', DEBUG_DEVELOPER);
}
global $CFG;
if ($options == 'none') { // 'none' is legacy, should be removed in v2.0
$options = null;
}
// add some sane default options for popup windows
if (!$options) {
$options = 'menubar=0,location=0,scrollbars,resizable';
}
if ($width) {
$options .= ',width='. $width;
}
if ($height) {
$options .= ',height='. $height;
}
if ($id) {
$id = ' id="'.$id.'" ';
}
if ($class) {
$class = ' class="'.$class.'" ';
}
if ($name) {
$_name = $name;
if (($name = preg_replace("/\s/", '_', $name)) != $_name) {
debugging('The $name of a popup window shouldn\'t contain spaces - string modified. '. $_name .' changed to '. $name, DEBUG_DEVELOPER);
}
} else {
$name = 'popup';
}
// get some default string, using the localized version of legacy defaults
if (is_null($linkname) || $linkname === '') {
$linkname = get_string('clickhere');
}
if (!$title) {
$title = get_string('popupwindowname');
}
$fullscreen = 0; // must be passed to openpopup
$element = '';
switch ($type) {
case 'button' :
$element = '<input type="button" name="'. $name .'" title="'. $title .'" value="'. $linkname .'" '. $id . $class .
"onclick=\"return openpopup('$url', '$name', '$options', $fullscreen);\" />\n";
break;
case 'link' :
// some log url entries contain _SERVER[HTTP_REFERRER] in which case wwwroot is already there.
if (!(strpos($url,$CFG->wwwroot) === false)) {
$url = substr($url, strlen($CFG->wwwroot));
}
$element = '<a title="'. s(strip_tags($title)) .'" href="'. $CFG->wwwroot . $url .'" '.
"$CFG->frametarget onclick=\"this.target='$name'; return openpopup('$url', '$name', '$options', $fullscreen);\">$linkname</a>";
break;
default :
error('Undefined element - can\'t create popup window.');
break;
}
if ($return) {
return $element;
} else {
echo $element;
}
}
/**
* Creates and displays (or returns) a link to a popup window, using element_to_popup_window function.
*
* @return string html code to display a link to a popup window.
* @see element_to_popup_window()
*/
function link_to_popup_window ($url, $name=null, $linkname=null,
$height=400, $width=500, $title=null,
$options=null, $return=false) {
return element_to_popup_window('link', $url, $name, $linkname, $height, $width, $title, $options, $return, null, null);
}
/**
* Creates and displays (or returns) a buttons to a popup window, using element_to_popup_window function.
*
* @return string html code to display a button to a popup window.
* @see element_to_popup_window()
*/
function button_to_popup_window ($url, $name=null, $linkname=null,
$height=400, $width=500, $title=null, $options=null, $return=false,
$id=null, $class=null) {
return element_to_popup_window('button', $url, $name, $linkname, $height, $width, $title, $options, $return, $id, $class);
}
/**
* Prints a simple button to close a window
* @param string $name name of the window to close
* @param boolean $return whether this function should return a string or output it
* @return string if $return is true, nothing otherwise
*/
function close_window_button($name='closewindow', $return=false) {
global $CFG;
$output = '';
$output .= '<div class="closewindow">' . "\n";
$output .= '<form action="#"><div>';
$output .= '<input type="button" onclick="self.close();" value="'.get_string($name).'" />';
$output .= '</div></form>';
$output .= '</div>' . "\n";
if ($return) {
return $output;
} else {
echo $output;
}
}
/*
* Try and close the current window immediately using Javascript
* @param int $delay the delay in seconds before closing the window
*/
function close_window($delay=0) {
?>
<script type="text/javascript">
//<![CDATA[
function close_this_window() {
self.close();
}
setTimeout("close_this_window()", <?php echo $delay * 1000 ?>);
//]]>
</script>
<noscript><center>
<?php print_string('pleaseclose') ?>
</center></noscript>
<?php
die;
}
/**
* Given an array of values, output the HTML for a select element with those options.
* Normally, you only need to use the first few parameters.
*
* @param array $options The options to offer. An array of the form
* $options[{value}] = {text displayed for that option};
* @param string $name the name of this form control, as in &lt;select name="..." ...
* @param string $selected the option to select initially, default none.
* @param string $nothing The label for the 'nothing is selected' option. Defaults to get_string('choose').
* Set this to '' if you don't want a 'nothing is selected' option.
* @param string $script in not '', then this is added to the &lt;select> element as an onchange handler.
* @param string $nothingvalue The value corresponding to the $nothing option. Defaults to 0.
* @param boolean $return if false (the default) the the output is printed directly, If true, the
* generated HTML is returned as a string.
* @param boolean $disabled if true, the select is generated in a disabled state. Default, false.
* @param int $tabindex if give, sets the tabindex attribute on the &lt;select> element. Default none.
* @param string $id value to use for the id attribute of the &lt;select> element. If none is given,
* then a suitable one is constructed.
* @param mixed $listbox if false, display as a dropdown menu. If true, display as a list box.
* By default, the list box will have a number of rows equal to min(10, count($options)), but if
* $listbox is an integer, that number is used for size instead.
* @param boolean $multiple if true, enable multiple selections, else only 1 item can be selected. Used
* when $listbox display is enabled
* @param string $class value to use for the class attribute of the &lt;select> element. If none is given,
* then a suitable one is constructed.
*/
function choose_from_menu ($options, $name, $selected='', $nothing='choose', $script='',
$nothingvalue='0', $return=false, $disabled=false, $tabindex=0,
$id='', $listbox=false, $multiple=false, $class='') {
if ($nothing == 'choose') {
$nothing = get_string('choose') .'...';
}
$attributes = ($script) ? 'onchange="'. $script .'"' : '';
if ($disabled) {
$attributes .= ' disabled="disabled"';
}
if ($tabindex) {
$attributes .= ' tabindex="'.$tabindex.'"';
}
if ($id ==='') {
$id = 'menu'.$name;
// name may contaion [], which would make an invalid id. e.g. numeric question type editing form, assignment quickgrading
$id = str_replace('[', '', $id);
$id = str_replace(']', '', $id);
}
if ($class ==='') {
$class = 'menu'.$name;
// name may contaion [], which would make an invalid class. e.g. numeric question type editing form, assignment quickgrading
$class = str_replace('[', '', $class);
$class = str_replace(']', '', $class);
}
$class = 'select ' . $class; /// Add 'select' selector always
if ($listbox) {
if (is_integer($listbox)) {
$size = $listbox;
} else {
$numchoices = count($options);
if ($nothing) {
$numchoices += 1;
}
$size = min(10, $numchoices);
}
$attributes .= ' size="' . $size . '"';
if ($multiple) {
$attributes .= ' multiple="multiple"';
}
}
$output = '<select id="'. $id .'" class="'. $class .'" name="'. $name .'" '. $attributes .'>' . "\n";
if ($nothing) {
$output .= ' <option value="'. s($nothingvalue) .'"'. "\n";
if ($nothingvalue === $selected) {
$output .= ' selected="selected"';
}
$output .= '>'. $nothing .'</option>' . "\n";
}
if (!empty($options)) {
foreach ($options as $value => $label) {
$output .= ' <option value="'. s($value) .'"';
if ((string)$value == (string)$selected ||
(is_array($selected) && in_array($value, $selected))) {
$output .= ' selected="selected"';
}
if ($label === '') {
$output .= '>'. $value .'</option>' . "\n";
} else {
$output .= '>'. $label .'</option>' . "\n";
}
}
}
$output .= '</select>' . "\n";
if ($return) {
return $output;
} else {
echo $output;
}
}
/**
* Choose value 0 or 1 from a menu with options 'No' and 'Yes'.
* Other options like choose_from_menu.
* @param string $name
* @param string $selected
* @param string $string (defaults to '')
* @param boolean $return whether this function should return a string or output it (defaults to false)
* @param boolean $disabled (defaults to false)
* @param int $tabindex
*/
function choose_from_menu_yesno($name, $selected, $script = '',
$return = false, $disabled = false, $tabindex = 0) {
return choose_from_menu(array(get_string('no'), get_string('yes')), $name,
$selected, '', $script, '0', $return, $disabled, $tabindex);
}
/**
* Just like choose_from_menu, but takes a nested array (2 levels) and makes a dropdown menu
* including option headings with the first level.
*/
function choose_from_menu_nested($options,$name,$selected='',$nothing='choose',$script = '',
$nothingvalue=0,$return=false,$disabled=false,$tabindex=0) {
if ($nothing == 'choose') {
$nothing = get_string('choose') .'...';
}
$attributes = ($script) ? 'onchange="'. $script .'"' : '';
if ($disabled) {
$attributes .= ' disabled="disabled"';
}
if ($tabindex) {
$attributes .= ' tabindex="'.$tabindex.'"';
}
$output = '<select id="menu'.$name.'" name="'. $name .'" '. $attributes .'>' . "\n";
if ($nothing) {
$output .= ' <option value="'. $nothingvalue .'"'. "\n";
if ($nothingvalue === $selected) {
$output .= ' selected="selected"';
}
$output .= '>'. $nothing .'</option>' . "\n";
}
if (!empty($options)) {
foreach ($options as $section => $values) {
$output .= ' <optgroup label="'. s(strip_tags(format_string($section))) .'">'."\n";
foreach ($values as $value => $label) {
$output .= ' <option value="'. format_string($value) .'"';
if ((string)$value == (string)$selected) {
$output .= ' selected="selected"';
}
if ($label === '') {
$output .= '>'. $value .'</option>' . "\n";
} else {
$output .= '>'. $label .'</option>' . "\n";
}
}
$output .= ' </optgroup>'."\n";
}
}
$output .= '</select>' . "\n";
if ($return) {
return $output;
} else {
echo $output;
}
}
/**
* Given an array of values, creates a group of radio buttons to be part of a form
*
* @param array $options An array of value-label pairs for the radio group (values as keys)
* @param string $name Name of the radiogroup (unique in the form)
* @param string $checked The value that is already checked
*/
function choose_from_radio ($options, $name, $checked='', $return=false) {
static $idcounter = 0;
if (!$name) {
$name = 'unnamed';
}
$output = '<span class="radiogroup '.$name."\">\n";
if (!empty($options)) {
$currentradio = 0;
foreach ($options as $value => $label) {
$htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter);
$output .= ' <span class="radioelement '.$name.' rb'.$currentradio."\">";
$output .= '<input name="'.$name.'" id="'.$htmlid.'" type="radio" value="'.$value.'"';
if ($value == $checked) {
$output .= ' checked="checked"';
}
if ($label === '') {
$output .= ' /> <label for="'.$htmlid.'">'. $value .'</label></span>' . "\n";
} else {
$output .= ' /> <label for="'.$htmlid.'">'. $label .'</label></span>' . "\n";
}
$currentradio = ($currentradio + 1) % 2;
}
}
$output .= '</span>' . "\n";
if ($return) {
return $output;
} else {
echo $output;
}
}
/** Display an standard html checkbox with an optional label
*
* @param string $name The name of the checkbox
* @param string $value The valus that the checkbox will pass when checked
* @param boolean $checked The flag to tell the checkbox initial state
* @param string $label The label to be showed near the checkbox
* @param string $alt The info to be inserted in the alt tag
*/
function print_checkbox ($name, $value, $checked = true, $label = '', $alt = '', $script='',$return=false) {
static $idcounter = 0;
if (!$name) {
$name = 'unnamed';
}
if ($alt) {
$alt = strip_tags($alt);
} else {
$alt = 'checkbox';
}
if ($checked) {
$strchecked = ' checked="checked"';
} else {
$strchecked = '';
}
$htmlid = 'auto-cb'.sprintf('%04d', ++$idcounter);
$output = '<span class="checkbox '.$name."\">";
$output .= '<input name="'.$name.'" id="'.$htmlid.'" type="checkbox" value="'.$value.'" alt="'.$alt.'"'.$strchecked.' '.((!empty($script)) ? ' onclick="'.$script.'" ' : '').' />';
if(!empty($label)) {
$output .= ' <label for="'.$htmlid.'">'.$label.'</label>';
}
$output .= '</span>'."\n";
if (empty($return)) {
echo $output;
} else {
return $output;
}
}
/** Display an standard html text field with an optional label
*
* @param string $name The name of the text field
* @param string $value The value of the text field
* @param string $label The label to be showed near the text field
* @param string $alt The info to be inserted in the alt tag
*/
function print_textfield ($name, $value, $alt = '',$size=50,$maxlength=0, $return=false) {
static $idcounter = 0;
if (empty($name)) {
$name = 'unnamed';
}
if (empty($alt)) {
$alt = 'textfield';
}
if (!empty($maxlength)) {
$maxlength = ' maxlength="'.$maxlength.'" ';
}
$htmlid = 'auto-tf'.sprintf('%04d', ++$idcounter);
$output = '<span class="textfield '.$name."\">";
$output .= '<input name="'.$name.'" id="'.$htmlid.'" type="text" value="'.$value.'" size="'.$size.'" '.$maxlength.' alt="'.$alt.'" />';
$output .= '</span>'."\n";
if (empty($return)) {
echo $output;
} else {
return $output;
}
}
/**
* Implements a complete little popup form
*
* @uses $CFG
* @param string $common The URL up to the point of the variable that changes
* @param array $options Alist of value-label pairs for the popup list
* @param string $formid Id must be unique on the page (originaly $formname)
* @param string $selected The option that is already selected
* @param string $nothing The label for the "no choice" option
* @param string $help The name of a help page if help is required
* @param string $helptext The name of the label for the help button
* @param boolean $return Indicates whether the function should return the text
* as a string or echo it directly to the page being rendered
* @param string $targetwindow The name of the target page to open the linked page in.
* @param string $selectlabel Text to place in a [label] element - preferred for accessibility.
* @param array $optionsextra TODO, an array?
* @param mixed $gobutton If set, this turns off the JavaScript and uses a 'go'
* button instead (as is always included for JS-disabled users). Set to true
* for a literal 'Go' button, or to a string to change the name of the button.
* @return string If $return is true then the entire form is returned as a string.
* @todo Finish documenting this function<br>
*/
function popup_form($common, $options, $formid, $selected='', $nothing='choose', $help='', $helptext='', $return=false,
$targetwindow='self', $selectlabel='', $optionsextra=NULL, $gobutton=NULL) {
global $CFG;
static $go, $choose; /// Locally cached, in case there's lots on a page
if (empty($options)) {
return '';
}
if (!isset($go)) {
$go = get_string('go');
}
if ($nothing == 'choose') {
if (!isset($choose)) {
$choose = get_string('choose');
}
$nothing = $choose.'...';
}
// changed reference to document.getElementById('id_abc') instead of document.abc
// MDL-7861
$output = '<form action="'.$CFG->wwwroot.'/course/jumpto.php"'.
' method="get" '.
$CFG->frametarget.
' id="'.$formid.'"'.
' class="popupform">';
if ($help) {
$button = helpbutton($help, $helptext, 'moodle', true, false, '', true);
} else {
$button = '';
}
if ($selectlabel) {
$selectlabel = '<label for="'.$formid.'_jump">'.$selectlabel.'</label>';
}
if ($gobutton) {
// Using the no-JavaScript version
$javascript = '';
} else if (check_browser_version('MSIE') || (check_browser_version('Opera') && !check_browser_operating_system("Linux"))) {
//IE and Opera fire the onchange when ever you move into a dropdown list with the keyboard.
//onfocus will call a function inside dropdown.js. It fixes this IE/Opera behavior.
//Note: There is a bug on Opera+Linux with the javascript code (first mouse selection is inactive),
//so we do not fix the Opera behavior on Linux
$javascript = ' onfocus="initSelect(\''.$formid.'\','.$targetwindow.')"';
} else {
//Other browser
$javascript = ' onchange="'.$targetwindow.
'.location=document.getElementById(\''.$formid.
'\').jump.options[document.getElementById(\''.
$formid.'\').jump.selectedIndex].value;"';
}
$output .= '<div>'.$selectlabel.$button.'<select id="'.$formid.'_jump" name="jump"'.$javascript.'>'."\n";
if ($nothing != '') {
$output .= " <option value=\"javascript:void(0)\">$nothing</option>\n";
}
$inoptgroup = false;
foreach ($options as $value => $label) {
if ($label == '--') { /// we are ending previous optgroup
/// Check to see if we already have a valid open optgroup
/// XHTML demands that there be at least 1 option within an optgroup
if ($inoptgroup and (count($optgr) > 1) ) {
$output .= implode('', $optgr);
$output .= ' </optgroup>';
}
$optgr = array();
$inoptgroup = false;
continue;
} else if (substr($label,0,2) == '--') { /// we are starting a new optgroup
/// Check to see if we already have a valid open optgroup
/// XHTML demands that there be at least 1 option within an optgroup
if ($inoptgroup and (count($optgr) > 1) ) {
$output .= implode('', $optgr);
$output .= ' </optgroup>';
}
unset($optgr);
$optgr = array();
$optgr[] = ' <optgroup label="'. s(format_string(substr($label,2))) .'">'; // Plain labels
$inoptgroup = true; /// everything following will be in an optgroup
continue;
} else {
if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()]))
{
$url=sid_process_url( $common . $value );
} else
{
$url=$common . $value;
}
$optstr = ' <option value="' . $url . '"';
if ($value == $selected) {
$optstr .= ' selected="selected"';
}
if (!empty($optionsextra[$value])) {
$optstr .= ' '.$optionsextra[$value];
}
if ($label) {
$optstr .= '>'. $label .'</option>' . "\n";
} else {
$optstr .= '>'. $value .'</option>' . "\n";
}
if ($inoptgroup) {
$optgr[] = $optstr;
} else {
$output .= $optstr;
}
}
}
/// catch the final group if not closed
if ($inoptgroup and count($optgr) > 1) {
$output .= implode('', $optgr);
$output .= ' </optgroup>';
}
$output .= '</select>';
$output .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
if ($gobutton) {
$output .= '<input type="submit" value="'.
($gobutton===true ? $go : $gobutton).'" />';
} else {
$output .= '<div id="noscript'.$formid.'" style="display: inline;">';
$output .= '<input type="submit" value="'.$go.'" /></div>';
$output .= '<script type="text/javascript">'.
"\n//<![CDATA[\n".
'document.getElementById("noscript'.$formid.'").style.display = "none";'.
"\n//]]>\n".'</script>';
}
$output .= '</div></form>';
if ($return) {
return $output;
} else {
echo $output;
}
}
/**
* Prints some red text
*
* @param string $error The text to be displayed in red
*/
function formerr($error) {
if (!empty($error)) {
echo '<span class="error">'. $error .'</span>';
}
}
/**
* Validates an email to make sure it makes sense.
*
* @param string $address The email address to validate.
* @return boolean
*/
function validate_email($address) {
return (ereg('^[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+'.
'(\.[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+)*'.
'@'.
'[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.
'[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$',
$address));
}
/**
* Extracts file argument either from file parameter or PATH_INFO
*
* @param string $scriptname name of the calling script
* @return string file path (only safe characters)
*/
function get_file_argument($scriptname) {
global $_SERVER;
$relativepath = FALSE;
// first try normal parameter (compatible method == no relative links!)
$relativepath = optional_param('file', FALSE, PARAM_PATH);
if ($relativepath === '/testslasharguments') {
echo 'test -1 : Incorrect use - try "file.php/testslasharguments" instead'; //indicate fopen/fread works for health center
die;
}
// then try extract file from PATH_INFO (slasharguments method)
if (!$relativepath and !empty($_SERVER['PATH_INFO'])) {
$path_info = $_SERVER['PATH_INFO'];
// check that PATH_INFO works == must not contain the script name
if (!strpos($path_info, $scriptname)) {
$relativepath = clean_param(rawurldecode($path_info), PARAM_PATH);
if ($relativepath === '/testslasharguments') {
echo 'test 1 : Slasharguments test passed. Server confguration is compatible with file.php/1/pic.jpg slashargument setting.'; //indicate ok for health center
die;
}
}
}
// now if both fail try the old way
// (for compatibility with misconfigured or older buggy php implementations)
if (!$relativepath) {
$arr = explode($scriptname, me());
if (!empty($arr[1])) {
$path_info = strip_querystring($arr[1]);
$relativepath = clean_param(rawurldecode($path_info), PARAM_PATH);
if ($relativepath === '/testslasharguments') {
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
die;
}
}
}
return $relativepath;
}
/**
* Searches the current environment variables for some slash arguments
*
* @param string $file ?
* @todo Finish documenting this function
*/
function get_slash_arguments($file='file.php') {
if (!$string = me()) {
return false;
}
$pathinfo = explode($file, $string);
if (!empty($pathinfo[1])) {
return addslashes($pathinfo[1]);
} else {
return false;
}
}
/**
* Extracts arguments from "/foo/bar/something"
* eg http://mysite.com/script.php/foo/bar/something
*
* @param string $string ?
* @param int $i ?
* @return array|string
* @todo Finish documenting this function
*/
function parse_slash_arguments($string, $i=0) {
if (detect_munged_arguments($string)) {
return false;
}
$args = explode('/', $string);
if ($i) { // return just the required argument
return $args[$i];
} else { // return the whole array
array_shift($args); // get rid of the empty first one
return $args;
}
}
/**
* Just returns an array of text formats suitable for a popup menu
*
* @uses FORMAT_MOODLE
* @uses FORMAT_HTML
* @uses FORMAT_PLAIN
* @uses FORMAT_MARKDOWN
* @return array
*/
function format_text_menu() {
return array (FORMAT_MOODLE => get_string('formattext'),
FORMAT_HTML => get_string('formathtml'),
FORMAT_PLAIN => get_string('formatplain'),
FORMAT_MARKDOWN => get_string('formatmarkdown'));
}
/**
* Given text in a variety of format codings, this function returns
* the text as safe HTML.
*
* This function should mainly be used for long strings like posts,
* answers, glossary items etc. For short strings @see format_string().
*
* @uses $CFG
* @uses FORMAT_MOODLE
* @uses FORMAT_HTML
* @uses FORMAT_PLAIN
* @uses FORMAT_WIKI
* @uses FORMAT_MARKDOWN
* @param string $text The text to be formatted. This is raw text originally from user input.
* @param int $format Identifier of the text format to be used
* (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN)
* @param array $options ?
* @param int $courseid ?
* @return string
* @todo Finish documenting this function
*/
function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
global $CFG, $COURSE;
static $croncache = array();
if ($text === '') {
return ''; // no need to do any filters and cleaning
}
if (!isset($options->trusttext)) {
$options->trusttext = false;
}
if (!isset($options->noclean)) {
$options->noclean=false;
}
if (!isset($options->nocache)) {
$options->nocache=false;
}
if (!isset($options->smiley)) {
$options->smiley=true;
}
if (!isset($options->filter)) {
$options->filter=true;
}
if (!isset($options->para)) {
$options->para=true;
}
if (!isset($options->newlines)) {
$options->newlines=true;
}
if (empty($courseid)) {
$courseid = $COURSE->id;
}
if (!empty($CFG->cachetext) and empty($options->nocache)) {
$time = time() - $CFG->cachetext;
$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);
if (defined('FULLME') and FULLME == 'cron') {
if (isset($croncache[$md5key])) {
return $croncache[$md5key];
}
}
if ($oldcacheitem = get_record_sql('SELECT * FROM '.$CFG->prefix.'cache_text WHERE md5key = \''.$md5key.'\'', true)) {
if ($oldcacheitem->timemodified >= $time) {
if (defined('FULLME') and FULLME == 'cron') {
if (count($croncache) > 150) {
reset($croncache);
$key = key($croncache);
unset($croncache[$key]);
}
$croncache[$md5key] = $oldcacheitem->formattedtext;
}
return $oldcacheitem->formattedtext;
}
}
}
// trusttext overrides the noclean option!
if ($options->trusttext) {
if (trusttext_present($text)) {
$text = trusttext_strip($text);
if (!empty($CFG->enabletrusttext)) {
$options->noclean = true;
} else {
$options->noclean = false;
}
} else {
$options->noclean = false;
}
} else if (!debugging('', DEBUG_DEVELOPER)) {
// strip any forgotten trusttext in non-developer mode
// do not forget to disable text cache when debugging trusttext!!
$text = trusttext_strip($text);
}
$CFG->currenttextiscacheable = true; // Default status - can be changed by any filter
switch ($format) {
case FORMAT_HTML:
if ($options->smiley) {
replace_smilies($text);
}
if (!$options->noclean) {
$text = clean_text($text, FORMAT_HTML);
}
if ($options->filter) {
$text = filter_text($text, $courseid);
}
break;
case FORMAT_PLAIN:
$text = s($text); // cleans dangerous JS
$text = rebuildnolinktag($text);
$text = str_replace(' ', '&nbsp; ', $text);
$text = nl2br($text);
break;
case FORMAT_WIKI:
// this format is deprecated
$text = '<p>NOTICE: Wiki-like formatting has been removed from Moodle. You should not be seeing
this message as all texts should have been converted to Markdown format instead.
Please post a bug report to http://moodle.org/bugs with information about where you
saw this message.</p>'.s($text);
break;
case FORMAT_MARKDOWN:
$text = markdown_to_html($text);
if ($options->smiley) {
replace_smilies($text);
}
if (!$options->noclean) {
$text = clean_text($text, FORMAT_HTML);
}
if ($options->filter) {
$text = filter_text($text, $courseid);
}
break;
default: // FORMAT_MOODLE or anything else
$text = text_to_html($text, $options->smiley, $options->para, $options->newlines);
if (!$options->noclean) {
$text = clean_text($text, FORMAT_HTML);
}
if ($options->filter) {
$text = filter_text($text, $courseid);
}
break;
}
if (empty($options->nocache) and !empty($CFG->cachetext) and $CFG->currenttextiscacheable) {
if (defined('FULLME') and FULLME == 'cron') {
// special static cron cache - no need to store it in db if its not already there
if (count($croncache) > 150) {
reset($croncache);
$key = key($croncache);
unset($croncache[$key]);
}
$croncache[$md5key] = $text;
return $text;
}
$newcacheitem = new object();
$newcacheitem->md5key = $md5key;
$newcacheitem->formattedtext = addslashes($text);
$newcacheitem->timemodified = time();
if ($oldcacheitem) { // See bug 4677 for discussion
$newcacheitem->id = $oldcacheitem->id;
@update_record('cache_text', $newcacheitem); // Update existing record in the cache table
// It's unlikely that the cron cache cleaner could have
// deleted this entry in the meantime, as it allows
// some extra time to cover these cases.
} else {
@insert_record('cache_text', $newcacheitem); // Insert a new record in the cache table
// Again, it's possible that another user has caused this
// record to be created already in the time that it took
// to traverse this function. That's OK too, as the
// call above handles duplicate entries, and eventually
// the cron cleaner will delete them.
}
}
return $text;
}
/** Converts the text format from the value to the 'internal'
* name or vice versa. $key can either be the value or the name
* and you get the other back.
*
* @param mixed int 0-4 or string one of 'moodle','html','plain','markdown'
* @return mixed as above but the other way around!
*/
function text_format_name( $key ) {
$lookup = array();
$lookup[FORMAT_MOODLE] = 'moodle';
$lookup[FORMAT_HTML] = 'html';
$lookup[FORMAT_PLAIN] = 'plain';
$lookup[FORMAT_MARKDOWN] = 'markdown';
$value = "error";
if (!is_numeric($key)) {
$key = strtolower( $key );
$value = array_search( $key, $lookup );
}
else {
if (isset( $lookup[$key] )) {
$value = $lookup[ $key ];
}
}
return $value;
}
/**
* Resets all data related to filters, called during upgrade or when filter settings change.
* @return void
*/
function reset_text_filters_cache() {
global $CFG;
delete_records('cache_text');
$purifdir = $CFG->dataroot.'/cache/htmlpurifier';
remove_dir($purifdir, true);
}
/** Given a simple string, this function returns the string
* processed by enabled string filters if $CFG->filterall is enabled
*
* This function should be used to print short strings (non html) that
* need filter processing e.g. activity titles, post subjects,
* glossary concepts.
*
* @param string $string The string to be filtered.
* @param boolean $striplinks To strip any link in the result text (Moodle 1.8 default changed from false to true! MDL-8713)
* @param int $courseid Current course as filters can, potentially, use it
* @return string
*/
function format_string ($string, $striplinks=true, $courseid=NULL ) {
global $CFG, $COURSE;
//We'll use a in-memory cache here to speed up repeated strings
static $strcache = false;
if ($strcache === false or count($strcache) > 2000 ) { // this number might need some tuning to limit memory usage in cron
$strcache = array();
}
//init course id
if (empty($courseid)) {
$courseid = $COURSE->id;
}
//Calculate md5
$md5 = md5($string.'<+>'.$striplinks.'<+>'.$courseid.'<+>'.current_language());
//Fetch from cache if possible
if (isset($strcache[$md5])) {
return $strcache[$md5];
}
// First replace all ampersands not followed by html entity code
$string = preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&amp;", $string);
if (!empty($CFG->filterall)) {
$string = filter_string($string, $courseid);
}
// If the site requires it, strip ALL tags from this string
if (!empty($CFG->formatstringstriptags)) {
$string = strip_tags($string);
} else {
// Otherwise strip just links if that is required (default)
if ($striplinks) { //strip links in string
$string = preg_replace('/(<a\s[^>]+?>)(.+?)(<\/a>)/is','$2',$string);
}
$string = clean_text($string);
}
//Store to cache
$strcache[$md5] = $string;
return $string;
}
/**
* Given text in a variety of format codings, this function returns
* the text as plain text suitable for plain email.
*
* @uses FORMAT_MOODLE
* @uses FORMAT_HTML
* @uses FORMAT_PLAIN
* @uses FORMAT_WIKI
* @uses FORMAT_MARKDOWN
* @param string $text The text to be formatted. This is raw text originally from user input.
* @param int $format Identifier of the text format to be used
* (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN)
* @return string
*/
function format_text_email($text, $format) {
switch ($format) {
case FORMAT_PLAIN:
return $text;
break;
case FORMAT_WIKI:
$text = wiki_to_html($text);
/// This expression turns links into something nice in a text format. (Russell Jungwirth)
/// From: http://php.net/manual/en/function.eregi-replace.php and simplified
$text = eregi_replace('(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)','\\3 [ \\2 ]', $text);
return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
break;
case FORMAT_HTML:
return html_to_text($text);
break;
case FORMAT_MOODLE:
case FORMAT_MARKDOWN:
default:
$text = eregi_replace('(<a [^<]*href=["|\']?([^ "\']*)["|\']?[^>]*>([^<]*)</a>)','\\3 [ \\2 ]', $text);
return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
break;
}
}
/**
* Given some text in HTML format, this function will pass it
* through any filters that have been defined in $CFG->textfilterx
* The variable defines a filepath to a file containing the
* filter function. The file must contain a variable called
* $textfilter_function which contains the name of the function
* with $courseid and $text parameters
*
* @param string $text The text to be passed through format filters
* @param int $courseid ?
* @return string
* @todo Finish documenting this function
*/
function filter_text($text, $courseid=NULL) {
global $CFG, $COURSE;
if (empty($courseid)) {
$courseid = $COURSE->id; // (copied from format_text)
}
if (!empty($CFG->textfilters)) {
require_once($CFG->libdir.'/filterlib.php');
$textfilters = explode(',', $CFG->textfilters);
foreach ($textfilters as $textfilter) {
if (is_readable($CFG->dirroot .'/'. $textfilter .'/filter.php')) {
include_once($CFG->dirroot .'/'. $textfilter .'/filter.php');
$functionname = basename($textfilter).'_filter';
if (function_exists($functionname)) {
$text = $functionname($courseid, $text);
}
}
}
}
/// <nolink> tags removed for XHTML compatibility
$text = str_replace('<nolink>', '', $text);
$text = str_replace('</nolink>', '', $text);
return $text;
}
/**
* Given a string (short text) in HTML format, this function will pass it
* through any filters that have been defined in $CFG->stringfilters
* The variable defines a filepath to a file containing the
* filter function. The file must contain a variable called
* $textfilter_function which contains the name of the function
* with $courseid and $text parameters
*
* @param string $string The text to be passed through format filters
* @param int $courseid The id of a course
* @return string
*/
function filter_string($string, $courseid=NULL) {
global $CFG, $COURSE;
if (empty($CFG->textfilters)) { // All filters are disabled anyway so quit
return $string;
}
if (empty($courseid)) {
$courseid = $COURSE->id;
}
require_once($CFG->libdir.'/filterlib.php');
if (isset($CFG->stringfilters)) { // We have a predefined list to use, great!
if (empty($CFG->stringfilters)) { // but it's blank, so finish now
return $string;
}
$stringfilters = explode(',', $CFG->stringfilters); // ..use the list we have
} else { // Otherwise try to derive a list from textfilters
if (strpos($CFG->textfilters, 'filter/multilang') !== false) { // Multilang is here
$stringfilters = array('filter/multilang'); // Let's use just that
$CFG->stringfilters = 'filter/multilang'; // Save it for next time through
} else {
$CFG->stringfilters = ''; // Save the result and return
return $string;
}
}
foreach ($stringfilters as $stringfilter) {
if (is_readable($CFG->dirroot .'/'. $stringfilter .'/filter.php')) {
include_once($CFG->dirroot .'/'. $stringfilter .'/filter.php');
$functionname = basename($stringfilter).'_filter';
if (function_exists($functionname)) {
$string = $functionname($courseid, $string);
}
}
}
/// <nolink> tags removed for XHTML compatibility
$string = str_replace('<nolink>', '', $string);
$string = str_replace('</nolink>', '', $string);
return $string;
}
/**
* Is the text marked as trusted?
*
* @param string $text text to be searched for TRUSTTEXT marker
* @return boolean
*/
function trusttext_present($text) {
if (strpos($text, TRUSTTEXT) !== FALSE) {
return true;
} else {
return false;
}
}
/**
* This funtion MUST be called before the cleaning or any other
* function that modifies the data! We do not know the origin of trusttext
* in database, if it gets there in tweaked form we must not convert it
* to supported form!!!
*
* Please be carefull not to use stripslashes on data from database
* or twice stripslashes when processing data recieved from user.
*
* @param string $text text that may contain TRUSTTEXT marker
* @return text without any TRUSTTEXT marker
*/
function trusttext_strip($text) {
global $CFG;
while (true) { //removing nested TRUSTTEXT
$orig = $text;
$text = str_replace(TRUSTTEXT, '', $text);
if (strcmp($orig, $text) === 0) {
return $text;
}
}
}
/**
* Mark text as trusted, such text may contain any HTML tags because the
* normal text cleaning will be bypassed.
* Please make sure that the text comes from trusted user before storing
* it into database!
*/
function trusttext_mark($text) {
global $CFG;
if (!empty($CFG->enabletrusttext) and (strpos($text, TRUSTTEXT) === FALSE)) {
return TRUSTTEXT.$text;
} else {
return $text;
}
}
function trusttext_after_edit(&$text, $context) {
if (has_capability('moodle/site:trustcontent', $context)) {
$text = trusttext_strip($text);
$text = trusttext_mark($text);
} else {
$text = trusttext_strip($text);
}
}
function trusttext_prepare_edit(&$text, &$format, $usehtmleditor, $context) {
global $CFG;
$options = new object();
$options->smiley = false;
$options->filter = false;
if (!empty($CFG->enabletrusttext)
and has_capability('moodle/site:trustcontent', $context)
and trusttext_present($text)) {
$options->noclean = true;
} else {
$options->noclean = false;
}
$text = trusttext_strip($text);
if ($usehtmleditor) {
$text = format_text($text, $format, $options);
$format = FORMAT_HTML;
} else if (!$options->noclean){
$text = clean_text($text, $format);
}
}
/**
* Given raw text (eg typed in by a user), this function cleans it up
* and removes any nasty tags that could mess up Moodle pages.
*
* @uses FORMAT_MOODLE
* @uses FORMAT_PLAIN
* @uses ALLOWED_TAGS
* @param string $text The text to be cleaned
* @param int $format Identifier of the text format to be used
* (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN)
* @return string The cleaned up text
*/
function clean_text($text, $format=FORMAT_MOODLE) {
global $ALLOWED_TAGS, $CFG;
if (empty($text) or is_numeric($text)) {
return (string)$text;
}
switch ($format) {
case FORMAT_PLAIN:
case FORMAT_MARKDOWN:
return $text;
default:
if (!empty($CFG->enablehtmlpurifier)) {
$text = purify_html($text);
} else {
/// Fix non standard entity notations
$text = preg_replace('/&#0*([0-9]+);?/', "&#\\1;", $text);
$text = preg_replace('/&#x0*([0-9a-fA-F]+);?/', "&#x\\1;", $text);
/// Remove tags that are not allowed
$text = strip_tags($text, $ALLOWED_TAGS);
/// Clean up embedded scripts and , using kses
$text = cleanAttributes($text);
/// Again remove tags that are not allowed
$text = strip_tags($text, $ALLOWED_TAGS);
}
/// Remove potential script events - some extra protection for undiscovered bugs in our code
$text = eregi_replace("([^a-z])language([[:space:]]*)=", "\\1Xlanguage=", $text);
$text = eregi_replace("([^a-z])on([a-z]+)([[:space:]]*)=", "\\1Xon\\2=", $text);
return $text;
}
}
/**
* KSES replacement cleaning function - uses HTML Purifier.
*/
function purify_html($text) {
global $CFG;
// this can not be done only once because we sometimes need to reset the cache
$cachedir = $CFG->dataroot.'/cache/htmlpurifier/';
$status = check_dir_exists($cachedir, true, true);
static $purifier = false;
if ($purifier === false) {
require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('Core', 'AcceptFullDocuments', false);
$config->set('Core', 'Encoding', 'UTF-8');
$config->set('HTML', 'Doctype', 'XHTML 1.0 Transitional');
$config->set('Cache', 'SerializerPath', $cachedir);
$config->set('URI', 'AllowedSchemes', array('http'=>1, 'https'=>1, 'ftp'=>1, 'irc'=>1, 'nntp'=>1, 'news'=>1, 'rtsp'=>1, 'teamspeak'=>1, 'gopher'=>1, 'mms'=>1));
$config->set('Attr', 'AllowedFrameTargets', array('_blank'));
$purifier = new HTMLPurifier($config);
}
return $purifier->purify($text);
}
/**
* This function takes a string and examines it for HTML tags.
* If tags are detected it passes the string to a helper function {@link cleanAttributes2()}
* which checks for attributes and filters them for malicious content
* 17/08/2004 :: Eamon DOT Costello AT dcu DOT ie
*
* @param string $str The string to be examined for html tags
* @return string
*/
function cleanAttributes($str){
$result = preg_replace_callback(
'%(<[^>]*(>|$)|>)%m', #search for html tags
"cleanAttributes2",
$str
);
return $result;
}
/**
* This function takes a string with an html tag and strips out any unallowed
* protocols e.g. javascript:
* It calls ancillary functions in kses which are prefixed by kses
* 17/08/2004 :: Eamon DOT Costello AT dcu DOT ie
*
* @param array $htmlArray An array from {@link cleanAttributes()}, containing in its 1st
* element the html to be cleared
* @return string
*/
function cleanAttributes2($htmlArray){
global $CFG, $ALLOWED_PROTOCOLS;
require_once($CFG->libdir .'/kses.php');
$htmlTag = $htmlArray[1];
if (substr($htmlTag, 0, 1) != '<') {
return '&gt;'; //a single character ">" detected
}
if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $htmlTag, $matches)) {
return ''; // It's seriously malformed
}
$slash = trim($matches[1]); //trailing xhtml slash
$elem = $matches[2]; //the element name
$attrlist = $matches[3]; // the list of attributes as a string
$attrArray = kses_hair($attrlist, $ALLOWED_PROTOCOLS);
$attStr = '';
foreach ($attrArray as $arreach) {
$arreach['name'] = strtolower($arreach['name']);
if ($arreach['name'] == 'style') {
$value = $arreach['value'];
while (true) {
$prevvalue = $value;
$value = kses_no_null($value);
$value = preg_replace("/\/\*.*\*\//Us", '', $value);
$value = kses_decode_entities($value);
$value = preg_replace('/(&#[0-9]+)(;?)/', "\\1;", $value);
$value = preg_replace('/(&#x[0-9a-fA-F]+)(;?)/', "\\1;", $value);
if ($value === $prevvalue) {
$arreach['value'] = $value;
break;
}
}
$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']);
$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']);
$arreach['value'] = preg_replace("/b\s*i\s*n\s*d\s*i\s*n\s*g/i", "Xbinding", $arreach['value']);
} else if ($arreach['name'] == 'href') {
//Adobe Acrobat Reader XSS protection
$arreach['value'] = preg_replace('/(\.(pdf|fdf|xfdf|xdp|xfd)[^#]*)#.*$/i', '$1', $arreach['value']);
}
$attStr .= ' '.$arreach['name'].'="'.$arreach['value'].'"';
}
$xhtml_slash = '';
if (preg_match('%/\s*$%', $attrlist)) {
$xhtml_slash = ' /';
}
return '<'. $slash . $elem . $attStr . $xhtml_slash .'>';
}
/**
* Replaces all known smileys in the text with image equivalents
*
* @uses $CFG
* @param string $text Passed by reference. The string to search for smily strings.
* @return string
*/
function replace_smilies(&$text) {
global $CFG;
if (empty($CFG->emoticons)) { /// No emoticons defined, nothing to process here
return;
}
$lang = current_language();
$emoticonstring = $CFG->emoticons;
static $e = array();
static $img = array();
static $emoticons = null;
if (is_null($emoticons)) {
$emoticons = array();
if ($emoticonstring) {
$items = explode('{;}', $CFG->emoticons);
foreach ($items as $item) {
$item = explode('{:}', $item);
$emoticons[$item[0]] = $item[1];
}
}
}
if (empty($img[$lang])) { /// After the first time this is not run again
$e[$lang] = array();
$img[$lang] = array();
foreach ($emoticons as $emoticon => $image){
$alttext = get_string($image, 'pix');
$alttext = preg_replace('/^\[\[(.*)\]\]$/', '$1', $alttext); /// Clean alttext in case there isn't lang string for it.
$e[$lang][] = $emoticon;
$img[$lang][] = '<img alt="'. $alttext .'" width="15" height="15" src="'. $CFG->pixpath .'/s/'. $image .'.gif" />';
}
}
// Exclude from transformations all the code inside <script> tags
// Needed to solve Bug 1185. Thanks to jouse 2001 detecting it. :-)
// Based on code from glossary fiter by Williams Castillo.
// - Eloy
// Detect all the <script> zones to take out
$excludes = array();
preg_match_all('/<script language(.+?)<\/script>/is',$text,$list_of_excludes);
// Take out all the <script> zones from text
foreach (array_unique($list_of_excludes[0]) as $key=>$value) {
$excludes['<+'.$key.'+>'] = $value;
}
if ($excludes) {
$text = str_replace($excludes,array_keys($excludes),$text);
}
/// this is the meat of the code - this is run every time
$text = str_replace($e[$lang], $img[$lang], $text);
// Recover all the <script> zones to text
if ($excludes) {
$text = str_replace(array_keys($excludes),$excludes,$text);
}
}
/**
* Given plain text, makes it into HTML as nicely as possible.
* May contain HTML tags already
*
* @uses $CFG
* @param string $text The string to convert.
* @param boolean $smiley Convert any smiley characters to smiley images?
* @param boolean $para If true then the returned string will be wrapped in paragraph tags
* @param boolean $newlines If true then lines newline breaks will be converted to HTML newline breaks.
* @return string
*/
function text_to_html($text, $smiley=true, $para=true, $newlines=true) {
///
global $CFG;
/// Remove any whitespace that may be between HTML tags
$text = eregi_replace(">([[:space:]]+)<", "><", $text);
/// Remove any returns that precede or follow HTML tags
$text = eregi_replace("([\n\r])<", " <", $text);
$text = eregi_replace(">([\n\r])", "> ", $text);
convert_urls_into_links($text);
/// Make returns into HTML newlines.
if ($newlines) {
$text = nl2br($text);
}
/// Turn smileys into images.
if ($smiley) {
replace_smilies($text);
}
/// Wrap the whole thing in a paragraph tag if required
if ($para) {
return '<p>'.$text.'</p>';
} else {
return $text;
}
}
/**
* Given Markdown formatted text, make it into XHTML using external function
*
* @uses $CFG
* @param string $text The markdown formatted text to be converted.
* @return string Converted text
*/
function markdown_to_html($text) {
global $CFG;
require_once($CFG->libdir .'/markdown.php');
return Markdown($text);
}
/**
* Given HTML text, make it into plain text using external function
*
* @uses $CFG
* @param string $html The text to be converted.
* @return string
*/
function html_to_text($html) {
global $CFG;
require_once($CFG->libdir .'/html2text.php');
$h2t = new html2text($html);
$result = $h2t->get_text();
return $result;
}
/**
* Given some text this function converts any URLs it finds into HTML links
*
* @param string $text Passed in by reference. The string to be searched for urls.
*/
function convert_urls_into_links(&$text) {
/// Make lone URLs into links. eg http://moodle.com/
$text = eregi_replace("([[:space:]]|^|\(|\[)([[:alnum:]]+)://([^[:space:]]*)([[:alnum:]#?/&=])",
"\\1<a href=\"\\2://\\3\\4\" target=\"_blank\">\\2://\\3\\4</a>", $text);
/// eg www.moodle.com
$text = eregi_replace("([[:space:]]|^|\(|\[)www\.([^[:space:]]*)([[:alnum:]#?/&=])",
"\\1<a href=\"http://www.\\2\\3\" target=\"_blank\">www.\\2\\3</a>", $text);
}
/**
* This function will highlight search words in a given string
* It cares about HTML and will not ruin links. It's best to use
* this function after performing any conversions to HTML.
*
* @param string $needle The search string. Syntax like "word1 +word2 -word3" is dealt with correctly.
* @param string $haystack The string (HTML) within which to highlight the search terms.
* @param boolean $matchcase whether to do case-sensitive. Default case-insensitive.
* @param string $prefix the string to put before each search term found.
* @param string $suffix the string to put after each search term found.
* @return string The highlighted HTML.
*/
function highlight($needle, $haystack, $matchcase = false,
$prefix = '<span class="highlight">', $suffix = '</span>') {
/// Quick bail-out in trivial cases.
if (empty($needle) or empty($haystack)) {
return $haystack;
}
/// Break up the search term into words, discard any -words and build a regexp.
$words = preg_split('/ +/', trim($needle));
foreach ($words as $index => $word) {
if (strpos($word, '-') === 0) {
unset($words[$index]);
} else if (strpos($word, '+') === 0) {
$words[$index] = '\b' . preg_quote(ltrim($word, '+'), '/') . '\b'; // Match only as a complete word.
} else {
$words[$index] = preg_quote($word, '/');
}
}
$regexp = '/(' . implode('|', $words) . ')/u'; // u is do UTF-8 matching.
if (!$matchcase) {
$regexp .= 'i';
}
/// Another chance to bail-out if $search was only -words
if (empty($words)) {
return $haystack;
}
/// Find all the HTML tags in the input, and store them in a placeholders array.
$placeholders = array();
$matches = array();
preg_match_all('/<[^>]*>/', $haystack, $matches);
foreach (array_unique($matches[0]) as $key => $htmltag) {
$placeholders['<|' . $key . '|>'] = $htmltag;
}
/// In $hastack, replace each HTML tag with the corresponding placeholder.
$haystack = str_replace($placeholders, array_keys($placeholders), $haystack);
/// In the resulting string, Do the highlighting.
$haystack = preg_replace($regexp, $prefix . '$1' . $suffix, $haystack);
/// Turn the placeholders back into HTML tags.
$haystack = str_replace(array_keys($placeholders), $placeholders, $haystack);
return $haystack;
}
/**
* This function will highlight instances of $needle in $haystack
* It's faster that the above function and doesn't care about
* HTML or anything.
*
* @param string $needle The string to search for
* @param string $haystack The string to search for $needle in
* @return string
*/
function highlightfast($needle, $haystack) {
if (empty($needle) or empty($haystack)) {
return $haystack;
}
$parts = explode(moodle_strtolower($needle), moodle_strtolower($haystack));
if (count($parts) === 1) {
return $haystack;
}
$pos = 0;
foreach ($parts as $key => $part) {
$parts[$key] = substr($haystack, $pos, strlen($part));
$pos += strlen($part);
$parts[$key] .= '<span class="highlight">'.substr($haystack, $pos, strlen($needle)).'</span>';
$pos += strlen($needle);
}
return str_replace('<span class="highlight"></span>', '', join('', $parts));
}
/**
* Return a string containing 'lang', xml:lang and optionally 'dir' HTML attributes.
* Internationalisation, for print_header and backup/restorelib.
* @param $dir Default false.
* @return string Attributes.
*/
function get_html_lang($dir = false) {
$direction = '';
if ($dir) {
if (get_string('thisdirection') == 'rtl') {
$direction = ' dir="rtl"';
} else {
$direction = ' dir="ltr"';
}
}
//Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag.
$language = str_replace('_', '-', str_replace('_utf8', '', current_language()));
@header('Content-Language: '.$language);
return ($direction.' lang="'.$language.'" xml:lang="'.$language.'"');
}
/**
* Return the markup for the destination of the 'Skip to main content' links.
* Accessibility improvement for keyboard-only users.
* Used in course formats, /index.php and /course/index.php
* @return string HTML element.
*/
function skip_main_destination() {
return '<span id="maincontent"></span>';
}
/// STANDARD WEB PAGE PARTS ///////////////////////////////////////////////////
/**
* Print a standard header
*
* @uses $USER
* @uses $CFG
* @uses $SESSION
* @param string $title Appears at the top of the window
* @param string $heading Appears at the top of the page
* @param array $navigation Array of $navlinks arrays (keys: name, link, type) for use as breadcrumbs links
* @param string $focus Indicates form element to get cursor focus on load eg inputform.password
* @param string $meta Meta tags to be added to the header
* @param boolean $cache Should this page be cacheable?
* @param string $button HTML code for a button (usually for module editing)
* @param string $menu HTML code for a popup menu
* @param boolean $usexml use XML for this page
* @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc)
* @param bool $return If true, return the visible elements of the header instead of echoing them.
*/
function print_header ($title='', $heading='', $navigation='', $focus='',
$meta='', $cache=true, $button='&nbsp;', $menu='',
$usexml=false, $bodytags='', $return=false) {
global $USER, $CFG, $THEME, $SESSION, $ME, $SITE, $COURSE;
if (gettype($navigation) == 'string' && strlen($navigation) != 0 && $navigation != 'home') {
debugging("print_header() was sent a string as 3rd ($navigation) parameter. "
. "This is deprecated in favour of an array built by build_navigation(). Please upgrade your code.", DEBUG_DEVELOPER);
}
$heading = format_string($heading); // Fix for MDL-8582
/// This makes sure that the header is never repeated twice on a page
if (defined('HEADER_PRINTED')) {
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().');
return;
}
define('HEADER_PRINTED', 'true');
/// Perform a browser environment check for the flash version. Should only run once per login session.
if (isloggedin() && !empty($CFG->excludeoldflashclients) && empty($SESSION->flashversion)) {
// Unfortunately we can't use require_js here and keep it all clean in 1.9 ...
// require_js(array('yui_yahoo', 'yui_event', 'yui_connection', $CFG->httpswwwroot."/lib/swfobject/swfobject.js"));
$meta .= '<script type="text/javascript" src="'.$CFG->wwwroot.'/lib/yui/yahoo/yahoo-min.js"></script>';
$meta .= '<script type="text/javascript" src="'.$CFG->wwwroot.'/lib/yui/event/event-min.js"></script>';
$meta .= '<script type="text/javascript" src="'.$CFG->wwwroot.'/lib/yui/connection/connection-min.js"></script>';
$meta .= '<script type="text/javascript" src="'.$CFG->wwwroot.'/lib/swfobject/swfobject.js"></script>';
$meta .=
"<script type=\"text/javascript\">\n".
" var flashversion = swfobject.getFlashPlayerVersion();\n".
" YAHOO.util.Connect.asyncRequest('GET','".$CFG->wwwroot."/login/environment.php?sesskey=".sesskey()."&amp;flashversion='+flashversion.major+'.'+flashversion.minor+'.'+flashversion.release);\n".
"</script>";
}
/// Add the required stylesheets
$stylesheetshtml = '';
foreach ($CFG->stylesheets as $stylesheet) {
$stylesheetshtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
}
$meta = $stylesheetshtml.$meta;
/// Add the meta page from the themes if any were requested
$metapage = '';
if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) {
ob_start();
include_once($CFG->dirroot.'/theme/standard/meta.php');
$metapage .= ob_get_contents();
ob_end_clean();
}
if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) {
if (file_exists($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php')) {
ob_start();
include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php');
$metapage .= ob_get_contents();
ob_end_clean();
}
}
if (!isset($THEME->metainclude) || $THEME->metainclude) {
if (file_exists($CFG->dirroot.'/theme/'.current_theme().'/meta.php')) {
ob_start();
include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php');
$metapage .= ob_get_contents();
ob_end_clean();
}
}
$meta = $meta."\n".$metapage;
$meta .= "\n".require_js('',1);
/// Set up some navigation variables
if (is_newnav($navigation)){
$home = false;
} else {
if ($navigation == 'home') {
$home = true;
$navigation = '';
} else {
$home = false;
}
}
/// This is another ugly hack to make navigation elements available to print_footer later
$THEME->title = $title;
$THEME->heading = $heading;
$THEME->navigation = $navigation;
$THEME->button = $button;
$THEME->menu = $menu;
$navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : '';
if ($button == '') {
$button = '&nbsp;';
}
if (file_exists($CFG->dataroot.'/'.SITEID.'/maintenance.html')) {
$button = '<a href="'.$CFG->wwwroot.'/'.$CFG->admin.'/maintenance.php">'.get_string('maintenancemode', 'admin').'</a> '.$button;
if(!empty($title)) {
$title .= ' - ';
}
$title .= get_string('maintenancemode', 'admin');
}
if (!$menu and $navigation) {
if (empty($CFG->loginhttps)) {
$wwwroot = $CFG->wwwroot;
} else {
$wwwroot = str_replace('http:','https:',$CFG->wwwroot);
}
$menu = user_login_string($COURSE);
}
if (isset($SESSION->justloggedin)) {
unset($SESSION->justloggedin);
if (!empty($CFG->displayloginfailures)) {
if (!empty($USER->username) and $USER->username != 'guest') {
if ($count = count_login_failures($CFG->displayloginfailures, $USER->username, $USER->lastlogin)) {
$menu .= '&nbsp;<font size="1">';
if (empty($count->accounts)) {
$menu .= get_string('failedloginattempts', '', $count);
} else {
$menu .= get_string('failedloginattemptsall', '', $count);
}
if (has_capability('coursereport/log:view', get_context_instance(CONTEXT_SYSTEM))) {
$menu .= ' (<a href="'.$CFG->wwwroot.'/course/report/log/index.php'.
'?chooselog=1&amp;id=1&amp;modid=site_errors">'.get_string('logs').'</a>)';
}
$menu .= '</font>';
}
}
}
}
$meta = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' .
"\n" . $meta . "\n";
if (!$usexml) {
@header('Content-Type: text/html; charset=utf-8');
}
@header('Content-Script-Type: text/javascript');
@header('Content-Style-Type: text/css');
//Accessibility: added the 'lang' attribute to $direction, used in theme <html> tag.
$direction = get_html_lang($dir=true);
if ($cache) { // Allow caching on "back" (but not on normal clicks)
@header('Cache-Control: private, pre-check=0, post-check=0, max-age=0');
@header('Pragma: no-cache');
@header('Expires: ');
} else { // Do everything we can to always prevent clients and proxies caching
@header('Cache-Control: no-store, no-cache, must-revalidate');
@header('Cache-Control: post-check=0, pre-check=0', false);
@header('Pragma: no-cache');
@header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
@header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
$meta .= "\n<meta http-equiv=\"pragma\" content=\"no-cache\" />";
$meta .= "\n<meta http-equiv=\"expires\" content=\"0\" />";
}
@header('Accept-Ranges: none');
$currentlanguage = current_language();
if (empty($usexml)) {
$direction = ' xmlns="http://www.w3.org/1999/xhtml"'. $direction; // See debug_header
} else {
$mathplayer = preg_match("/MathPlayer/i", $_SERVER['HTTP_USER_AGENT']);
if(!$mathplayer) {
header('Content-Type: application/xhtml+xml');
}
echo '<?xml version="1.0" ?>'."\n";
if (!empty($CFG->xml_stylesheets)) {
$stylesheets = explode(';', $CFG->xml_stylesheets);
foreach ($stylesheets as $stylesheet) {
echo '<?xml-stylesheet type="text/xsl" href="'. $CFG->wwwroot .'/'. $stylesheet .'" ?>' . "\n";
}
}
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1';
if (!empty($CFG->xml_doctype_extra)) {
echo ' plus '. $CFG->xml_doctype_extra;
}
echo '//' . strtoupper($currentlanguage) . '" "'. $CFG->xml_dtd .'">'."\n";
$direction = " xmlns=\"http://www.w3.org/1999/xhtml\"
xmlns:math=\"http://www.w3.org/1998/Math/MathML\"
xmlns:xlink=\"http://www.w3.org/1999/xlink\"
$direction";
if($mathplayer) {
$meta .= '<object id="mathplayer" classid="clsid:32F66A20-7614-11D4-BD11-00104BD3F987">' . "\n";
$meta .= '<!--comment required to prevent this becoming an empty tag-->'."\n";
$meta .= '</object>'."\n";
$meta .= '<?import namespace="math" implementation="#mathplayer" ?>' . "\n";
}
}
// Clean up the title
$title = format_string($title); // fix for MDL-8582
$title = str_replace('"', '&quot;', $title);
// Create class and id for this page
page_id_and_class($pageid, $pageclass);
$pageclass .= ' course-'.$COURSE->id;
if (!isloggedin()) {
$pageclass .= ' notloggedin';
}
if (!empty($USER->editing)) {
$pageclass .= ' editing';
}
if (!empty($CFG->blocksdrag)) {
$pageclass .= ' drag';
}
$pageclass .= ' dir-'.get_string('thisdirection');
$pageclass .= ' lang-'.$currentlanguage;
$bodytags .= ' class="'.$pageclass.'" id="'.$pageid.'"';
ob_start();
include($CFG->header);
$output = ob_get_contents();
ob_end_clean();
// container debugging info
$THEME->open_header_containers = open_containers();
// Skip to main content, see skip_main_destination().
if ($pageid=='course-view' or $pageid=='site-index' or $pageid=='course-index') {
$skiplink = '<a class="skip" href="#maincontent">'.get_string('tocontent', 'access').'</a>';
if (! preg_match('/(.*<div[^>]+id="page"[^>]*>)(.*)/s', $output, $matches)) {
preg_match('/(.*<body.*?>)(.*)/s', $output, $matches);
}
$output = $matches[1]."\n". $skiplink .$matches[2];
}
$output = force_strict_header($output);
if (!empty($CFG->messaging)) {
$output .= message_popup_window();
}
// Add in any extra JavaScript libraries that occurred during the header
$output .= require_js('', 2);
if ($return) {
return $output;
} else {
echo $output;
}
}
/**
* Used to include JavaScript libraries.
*
* When the $lib parameter is given, the function will ensure that the
* named library is loaded onto the page - either in the HTML <head>,
* just after the header, or at an arbitrary later point in the page,
* depending on where this function is called.
*
* Libraries will not be included more than once, so this works like
* require_once in PHP.
*
* There are two special-case calls to this function which are both used only
* by weblib print_header:
* $extracthtml = 1: this is used before printing the header.
* It returns the script tag code that should go inside the <head>.
* $extracthtml = 2: this is used after printing the header and handles any
* require_js calls that occurred within the header itself.
*
* @param mixed $lib - string or array of strings
* string(s) should be the shortname for the library or the
* full path to the library file.
* @param int $extracthtml Do not set this parameter usually (leave 0), only
* weblib should set this to 1 or 2 in print_header function.
* @return mixed No return value, except when using $extracthtml it returns the html code.
*/
function require_js($lib,$extracthtml=0) {
global $CFG;
static $loadlibs = array();
static $state = REQUIREJS_BEFOREHEADER;
static $latecode = '';
if (!empty($lib)) {
// Add the lib to the list of libs to be loaded, if it isn't already
// in the list.
if (is_array($lib)) {
foreach($lib as $singlelib) {
require_js($singlelib);
}
} else {
$libpath = ajax_get_lib($lib);
if (array_search($libpath, $loadlibs) === false) {
$loadlibs[] = $libpath;
// For state other than 0 we need to take action as well as just
// adding it to loadlibs
if($state != REQUIREJS_BEFOREHEADER) {
// Get the script statement for this library
$scriptstatement=get_require_js_code(array($libpath));
if($state == REQUIREJS_AFTERHEADER) {
// After the header, print it immediately
print $scriptstatement;
} else {
// Haven't finished the header yet. Add it after the
// header
$latecode .= $scriptstatement;
}
}
}
}
} else if($extracthtml==1) {
if($state !== REQUIREJS_BEFOREHEADER) {
debugging('Incorrect state in require_js (expected BEFOREHEADER): be careful not to call with empty $lib (except in print_header)');
} else {
$state = REQUIREJS_INHEADER;
}
return get_require_js_code($loadlibs);
} else if($extracthtml==2) {
if($state !== REQUIREJS_INHEADER) {
debugging('Incorrect state in require_js (expected INHEADER): be careful not to call with empty $lib (except in print_header)');
return '';
} else {
$state = REQUIREJS_AFTERHEADER;
return $latecode;
}
} else {
debugging('Unexpected value for $extracthtml');
}
}
/**
* Should not be called directly - use require_js. This function obtains the code
* (script tags) needed to include JavaScript libraries.
* @param array $loadlibs Array of library files to include
* @return string HTML code to include them
*/
function get_require_js_code($loadlibs) {
global $CFG;
// Return the html needed to load the JavaScript files defined in
// our list of libs to be loaded.
$output = '';
foreach ($loadlibs as $loadlib) {
$output .= '<script type="text/javascript" ';
$output .= " src=\"$loadlib\"></script>\n";
if ($loadlib == $CFG->wwwroot.'/lib/yui/logger/logger-min.js') {
// Special case, we need the CSS too.
$output .= '<link type="text/css" rel="stylesheet" ';
$output .= " href=\"{$CFG->wwwroot}/lib/yui/logger/assets/logger.css\" />\n";
}
}
return $output;
}
/**
* Debugging aid: serve page as 'application/xhtml+xml' where possible,
* and substitute the XHTML strict document type.
* Note, requires the 'xmlns' fix in function print_header above.
* See: http://tracker.moodle.org/browse/MDL-7883
* TODO:
*/
function force_strict_header($output) {
global $CFG;
$strict = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
$xsl = '/lib/xhtml.xsl';
if (!headers_sent() && !empty($CFG->xmlstrictheaders)) { // With xml strict headers, the browser will barf
$ctype = 'Content-Type: ';
$prolog= "<?xml version='1.0' encoding='utf-8'?>\n";
if (isset($_SERVER['HTTP_ACCEPT'])
&& false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml')) {
//|| false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') //Safari "Entity 'copy' not defined".
// Firefox et al.
$ctype .= 'application/xhtml+xml';
$prolog .= "<!--\n DEBUG: $ctype \n-->\n";
} else if (file_exists($CFG->dirroot.$xsl)
&& preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
// XSL hack for IE 5+ on Windows.
//$www_xsl = preg_replace('/(http:\/\/.+?\/).*/', '', $CFG->wwwroot) .$xsl;
$www_xsl = $CFG->wwwroot .$xsl;
$ctype .= 'application/xml';
$prolog .= "<?xml-stylesheet type='text/xsl' href='$www_xsl'?>\n";
$prolog .= "<!--\n DEBUG: $ctype \n-->\n";
} else {
//ELSE: Mac/IE, old/non-XML browsers.
$ctype .= 'text/html';
$prolog = '';
}
@header($ctype.'; charset=utf-8');
$output = $prolog . $output;
// Test parser error-handling.
if (isset($_GET['error'])) {
$output .= "__ TEST: XML well-formed error < __\n";
}
}
$output = preg_replace('/(<!DOCTYPE.+?>)/s', $strict, $output); // Always change the DOCTYPE to Strict 1.0
return $output;
}
/**
* This version of print_header is simpler because the course name does not have to be
* provided explicitly in the strings. It can be used on the site page as in courses
* Eventually all print_header could be replaced by print_header_simple
*
* @param string $title Appears at the top of the window
* @param string $heading Appears at the top of the page
* @param string $navigation Premade navigation string (for use as breadcrumbs links)
* @param string $focus Indicates form element to get cursor focus on load eg inputform.password
* @param string $meta Meta tags to be added to the header
* @param boolean $cache Should this page be cacheable?
* @param string $button HTML code for a button (usually for module editing)
* @param string $menu HTML code for a popup menu
* @param boolean $usexml use XML for this page
* @param string $bodytags This text will be included verbatim in the <body> tag (useful for onload() etc)
* @param bool $return If true, return the visible elements of the header instead of echoing them.
*/
function print_header_simple($title='', $heading='', $navigation='', $focus='', $meta='',
$cache=true, $button='&nbsp;', $menu='', $usexml=false, $bodytags='', $return=false) {
global $COURSE, $CFG;
// if we have no navigation specified, build it
if( empty($navigation) ){
$navigation = build_navigation('');
}
// If old style nav prepend course short name otherwise leave $navigation object alone
if (!is_newnav($navigation)) {
if ($COURSE->id != SITEID) {
$shortname = '<a href="'.$CFG->wwwroot.'/course/view.php?id='. $COURSE->id .'">'. $COURSE->shortname .'</a> ->';
$navigation = $shortname.' '.$navigation;
}
}
$output = print_header($COURSE->shortname .': '. $title, $COURSE->fullname .' '. $heading, $navigation, $focus, $meta,
$cache, $button, $menu, $usexml, $bodytags, true);
if ($return) {
return $output;
} else {
echo $output;
}
}
/**
* Can provide a course object to make the footer contain a link to
* to the course home page, otherwise the link will go to the site home
* @uses $USER
* @param mixed $course course object, used for course link button or
* 'none' means no user link, only docs link
* 'empty' means nothing printed in footer
* 'home' special frontpage footer
* @param object $usercourse course used in user link
* @param boolean $return output as string
* @return mixed string or void
*/
function print_footer($course=NULL, $usercourse=NULL, $return=false) {
global $USER, $CFG, $THEME, $COURSE;
if (defined('ADMIN_EXT_HEADER_PRINTED') and !defined('ADMIN_EXT_FOOTER_PRINTED')) {
admin_externalpage_print_footer();
return;
}
/// Course links or special footer
if ($course) {
if ($course === 'empty') {
// special hack - sometimes we do not want even the docs link in footer
$output = '';
if (!empty($THEME->open_header_containers)) {
for ($i=0; $i<$THEME->open_header_containers; $i++) {
$output .= print_container_end_all(); // containers opened from header
}
} else {
//1.8 theme compatibility
$output .= "\n</div>"; // content div
}
$output .= "\n</div>\n</body>\n</html>"; // close page div started in header
if ($return) {
return $output;
} else {
echo $output;
return;
}
} else if ($course === 'none') { // Don't print any links etc
$homelink = '';
$loggedinas = '';
$home = false;
} else if ($course === 'home') { // special case for site home page - please do not remove
$course = get_site();
$homelink = '<div class="sitelink">'.
'<a title="Moodle" href="http://moodle.org/">'.
'<img style="width:100px;height:30px" src="pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
$home = true;
} else {
$homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot.
'/course/view.php?id='.$course->id.'">'.format_string($course->shortname).'</a></div>';
$home = false;
}
} else {
$course = get_site(); // Set course as site course by default
$homelink = '<div class="homelink"><a '.$CFG->frametarget.' href="'.$CFG->wwwroot.'/">'.get_string('home').'</a></div>';
$home = false;
}
/// Set up some other navigation links (passed from print_header by ugly hack)
$menu = isset($THEME->menu) ? str_replace('navmenu', 'navmenufooter', $THEME->menu) : '';
$title = isset($THEME->title) ? $THEME->title : '';
$button = isset($THEME->button) ? $THEME->button : '';
$heading = isset($THEME->heading) ? $THEME->heading : '';
$navigation = isset($THEME->navigation) ? $THEME->navigation : '';
$navmenulist = isset($THEME->navmenulist) ? $THEME->navmenulist : '';
/// Set the user link if necessary
if (!$usercourse and is_object($course)) {
$usercourse = $course;
}
if (!isset($loggedinas)) {
$loggedinas = user_login_string($usercourse, $USER);
}
if ($loggedinas == $menu) {
$menu = '';
}
/// there should be exactly the same number of open containers as after the header
if ($THEME->open_header_containers != open_containers()) {
debugging('Unexpected number of open containers: '.open_containers().', expecting '.$THEME->open_header_containers, DEBUG_DEVELOPER);
}
/// Provide some performance info if required
$performanceinfo = '';
if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
$perf = get_performance_info();
if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdow