/** * 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(). * * <pre> * Options: * trusted : If true the string won't be cleaned. Default false required noclean=true. * noclean : If true the string won't be cleaned. 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. * </pre> * * @todo Finish documenting this function * * @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 $courseid_do_not_use deprecated course id, use context option instead * @return string */ function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_do_not_use = NULL) { global $CFG, $COURSE, $DB, $PAGE; static $croncache = array(); if ($text === '' || is_null($text)) { return ''; // no need to do any filters and cleaning } $options = (array) $options; // detach object, we can not modify it 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['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; } // Calculate best context if (empty($CFG->version) or $CFG->version < 2010072800 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 = get_context_instance_by_id($options['context']); } } else { if ($courseid_do_not_use) { // legacy courseid $context = get_context_instance(CONTEXT_COURSE, $courseid_do_not_use); } 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(); } else { $filtermanager = new null_filter_manager(); } if (!empty($CFG->cachetext) and empty($options['nocache'])) { $hashstr = $text . '-' . $filtermanager->text_filtering_hash($context) . '-' . $context->id . '-' . current_language() . '-' . (int) $format . (int) $options['trusted'] . (int) $options['noclean'] . (int) $options['para'] . (int) $options['newlines']; $time = time() - $CFG->cachetext; $md5key = md5($hashstr); if (CLI_SCRIPT) { if (isset($croncache[$md5key])) { return $croncache[$md5key]; } } 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; } } } switch ($format) { case FORMAT_HTML: if (!$options['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_HTML, 'noclean' => $options['noclean'])); 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 = '<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['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_MARKDOWN, 'noclean' => $options['noclean'])); 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, array('originalformat' => $format, 'noclean' => $options['noclean'])); 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->httpswwwroot}/draftfile.php", "\"{$CFG->httpswwwroot}/brokenfile.php#", $text); } // 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['overflowdiv'])) { $text = html_writer::tag('div', $text, array('class' => 'no-overflow')); } 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; } $newcacheitem = new stdClass(); $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; }
/** * 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, $DB, $PAGE; static $croncache = array(); $hashstr = ''; if ($text === '') { return ''; // no need to do any filters and cleaning } 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]; } } if ($oldcacheitem = $DB->get_record('cache_text', array('md5key' => $md5key), '*', true)) { 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; } } } 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 = '<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); } $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; } $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; }
/** * 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 {@link format_string()}. * * <pre> * Options: * trusted : If true the string won't be cleaned. Default false required noclean=true. * noclean : If true the string won't be cleaned. 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. * </pre> * * @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 (!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; } // 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. } else { $filtermanager = new null_filter_manager(); } switch ($format) { case FORMAT_HTML: if (!$options['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_HTML, 'noclean' => $options['noclean'])); 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 = '<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['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_MARKDOWN, 'noclean' => $options['noclean'])); 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, array('originalformat' => $format, 'noclean' => $options['noclean'])); 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->httpswwwroot}/draftfile.php", "\"{$CFG->httpswwwroot}/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')); } return $text; }
/** * 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 {@link format_string()}. * * <pre> * Options: * trusted : If true the string won't be cleaned. Default false required noclean=true. * noclean : If true the string won't be cleaned. 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 <a> tags will have target="_blank" added unless target is explicitly specified. * </pre> * * @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 (!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 = '<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['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->httpswwwroot/draftfile.php", "\"$CFG->httpswwwroot/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(); $domdoc->loadHTML($text); 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 <html><body>, so I'm forced to use // this regex to remove those tags. $text = trim(preg_replace('~<(?:!DOCTYPE|/?(?:html|body))[^>]*>\s*~i', '', $domdoc->saveHTML())); } return $text; }