* Options: * trusted : If true the string won't be cleaned. Default false required noclean=true. * noclean : If true the string won't be cleaned, unless $CFG->forceclean is set. Default false required trusted=true. * nocache : If true the strign will not be cached and will be formatted every call. Default false. * filter : If true the string will be run through applicable filters as well. Default true. * para : If true then the returned string will be wrapped in div tags. Default true. * newlines : If true then lines newline breaks will be converted to HTML newline breaks. Default true. * context : The context that will be used for filtering. * overflowdiv : If set to true the formatted text will be encased in a div * with the class no-overflow before being returned. Default false. * allowid : If true then id attributes will not be removed, even when * using htmlpurifier. Default false. * blanktarget : If true all tags will have target="_blank" added unless target is explicitly specified. ** * @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_MARKDOWN] * @param object/array $options text formatting options * @param int $courseiddonotuse deprecated course id, use context option instead * @return string */ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseiddonotuse = null) { global $CFG, $DB, $PAGE; if ($text === '' || is_null($text)) { // No need to do any filters and cleaning. return ''; } // Detach object, we can not modify it. $options = (array)$options; 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 (!empty($CFG->forceclean)) { // Whatever the caller claims, the admin wants all content cleaned anyway. $options['noclean'] = false; } if (!isset($options['nocache'])) { $options['nocache'] = false; } if (!isset($options['filter'])) { $options['filter'] = true; } if (!isset($options['para'])) { $options['para'] = true; } if (!isset($options['newlines'])) { $options['newlines'] = true; } if (!isset($options['overflowdiv'])) { $options['overflowdiv'] = false; } $options['blanktarget'] = !empty($options['blanktarget']); // Calculate best context. if (empty($CFG->version) or $CFG->version < 2013051400 or during_initial_install()) { // Do not filter anything during installation or before upgrade completes. $context = null; } else if (isset($options['context'])) { // First by explicit passed context option. if (is_object($options['context'])) { $context = $options['context']; } else { $context = context::instance_by_id($options['context']); } } else if ($courseiddonotuse) { // Legacy courseid. $context = context_course::instance($courseiddonotuse); } else { // Fallback to $PAGE->context this may be problematic in CLI and other non-standard pages :-(. $context = $PAGE->context; } if (!$context) { // Either install/upgrade or something has gone really wrong because context does not exist (yet?). $options['nocache'] = true; $options['filter'] = false; } if ($options['filter']) { $filtermanager = filter_manager::instance(); $filtermanager->setup_page_for_filters($PAGE, $context); // Setup global stuff filters may have. $filteroptions = array( 'originalformat' => $format, 'noclean' => $options['noclean'], ); } else { $filtermanager = new null_filter_manager(); $filteroptions = array(); } switch ($format) { case FORMAT_HTML: if (!$options['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } $text = $filtermanager->filter_text($text, $context, $filteroptions); 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['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } $text = $filtermanager->filter_text($text, $context, $filteroptions); break; default: // FORMAT_MOODLE or anything else. $text = text_to_html($text, null, $options['para'], $options['newlines']); if (!$options['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } $text = $filtermanager->filter_text($text, $context, $filteroptions); break; } if ($options['filter']) { // At this point there should not be any draftfile links any more, // this happens when developers forget to post process the text. // The only potential problem is that somebody might try to format // the text before storing into database which would be itself big bug.. $text = str_replace("\"$CFG->wwwroot/draftfile.php", "\"$CFG->wwwroot/brokenfile.php#", $text); if ($CFG->debugdeveloper) { if (strpos($text, '@@PLUGINFILE@@/') !== false) { debugging('Before calling format_text(), the content must be processed with file_rewrite_pluginfile_urls()', DEBUG_DEVELOPER); } } } if (!empty($options['overflowdiv'])) { $text = html_writer::tag('div', $text, array('class' => 'no-overflow')); } if ($options['blanktarget']) { $domdoc = new DOMDocument(); libxml_use_internal_errors(true); $domdoc->loadHTML('' . $text); libxml_clear_errors(); foreach ($domdoc->getElementsByTagName('a') as $link) { if ($link->hasAttribute('target') && strpos($link->getAttribute('target'), '_blank') === false) { continue; } $link->setAttribute('target', '_blank'); if (strpos($link->getAttribute('rel'), 'noreferrer') === false) { $link->setAttribute('rel', trim($link->getAttribute('rel') . ' noreferrer')); } } // This regex is nasty and I don't like it. The correct way to solve this is by loading the HTML like so: // $domdoc->loadHTML($text, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); however it seems like the libxml // version that travis uses doesn't work properly and ends up leaving , so I'm forced to use // this regex to remove those tags. $text = trim(preg_replace('~<(?:!DOCTYPE|/?(?:html|body))[^>]*>\s*~i', '', $domdoc->saveHTML($domdoc->documentElement))); } return $text; } /** * Resets some data related to filters, called during upgrade or when general filter settings change. * * @param bool $phpunitreset true means called from our PHPUnit integration test reset * @return void */ function reset_text_filters_cache($phpunitreset = false) { global $CFG, $DB; if ($phpunitreset) { // HTMLPurifier does not change, DB is already reset to defaults, // nothing to do here, the dataroot was cleared too. return; } // The purge_all_caches() deals with cachedir and localcachedir purging, // the individual filter caches are invalidated as necessary elsewhere. // Update $CFG->filterall cache flag. if (empty($CFG->stringfilters)) { set_config('filterall', 0); return; } $installedfilters = core_component::get_plugin_list('filter'); $filters = explode(',', $CFG->stringfilters); foreach ($filters as $filter) { if (isset($installedfilters[$filter])) { set_config('filterall', 1); return; } } set_config('filterall', 0); } /** * 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. * * @staticvar bool $strcache * @param string $string The string to be filtered. Should be plain text, expect * possibly for multilang tags. * @param boolean $striplinks To strip any link in the result text. Moodle 1.8 default changed from false to true! MDL-8713 * @param array $options options array/object or courseid * @return string */ function format_string($string, $striplinks = true, $options = null) { global $CFG, $PAGE; // We'll use a in-memory cache here to speed up repeated strings. static $strcache = false; if (empty($CFG->version) or $CFG->version < 2013051400 or during_initial_install()) { // Do not filter anything during installation or before upgrade completes. return $string = strip_tags($string); } if ($strcache === false or count($strcache) > 2000) { // This number might need some tuning to limit memory usage in cron. $strcache = array(); } if (is_numeric($options)) { // Legacy courseid usage. $options = array('context' => context_course::instance($options)); } else { // Detach object, we can not modify it. $options = (array)$options; } if (empty($options['context'])) { // Fallback to $PAGE->context this may be problematic in CLI and other non-standard pages :-(. $options['context'] = $PAGE->context; } else if (is_numeric($options['context'])) { $options['context'] = context::instance_by_id($options['context']); } if (!isset($options['filter'])) { $options['filter'] = true; } $options['escape'] = !isset($options['escape']) || $options['escape']; if (!$options['context']) { // We did not find any context? weird. return $string = strip_tags($string); } // Calculate md5. $cachekeys = array($string, $striplinks, $options['context']->id, $options['escape'], current_language(), $options['filter']); $md5 = md5(implode('<+>', $cachekeys)); // 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 = $options['escape'] ? replace_ampersands_not_followed_by_entity($string) : $string; if (!empty($CFG->filterall) && $options['filter']) { $filtermanager = filter_manager::instance(); $filtermanager->setup_page_for_filters($PAGE, $options['context']); // Setup global stuff filters may have. $string = $filtermanager->filter_string($string, $options['context']); } // If the site requires it, strip ALL tags from this string. if (!empty($CFG->formatstringstriptags)) { if ($options['escape']) { $string = str_replace(array('<', '>'), array('<', '>'), strip_tags($string)); } else { $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); } /** * Given text in a variety of format codings, this function returns the text as plain text suitable for plain email. * * @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: // There should not be any of these any more! $text = wikify_links($text); return core_text::entities_to_utf8(strip_tags($text), true); break; case FORMAT_HTML: return html_to_text($text); break; case FORMAT_MOODLE: case FORMAT_MARKDOWN: default: $text = wikify_links($text); return core_text::entities_to_utf8(strip_tags($text), true); break; } } /** * Formats activity intro text * * @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 string */ function format_module_intro($module, $activity, $cmid, $filter=true) { global $CFG; require_once("$CFG->libdir/filelib.php"); $context = context_module::instance($cmid); $options = array('noclean' => true, 'para' => false, 'filter' => $filter, 'context' => $context, 'overflowdiv' => true); $intro = file_rewrite_pluginfile_urls($activity->intro, 'pluginfile.php', $context->id, 'mod_'.$module, 'intro', null); return trim(format_text($intro, $activity->introformat, $options, null)); } /** * Removes the usage of Moodle files from a text. * * In some rare cases we need to re-use a text that already has embedded links * to some files hosted within Moodle. But the new area in which we will push * this content does not support files... therefore we need to remove those files. * * @param string $source The text * @return string The stripped text */ function strip_pluginfile_content($source) { $baseurl = '@@PLUGINFILE@@'; // Looking for something like < .* "@@pluginfile@@.*" .* > $pattern = '$<[^<>]+["\']' . $baseurl . '[^"\']*["\'][^<>]*>$'; $stripped = preg_replace($pattern, '', $source); // Use purify html to rebalence potentially mismatched tags and generally cleanup. return purify_html($stripped); } /** * Legacy function, used for cleaning of old forum and glossary text only. * * @param string $text text that may contain legacy TRUSTTEXT marker * @return string text without legacy TRUSTTEXT marker */ function trusttext_strip($text) { if (!is_string($text)) { // This avoids the potential for an endless loop below. throw new coding_exception('trusttext_strip parameter must be a string'); } 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 stdClass $object data object with xxx, xxxformat and xxxtrust fields * @param string $field name of text field * @param context $context active context * @return stdClass 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 context $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? * * @return bool */ function trusttext_active() { global $CFG; return !empty($CFG->enabletrusttext); } /** * Cleans raw text removing nasties. * * 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 through XSS attacks. * * The result must be used as a HTML text fragment, this function can not cleanup random * parts of html tags such as url or src attributes. * * NOTE: the format parameter was deprecated because we can safely clean only HTML. * * @param string $text The text to be cleaned * @param int|string $format deprecated parameter, should always contain FORMAT_HTML or FORMAT_MOODLE * @param array $options Array of options; currently only option supported is 'allowid' (if true, * does not remove id attributes when cleaning) * @return string The cleaned up text */ function clean_text($text, $format = FORMAT_HTML, $options = array()) { $text = (string)$text; if ($format != FORMAT_HTML and $format != FORMAT_HTML) { // TODO: we need to standardise cleanup of text when loading it into editor first. // debugging('clean_text() is designed to work only with html');. } if ($format == FORMAT_PLAIN) { return $text; } if (is_purify_html_necessary($text)) { $text = purify_html($text, $options); } // Originally we tried to neutralise some script events here, it was a wrong approach because // it was trivial to work around that (for example using style based XSS exploits). // We must not give false sense of security here - all developers MUST understand how to use // rawurlencode(), htmlentities(), htmlspecialchars(), p(), s(), moodle_url, html_writer and friends!!! return $text; } /** * Is it necessary to use HTMLPurifier? * * @private * @param string $text * @return bool false means html is safe and valid, true means use HTMLPurifier */ function is_purify_html_necessary($text) { if ($text === '') { return false; } if ($text === (string)((int)$text)) { return false; } if (strpos($text, '&') !== false or preg_match('|<[^pesb/]|', $text)) { // We need to normalise entities or other tags except p, em, strong and br present. return true; } $altered = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8', true); if ($altered === $text) { // No < > or other special chars means this must be safe. return false; } // Let's try to convert back some safe html tags. $altered = preg_replace('|<p>(.*?)</p>|m', '$1
', $altered); if ($altered === $text) { return false; } $altered = preg_replace('|<em>([^<>]+?)</em>|m', '$1', $altered); if ($altered === $text) { return false; } $altered = preg_replace('|<strong>([^<>]+?)</strong>|m', '$1', $altered); if ($altered === $text) { return false; } $altered = str_replace('<br />', '
* if (!isset($CFG->additionalhtmlhead)) {
* $CFG->additionalhtmlhead = '';
* }
* $CFG->additionalhtmlhead .= '';
* header('X-UA-Compatible: IE=8');
* echo $OUTPUT->header();
*
*
* Please note the $CFG->additionalhtmlhead alone might not work,
* you should send the IE compatibility header() too.
*
* @param string $contenttype
* @param bool $cacheable Can this page be cached on back?
* @return void, sends HTTP headers
*/
function send_headers($contenttype, $cacheable = true) {
global $CFG;
@header('Content-Type: ' . $contenttype);
@header('Content-Script-Type: text/javascript');
@header('Content-Style-Type: text/css');
if (empty($CFG->additionalhtmlhead) or stripos($CFG->additionalhtmlhead, 'X-UA-Compatible') === false) {
@header('X-UA-Compatible: IE=edge');
}
if ($cacheable) {
// Allow caching on "back" (but not on normal clicks).
@header('Cache-Control: private, pre-check=0, post-check=0, max-age=0, no-transform');
@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, no-transform', 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');
}
@header('Accept-Ranges: none');
// The Moodle app must be allowed to embed content always.
if (empty($CFG->allowframembedding) && !core_useragent::is_moodle_app()) {
@header('X-Frame-Options: sameorigin');
}
// If referrer policy is set, add a referrer header.
if (!empty($CFG->referrerpolicy) && ($CFG->referrerpolicy !== 'default')) {
@header('Referrer-Policy: ' . $CFG->referrerpolicy);
}
}
/**
* Return the right arrow with text ('next'), and optionally embedded in a link.
*
* @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases).
* @param string $url An optional link to use in a surrounding HTML anchor.
* @param bool $accesshide True if text should be hidden (for screen readers only).
* @param string $addclass Additional class names for the link, or the arrow character.
* @return string HTML string.
*/
function link_arrow_right($text, $url='', $accesshide=false, $addclass='', $addparams = []) {
global $OUTPUT; // TODO: move to output renderer.
$arrowclass = 'arrow ';
if (!$url) {
$arrowclass .= $addclass;
}
$arrow = ''.$OUTPUT->rarrow().'';
$htmltext = '';
if ($text) {
$htmltext = ''.$text.' ';
if ($accesshide) {
$htmltext = get_accesshide($htmltext);
}
}
if ($url) {
$class = 'arrow_link';
if ($addclass) {
$class .= ' '.$addclass;
}
$linkparams = [
'class' => $class,
'href' => $url,
'title' => preg_replace('/<.*?>/', '', $text),
];
$linkparams += $addparams;
return html_writer::link($url, $htmltext . $arrow, $linkparams);
}
return $htmltext.$arrow;
}
/**
* Return the left arrow with text ('previous'), and optionally embedded in a link.
*
* @param string $text HTML/plain text label (set to blank only for breadcrumb separator cases).
* @param string $url An optional link to use in a surrounding HTML anchor.
* @param bool $accesshide True if text should be hidden (for screen readers only).
* @param string $addclass Additional class names for the link, or the arrow character.
* @return string HTML string.
*/
function link_arrow_left($text, $url='', $accesshide=false, $addclass='', $addparams = []) {
global $OUTPUT; // TODO: move to utput renderer.
$arrowclass = 'arrow ';
if (! $url) {
$arrowclass .= $addclass;
}
$arrow = ''.$OUTPUT->larrow().'';
$htmltext = '';
if ($text) {
$htmltext = ' '.$text.'';
if ($accesshide) {
$htmltext = get_accesshide($htmltext);
}
}
if ($url) {
$class = 'arrow_link';
if ($addclass) {
$class .= ' '.$addclass;
}
$linkparams = [
'class' => $class,
'href' => $url,
'title' => preg_replace('/<.*?>/', '', $text),
];
$linkparams += $addparams;
return html_writer::link($url, $arrow . $htmltext, $linkparams);
}
return $arrow.$htmltext;
}
/**
* Return a HTML element with the class "accesshide", for accessibility.
*
* Please use cautiously - where possible, text should be visible!
*
* @param string $text Plain text.
* @param string $elem Lowercase element name, default "span".
* @param string $class Additional classes for the element.
* @param string $attrs Additional attributes string in the form, "name='value' name2='value2'"
* @return string HTML string.
*/
function get_accesshide($text, $elem='span', $class='', $attrs='') {
return "<$elem class=\"accesshide $class\" $attrs>$text$elem>";
}
/**
* Return the breadcrumb trail navigation separator.
*
* @return string HTML string.
*/
function get_separator() {
// Accessibility: the 'hidden' slash is preferred for screen readers.
return ' '.link_arrow_right($text='/', $url='', $accesshide=true, 'sep').' ';
}
/**
* Print (or return) a collapsible region, that has a caption that can be clicked to expand or collapse the region.
*
* If JavaScript is off, then the region will always be expanded.
*
* @param string $contents the contents of the box.
* @param string $classes class names added to the div that is output.
* @param string $id id added to the div that is output. Must not be blank.
* @param string $caption text displayed at the top. Clicking on this will cause the region to expand or contract.
* @param string $userpref the name of the user preference that stores the user's preferred default state.
* (May be blank if you do not wish the state to be persisted.
* @param boolean $default Initial collapsed state to use if the user_preference it not set.
* @param boolean $return if true, return the HTML as a string, rather than printing it.
* @return string|void If $return is false, returns nothing, otherwise returns a string of HTML.
*/
function print_collapsible_region($contents, $classes, $id, $caption, $userpref = '', $default = false, $return = false) {
$output = print_collapsible_region_start($classes, $id, $caption, $userpref, $default, true);
$output .= $contents;
$output .= print_collapsible_region_end(true);
if ($return) {
return $output;
} else {
echo $output;
}
}
/**
* Print (or return) the start of a collapsible region
*
* The collapsibleregion has a caption that can be clicked to expand or collapse the region. If JavaScript is off, then the region
* will always be expanded.
*
* @param string $classes class names added to the div that is output.
* @param string $id id added to the div that is output. Must not be blank.
* @param string $caption text displayed at the top. Clicking on this will cause the region to expand or contract.
* @param string $userpref the name of the user preference that stores the user's preferred default state.
* (May be blank if you do not wish the state to be persisted.
* @param boolean $default Initial collapsed state to use if the user_preference it not set.
* @param boolean $return if true, return the HTML as a string, rather than printing it.
* @param string $extracontent the extra content will show next to caption, eg.Help icon.
* @return string|void if $return is false, returns nothing, otherwise returns a string of HTML.
*/
function print_collapsible_region_start($classes, $id, $caption, $userpref = '', $default = false, $return = false,
$extracontent = null) {
global $PAGE;
// Work out the initial state.
if (!empty($userpref) and is_string($userpref)) {
user_preference_allow_ajax_update($userpref, PARAM_BOOL);
$collapsed = get_user_preferences($userpref, $default);
} else {
$collapsed = $default;
$userpref = false;
}
if ($collapsed) {
$classes .= ' collapsed';
}
$output = '';
$output .= 'print_location_comment(__FILE__, __LINE__);
*
* @param string $file
* @param integer $line
* @param boolean $return Whether to return or print the comment
* @return string|void Void unless true given as third parameter
*/
function print_location_comment($file, $line, $return = false) {
if ($return) {
return "\n";
} else {
echo "\n";
}
}
/**
* Returns true if the user is using a right-to-left language.
*
* @return boolean true if the current language is right-to-left (Hebrew, Arabic etc)
*/
function right_to_left() {
return (get_string('thisdirection', 'langconfig') === 'rtl');
}
/**
* Returns swapped left<=> right if in RTL environment.
*
* Part of RTL Moodles support.
*
* @param string $align align to check
* @return string
*/
function fix_align_rtl($align) {
if (!right_to_left()) {
return $align;
}
if ($align == 'left') {
return 'right';
}
if ($align == 'right') {
return 'left';
}
return $align;
}
/**
* Returns true if the page is displayed in a popup window.
*
* Gets the information from the URL parameter inpopup.
*
* @todo Use a central function to create the popup calls all over Moodle and
* In the moment only works with resources and probably questions.
*
* @return boolean
*/
function is_in_popup() {
$inpopup = optional_param('inpopup', '', PARAM_BOOL);
return ($inpopup);
}
/**
* Progress trace class.
*
* Use this class from long operations where you want to output occasional information about
* what is going on, but don't know if, or in what format, the output should be.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core
*/
abstract class progress_trace {
/**
* Output an progress message in whatever format.
*
* @param string $message the message to output.
* @param integer $depth indent depth for this message.
*/
abstract public function output($message, $depth = 0);
/**
* Called when the processing is finished.
*/
public function finished() {
}
}
/**
* This subclass of progress_trace does not ouput anything.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core
*/
class null_progress_trace extends progress_trace {
/**
* Does Nothing
*
* @param string $message
* @param int $depth
* @return void Does Nothing
*/
public function output($message, $depth = 0) {
}
}
/**
* This subclass of progress_trace outputs to plain text.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core
*/
class text_progress_trace extends progress_trace {
/**
* Output the trace message.
*
* @param string $message
* @param int $depth
* @return void Output is echo'd
*/
public function output($message, $depth = 0) {
mtrace(str_repeat(' ', $depth) . $message);
}
}
/**
* This subclass of progress_trace outputs as HTML.
*
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @package core
*/
class html_progress_trace extends progress_trace {
/**
* Output the trace message.
*
* @param string $message
* @param int $depth
* @return void Output is echo'd
*/
public function output($message, $depth = 0) {
echo '', str_repeat(' ', $depth), htmlspecialchars($message), "
\n"; flush(); } } /** * HTML List Progress Tree * * @copyright 2009 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package core */ class html_list_progress_trace extends progress_trace { /** @var int */ protected $currentdepth = -1; /** * Echo out the list * * @param string $message The message to display * @param int $depth * @return void Output is echoed */ public function output($message, $depth = 0) { $samedepth = true; while ($this->currentdepth > $depth) { echo "\n\n"; $this->currentdepth -= 1; if ($this->currentdepth == $depth) { echo '