';
/**
* Allowed protocols - array of protocols that are safe to use in links and so on
* @global string $ALLOWED_PROTOCOLS
* @name $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()}
*
* @todo Remove obsolete param $obsolete if not used anywhere
*
* @param string $var the string potentially containing HTML characters
* @param boolean $obsolete no longer used.
* @return string
*/
function s($var, $obsolete = false) {
if ($var == '0') { // for integer 0, boolean false, string '0'
return '0';
}
return preg_replace("/&(#\d+);/i", "&$1;", htmlspecialchars($var));
}
/**
* Add quotes to HTML characters
*
* Prints $var with HTML characters (like "<", ">", etc.) properly quoted.
* This function simply calls {@link s()}
* @see s()
*
* @todo Remove obsolete param $obsolete if not used anywhere
*
* @param string $var the string potentially containing HTML characters
* @param boolean $obsolete no longer used.
* @return string
*/
function p($var, $obsolete = false) {
echo s($var, $obsolete);
}
/**
* Does proper javascript quoting.
*
* Do not use addslashes anymore, because it does not work when magic_quotes_sybase is enabled.
*
* @param mixed $var String, Array, or Object to add slashes to
* @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 The remaining URL
*/
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
*
* @uses $_SERVER
* @param boolean $stripquery if true, also removes the query part of the url.
* @return string The resulting referer or emtpy 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.)
* NOTE: This function returns false if the global variables needed are not set.
*
* @global string
* @return mixed String, or false if the global variables needed are not set
*/
function me() {
global $ME;
return $ME;
}
/**
* Returns the name of the current script, WITH the full URL.
*
* 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.
* NOTE: This function returns false if the global variables needed are not set.
*
* Like {@link me()} but returns a full URL
* @see me()
*
* @global string
* @return mixed String, or false if the global variables needed are not set
*/
function qualified_me() {
global $FULLME;
return $FULLME;
}
/**
* Class for creating and manipulating urls.
*
* It can be used in moodle pages where config.php has been included without any further includes.
*
* It is useful for manipulating urls with long lists of params.
* One situation where it will be useful is a page which links to itself to perfrom various actions
* and / or to process form data. A moodle_url object :
* can be created for a page to refer to itself with all the proper get params being passed from page call to
* page call and methods can be used to output a url including all the params, optionally adding and overriding
* params and can also be used to
* - output the url without any get params
* - and output the params as hidden fields to be output within a form
*
* One important usage note is that data passed to methods out, out_action, get_query_string and
* hidden_params_out affect what is returned by the function and do not change the data stored in the object.
* This is to help with typical usage of these objects where one object is used to output urls
* in many places in a page.
*
* @link http://docs.moodle.org/en/Development:lib/weblib.php_moodle_url See short write up here
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package moodlecore
*/
class moodle_url {
/**
* @var string
* @access protected
*/
protected $scheme = ''; // e.g. http
protected $host = '';
protected $port = '';
protected $user = '';
protected $pass = '';
protected $path = '';
protected $fragment = '';
/**
* @var array
* @access protected
*/
protected $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.
*
* @global string
* @param mixed $url a number of different forms are accespted:
* null - create a URL that is the same as the URL used to load this page, but with no query string
* '' - and empty URL
* string - a URL, will be parsed into it's bits, including query string
* array - as returned from the PHP function parse_url
* moodle_url - make a copy of another moodle_url
* @param array $params these params override anything in the query string
* where params have the same name.
*/
public function __construct($url = null, $params = array()) {
if ($url === '') {
// Leave URL blank.
} else if (is_a($url, 'moodle_url')) {
$this->scheme = $url->scheme;
$this->host = $url->host;
$this->port = $url->port;
$this->user = $url->user;
$this->pass = $url->pass;
$this->path = $url->path;
$this->fragment = $url->fragment;
$this->params = $url->params;
} else {
if ($url === null) {
global $ME;
$url = $ME;
}
if (is_string($url)) {
$url = parse_url($url);
}
$parts = $url;
if ($parts === FALSE) {
throw new moodle_exception('invalidurl');
}
if (isset($parts['query'])) {
parse_str(str_replace('&', '&', $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 returns all params.
* @return array Array of Params for url.
*/
public 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.
* Remove selected params if arguments are passed.
*
* Can be called as either remove_params('param1', 'param2')
* or remove_params(array('param1', 'param2')).
*
* @param mixed $params either an array of param names, or a string param name,
* @param string $params,... any number of additional param names.
*/
public function remove_params($params = NULL) {
if (empty($params)) {
$this->params = array();
return;
}
if (!is_array($params)) {
$params = func_get_args();
}
foreach ($params as $param) {
if (isset($this->params[$param])) {
unset($this->params[$param]);
}
}
}
/**
* Add a param to the params for this page.
*
* The added param overrides existing one if theyhave the same name.
*
* @param string $paramname name
* @param string $param Param value. Defaults to null. If null then return value of param 'name'
* @return void|string If $param was null then the value of $paramname was returned
* (null is returned if that param does not exist).
*/
public function param($paramname, $param = null) {
if (!is_null($param)) {
$this->params = array($paramname => $param) + $this->params;
} else if (array_key_exists($paramname, $this->params)) {
return $this->params[$paramname];
} else {
return null;
}
}
/**
* Get the params as as a query string.
*
* @param array $overrideparams params to add to the output params, these
* override existing ones with the same name.
* @param boolean $escaped Use & as params separator instead of plain &
* @return string query string that can be added to a url.
*/
public function get_query_string($overrideparams = array(), $escaped = true) {
$arr = array();
$params = $overrideparams + $this->params;
foreach ($params as $key => $val) {
$arr[] = urlencode($key)."=".urlencode($val);
}
if ($escaped) {
return implode('&', $arr);
} else {
return implode('&', $arr);
}
}
/**
* 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.
*/
public 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 \n";
}
}
return $str;
}
/**
* Output url
*
* If you use the returned URL in HTML code, you want the escaped ampersands. If you use
* the returned URL in HTTP headers, you want $escaped=false.
*
* @param boolean $omitquerystring 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.
* @param boolean $escaped Use & as params separator instead of plain &
* @return string Resulting URL
*/
public function out($omitquerystring = false, $overrideparams = array(), $escaped = true) {
$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 (!$omitquerystring) {
$querystring = $this->get_query_string($overrideparams, $escaped);
if ($querystring) {
$uri .= '?' . $querystring;
}
}
$uri .= $this->fragment ? '#'.$this->fragment : '';
return $uri;
}
/**
* Return a URL relative to $CFG->wwwroot.
*
* Throws an exception if this URL does not start with $CFG->wwwroot.
*
* The main use for this is when you want to pass a returnurl from one script to another.
* In this case the returnurl should be relative to $CFG->wwwroot for two reasons.
* First, it is shorter. More imporatantly, some web servers (e.g. IIS by default)
* give a 'security' error if you try to pass a full URL as a GET parameter in another URL.
*
* @return string the URL relative to $CFG->wwwroot. Note, you will need to urlencode
* this result if you are outputting a URL manually (but not if you are adding
* it to another moodle_url).
*/
public function out_returnurl() {
global $CFG;
$fulluri = $this->out(false, array(), false);
$uri = str_replace($CFG->wwwroot . '/', '', $fulluri);
if ($uri == $fulluri) {
throw new coding_exception('This URL (' . $fulluri . ') is not relative to $CFG->wwwroot.');
}
return $uri;
}
/**
* Output action url with sesskey
*
* Adds sesskey and overriderparams then calls {@link out()}
* @see out()
*
* @param array $overrideparams Allows you to override params
* @return string url
*/
public function out_action($overrideparams = array()) {
$overrideparams = array('sesskey'=> sesskey()) + $overrideparams;
return $this->out(false, $overrideparams);
}
/**
* Compares this moodle_url with another
* See documentation of constants for an explanation of the comparison flags.
* @param moodle_url $url The moodle_url object to compare
* @param int $matchtype The type of comparison (URL_MATCH_BASE, URL_MATCH_PARAMS, URL_MATCH_EXACT)
* @return boolean
*/
public function compare(moodle_url $url, $matchtype = URL_MATCH_EXACT) {
$baseself = $this->out(true);
$baseother = $url->out(true);
// Append index.php if there is no specific file
if (substr($baseself,-1)=='/') {
$baseself .= 'index.php';
}
if (substr($baseother,-1)=='/') {
$baseother .= 'index.php';
}
// Compare the two base URLs
if ($baseself != $baseother) {
return false;
}
if ($matchtype == URL_MATCH_BASE) {
return true;
}
$urlparams = $url->params();
foreach ($this->params() as $param => $value) {
if ($param == 'sesskey') {
continue;
}
if (!array_key_exists($param, $urlparams) || $urlparams[$param] != $value) {
return false;
}
}
if ($matchtype == URL_MATCH_PARAMS) {
return true;
}
foreach ($urlparams as $param => $value) {
if ($param == 'sesskey') {
continue;
}
if (!array_key_exists($param, $this->params()) || $this->param($param) != $value) {
return false;
}
}
return true;
}
}
/**
* Given an unknown $url type (string or moodle_url), returns a string URL.
* A relative URL is handled with $PAGE->url but gives a debugging error.
*
* @param mixed $url The URL (moodle_url or string)
* @param bool $stripformparams Whether or not to strip the query params from the URL
* @return string the URL. &s are unescaped. You must use s(...) to output this to XHTML. ($OUTPUT normally does this automatically.)
*/
function prepare_url($url, $stripformparams=false) {
global $CFG, $PAGE;
$output = $url;
if ($url instanceof moodle_url) {
$output = $url->out($stripformparams, array(), false);
}
// Handle relative URLs
if (substr($output, 0, 4) != 'http' && substr($output, 0, 1) != '/') {
if (preg_match('/(.*)\/([A-Za-z0-9-_]*\.php)$/', $PAGE->url->out(true), $matches)) {
return $matches[1] . "/$output";
} else if ($output == '') {
return $PAGE->url->out(false, array(), false) . '#';
} else {
throw new coding_exception('Unrecognied URL scheme. Please check the formatting of the URL passed to this function. Absolute URLs are the preferred scheme.');
}
}
// Add wwwroot only if the URL does not already start with http:// or https://
if (!preg_match('|https?://|', $output) ) {
$output = $CFG->wwwroot . $output;
}
return $output;
}
/**
* 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.
*
* @uses $_POST
* @return mixed false or object
*/
function data_submitted() {
if (empty($_POST)) {
return false;
} else {
return (object)$_POST;
}
}
/**
* 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;
}
/**
* Try and close the current window using JavaScript, either immediately, or after a delay.
*
* Echo's out the resulting XHTML & javascript
*
* @global object
* @global object
* @param integer $delay a delay in seconds before closing the window. Default 0.
* @param boolean $reloadopener if true, we will see if this window was a pop-up, and try
* to reload the parent window before this one closes.
*/
function close_window($delay = 0, $reloadopener = false) {
global $THEME, $PAGE, $OUTPUT;
if (!$PAGE->headerprinted) {
$PAGE->set_title(get_string('closewindow'));
echo $OUTPUT->header();
} else {
print_container_end_all(false, $THEME->open_header_containers);
}
if ($reloadopener) {
$function = 'close_window_reloading_opener';
} else {
$function = 'close_window';
}
echo '' . get_string('windowclosing') . '
';
$PAGE->requires->js_function_call($function)->after_delay($delay);
echo $OUTPUT->footer();
exit;
}
/**
* Returns a string containing a link to the user documentation for the current
* page. Also contains an icon by default. Shown to teachers and admin only.
*
* @global object
* @global object
* @param string $text The text to be displayed for the link
* @param string $iconpath The path to the icon to be displayed
* @return string The link to user documentation for this current page
*/
function page_doc_link($text='', $iconpath='') {
global $CFG, $PAGE, $OUTPUT;
if (empty($CFG->docroot) || during_initial_install()) {
return '';
}
if (!has_capability('moodle/site:doclinks', $PAGE->context)) {
return '';
}
$path = $PAGE->docspath;
if (!$path) {
return '';
}
return $OUTPUT->doc_link($path, $text, $iconpath);
}
/**
* 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
* Note: $scriptname parameter is not needed anymore
*
* @global string
* @uses $_SERVER
* @uses PARAM_PATH
* @return string file path (only safe characters)
*/
function get_file_argument() {
global $SCRIPT;
$relativepath = optional_param('file', FALSE, PARAM_PATH);
// then try extract file from PATH_INFO (slasharguments method)
if ($relativepath === false and isset($_SERVER['PATH_INFO']) and $_SERVER['PATH_INFO'] !== '') {
// check that PATH_INFO works == must not contain the script name
if (strpos($_SERVER['PATH_INFO'], $SCRIPT) === false) {
$relativepath = clean_param(urldecode($_SERVER['PATH_INFO']), PARAM_PATH);
}
}
// note: we are not using any other way because they are not compatible with unicode file names ;-)
return $relativepath;
}
/**
* 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().
*
* @todo Finish documenting this function
*
* @global object
* @global object
* @global object
* @global object
* @uses FORMAT_MOODLE
* @uses FORMAT_HTML
* @uses FORMAT_PLAIN
* @uses FORMAT_WIKI
* @uses FORMAT_MARKDOWN
* @uses CLI_SCRIPT
* @staticvar array $croncache
* @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 object $options ?
* @param int $courseid The courseid to use, defaults to $COURSE->courseid
* @return string
*/
function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
global $CFG, $COURSE, $DB, $PAGE;
static $croncache = array();
$hashstr = '';
if ($text === '') {
return ''; // no need to do any filters and cleaning
}
if (!empty($options->comments) && !empty($CFG->usecomments)) {
require_once($CFG->libdir . '/commentlib.php');
$comment = new comment($options->comments);
$cmt = $comment->init(true);
} else {
$cmt = '';
}
if (!isset($options->trusted)) {
$options->trusted = false;
}
if (!isset($options->noclean)) {
if ($options->trusted and trusttext_active()) {
// no cleaning if text trusted and noclean not specified
$options->noclean=true;
} else {
$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 ($options->filter) {
$filtermanager = filter_manager::instance();
} else {
$filtermanager = new null_filter_manager();
}
$context = $PAGE->context;
if (!empty($CFG->cachetext) and empty($options->nocache)) {
$hashstr .= $text.'-'.$filtermanager->text_filtering_hash($context, $courseid).'-'.(int)$courseid.'-'.current_language().'-'.
(int)$format.(int)$options->trusted.(int)$options->noclean.(int)$options->smiley.
(int)$options->filter.(int)$options->para.(int)$options->newlines;
$time = time() - $CFG->cachetext;
$md5key = md5($hashstr);
if (CLI_SCRIPT) {
if (isset($croncache[$md5key])) {
return $croncache[$md5key].$cmt;
}
}
if ($oldcacheitem = $DB->get_record('cache_text', array('md5key'=>$md5key), '*', IGNORE_MULTIPLE)) {
if ($oldcacheitem->timemodified >= $time) {
if (CLI_SCRIPT) {
if (count($croncache) > 150) {
reset($croncache);
$key = key($croncache);
unset($croncache[$key]);
}
$croncache[$md5key] = $oldcacheitem->formattedtext;
}
return $oldcacheitem->formattedtext.$cmt;
}
}
}
switch ($format) {
case FORMAT_HTML:
if ($options->smiley) {
replace_smilies($text);
}
if (!$options->noclean) {
$text = clean_text($text, FORMAT_HTML);
}
$text = $filtermanager->filter_text($text, $context, $courseid);
break;
case FORMAT_PLAIN:
$text = s($text); // cleans dangerous JS
$text = rebuildnolinktag($text);
$text = str_replace(' ', ' ', $text);
$text = nl2br($text);
break;
case FORMAT_WIKI:
// this format is deprecated
$text = '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.
'.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);
}
$text = $filtermanager->filter_text($text, $context, $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);
}
$text = $filtermanager->filter_text($text, $context, $courseid);
break;
}
// Warn people that we have removed this old mechanism, just in case they
// were stupid enough to rely on it.
if (isset($CFG->currenttextiscacheable)) {
debugging('Once upon a time, Moodle had a truly evil use of global variables ' .
'called $CFG->currenttextiscacheable. The good news is that this no ' .
'longer exists. The bad news is that you seem to be using a filter that '.
'relies on it. Please seek out and destroy that filter code.', DEBUG_DEVELOPER);
}
if (empty($options->nocache) and !empty($CFG->cachetext)) {
if (CLI_SCRIPT) {
// 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.$cmt;
}
$newcacheitem = new object();
$newcacheitem->md5key = $md5key;
$newcacheitem->formattedtext = $text;
$newcacheitem->timemodified = time();
if ($oldcacheitem) { // See bug 4677 for discussion
$newcacheitem->id = $oldcacheitem->id;
try {
$DB->update_record('cache_text', $newcacheitem); // Update existing record in the cache table
} catch (dml_exception $e) {
// 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 {
try {
$DB->insert_record('cache_text', $newcacheitem); // Insert a new record in the cache table
} catch (dml_exception $e) {
// 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.$cmt;
}
/**
* 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.
*
* @uses FORMAT_MOODLE
* @uses FORMAT_HTML
* @uses FORMAT_PLAIN
* @uses FORMAT_MARKDOWN
* @param mixed $key 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.
*
* @global object
* @global object
* @return void
*/
function reset_text_filters_cache() {
global $CFG, $DB;
$DB->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.
*
* @global object
* @global object
* @global object
* @staticvar bool $strcache
* @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, $PAGE;
//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
// Regular expression moved to its own method for easier unit testing
$string = replace_ampersands_not_followed_by_entity($string);
if (!empty($CFG->filterall) && $CFG->version >= 2009040600) { // Avoid errors during the upgrade to the new system.
$context = $PAGE->context;
$string = filter_manager::instance()->filter_string($string, $context, $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 = strip_links($string);
}
$string = clean_text($string);
}
//Store to cache
$strcache[$md5] = $string;
return $string;
}
/**
* Given a string, performs a negative lookahead looking for any ampersand character
* that is not followed by a proper HTML entity. If any is found, it is replaced
* by &. The string is then returned.
*
* @param string $string
* @return string
*/
function replace_ampersands_not_followed_by_entity($string) {
return preg_replace("/\&(?![a-zA-Z0-9#]{1,8};)/", "&", $string);
}
/**
* Given a string, replaces all .* by .* and returns the string.
*
* @param string $string
* @return string
*/
function strip_links($string) {
return preg_replace('/(]+?>)(.+?)(<\/a>)/is','$2',$string);
}
/**
* This expression turns links into something nice in a text format. (Russell Jungwirth)
*
* @param string $string
* @return string
*/
function wikify_links($string) {
return preg_replace('~( ]*>([^<]*) )~i','$3 [ $2 ]', $string);
}
/**
* Replaces non-standard HTML entities
*
* @param string $string
* @return string
*/
function fix_non_standard_entities($string) {
$text = preg_replace('/([0-9]+)(;?)/', '$1;', $string);
$text = preg_replace('/([0-9a-fA-F]+)(;?)/', '$1;', $text);
return $text;
}
/**
* 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);
$text = wikify_links($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 = wikify_links($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 configured for this context.
*
* @global object
* @global object
* @global object
* @param string $text The text to be passed through format filters
* @param int $courseid The current course.
* @return string the filtered string.
*/
function filter_text($text, $courseid=NULL) {
global $CFG, $COURSE, $PAGE;
if (empty($courseid)) {
$courseid = $COURSE->id; // (copied from format_text)
}
$context = $PAGE->context;
return filter_manager::instance()->filter_text($text, $context, $courseid);
}
/**
* Formats activity intro text
*
* @global object
* @uses CONTEXT_MODULE
* @param string $module name of module
* @param object $activity instance of activity
* @param int $cmid course module id
* @param bool $filter filter resulting html text
* @return text
*/
function format_module_intro($module, $activity, $cmid, $filter=true) {
global $CFG;
require_once("$CFG->libdir/filelib.php");
$options = (object)array('noclean'=>true, 'para'=>false, 'filter'=>false);
$context = get_context_instance(CONTEXT_MODULE, $cmid);
$intro = file_rewrite_pluginfile_urls($activity->intro, 'pluginfile.php', $context->id, $module.'_intro', null);
return trim(format_text($intro, $activity->introformat, $options));
}
/**
* Legacy function, used for cleaning of old forum and glossary text only.
*
* @global object
* @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;
}
}
}
/**
* Must be called before editing of all texts
* with trust flag. Removes all XSS nasties
* from texts stored in database if needed.
*
* @param object $object data object with xxx, xxxformat and xxxtrust fields
* @param string $field name of text field
* @param object $context active context
* @return object updated $object
*/
function trusttext_pre_edit($object, $field, $context) {
$trustfield = $field.'trust';
$formatfield = $field.'format';
if (!$object->$trustfield or !trusttext_trusted($context)) {
$object->$field = clean_text($object->$field, $object->$formatfield);
}
return $object;
}
/**
* Is current user trusted to enter no dangerous XSS in this context?
*
* Please note the user must be in fact trusted everywhere on this server!!
*
* @param object $context
* @return bool true if user trusted
*/
function trusttext_trusted($context) {
return (trusttext_active() and has_capability('moodle/site:trustcontent', $context));
}
/**
* Is trusttext feature active?
*
* @global object
* @param object $context
* @return bool
*/
function trusttext_active() {
global $CFG;
return !empty($CFG->enabletrusttext);
}
/**
* 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
* @global string
* @global object
* @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 = fix_non_standard_entities($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 = preg_replace("~([^a-z])language([[:space:]]*)=~i", "$1Xlanguage=", $text);
$text = preg_replace("~([^a-z])on([a-z]+)([[:space:]]*)=~i", "$1Xon$2=", $text);
return $text;
}
}
/**
* KSES replacement cleaning function - uses HTML Purifier.
*
* @global object
* @param string $text The (X)HTML string to purify
*/
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;
static $config;
if ($purifier === false) {
require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.safe-includes.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('Core.ConvertDocumentToFragment', true);
$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
*
* @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
*
* @global object
* @global string
* @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 '>'; //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('/([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
*
* @global object
* @staticvar array $e
* @staticvar array $img
* @staticvar array $emoticons
* @param string $text Passed by reference. The string to search for smily strings.
* @return string
*/
function replace_smilies(&$text) {
global $CFG, $OUTPUT;
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][] = ' ';
}
}
// Exclude from transformations all the code inside