mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 12:32:08 +02:00
MDL-80072 core: Promote all formatting options to parameters
Now that PHP has support for named parameters, and we can use them in Moodle, we should ditch `$options` arrays and use first-class, documented, parameters. Whilst this may seem scary, dumb, overwhelming, please note that you do not need to supply all args, for example, to change the last parameter of `format_text` you no longer need to do this: return \core\container::get(\core\formatting::class)->format_text( $text, FORMAT_MOODLE, $context, false, null, true, true, true, false, false, true, ); Instead you can do: return \core\container::get(\core\formatting::class)->format_text( $text, FORMAT_MOODLE, $context, allowid: true, ); Or better still: return \core\container::get(\core\formatting::class)->format_text( text: $text, format: FORMAT_MOODLE, context: $context, allowid: true, ); This means that we can get defaults in the function signature, improves our typing, and allows for deprecation and changes to options. It also sets us up for success in the future.
This commit is contained in:
parent
b353c8e1e2
commit
d56303aa54
@ -44,17 +44,20 @@ class formatting {
|
||||
* need filter processing e.g. activity titles, post subjects,
|
||||
* glossary concepts.
|
||||
*
|
||||
* @staticvar bool $strcache
|
||||
* @param null|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.
|
||||
* @param array $options options array
|
||||
* @param null|context $context The context used for formatting
|
||||
* @param bool $filter Whether to apply filters
|
||||
* @param bool $escape Whether to escape ampersands
|
||||
* @return string
|
||||
*/
|
||||
public function format_string(
|
||||
?string $string,
|
||||
bool $striplinks = true,
|
||||
array $options = [],
|
||||
?context $context = null,
|
||||
bool $filter = true,
|
||||
bool $escape = true,
|
||||
): string {
|
||||
global $PAGE;
|
||||
|
||||
@ -72,37 +75,26 @@ class formatting {
|
||||
$this->formatstringcache = [];
|
||||
}
|
||||
|
||||
// Detach object, we can not modify it.
|
||||
$options = (array)$options;
|
||||
|
||||
if (empty($options['context'])) {
|
||||
if ($context === null) {
|
||||
// Fallback to $PAGE->context this may be problematic in CLI and other non-standard pages :-(.
|
||||
// In future we may want to add debugging here.
|
||||
$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.
|
||||
throw new \coding_exception(
|
||||
'Unable to identify context for format_string()',
|
||||
);
|
||||
$context = $PAGE->context;
|
||||
if (!$context) {
|
||||
// We did not find any context? weird.
|
||||
throw new \coding_exception(
|
||||
'Unable to identify context for format_string()',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate md5.
|
||||
$cachekeys = [
|
||||
$string,
|
||||
$striplinks,
|
||||
$options['context']->id,
|
||||
$options['escape'],
|
||||
$context->id,
|
||||
$escape,
|
||||
current_language(),
|
||||
$options['filter'],
|
||||
$filter,
|
||||
];
|
||||
$md5 = md5(implode('<+>', $cachekeys));
|
||||
|
||||
@ -113,17 +105,19 @@ class formatting {
|
||||
|
||||
// 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 ($escape) {
|
||||
$string = replace_ampersands_not_followed_by_entity($string);
|
||||
}
|
||||
|
||||
if (!empty($this->get_filterall()) && $options['filter']) {
|
||||
if (!empty($this->get_filterall()) && $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']);
|
||||
$filtermanager->setup_page_for_filters($PAGE, $context); // Setup global stuff filters may have.
|
||||
$string = $filtermanager->filter_string($string, $context);
|
||||
}
|
||||
|
||||
// If the site requires it, strip ALL tags from this string.
|
||||
if (!empty($this->get_striptags())) {
|
||||
if ($options['escape']) {
|
||||
if ($escape) {
|
||||
$string = str_replace(['<', '>'], ['<', '>'], strip_tags($string));
|
||||
} else {
|
||||
$string = strip_tags($string);
|
||||
@ -143,41 +137,40 @@ class formatting {
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, 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 <a> tags will have target="_blank" added unless target is explicitly specified.
|
||||
* </pre>
|
||||
*
|
||||
* @staticvar array $croncache
|
||||
* @param null|string $text The text to be formatted. This is raw text originally from user input.
|
||||
* @param string $format Identifier of the text format to be used
|
||||
* [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_MARKDOWN]
|
||||
* @param array $options text formatting options
|
||||
* [FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_MARKDOWN]
|
||||
* @param null|context $context The context used for filtering
|
||||
* @param bool $trusted If true the string won't be cleaned.
|
||||
* Note: FORMAT_MARKDOWN does not support trusted text.
|
||||
* @param null|bool $clean If true the string will be cleaned.
|
||||
* Note: This parameter is overridden if the text is trusted
|
||||
* @param bool $filter If true the string will be run through applicable filters as well.
|
||||
* @param bool $para If true then the returned string will be wrapped in div tags.
|
||||
* @param bool $newlines If true then lines newline breaks will be converted to HTML newline breaks.
|
||||
* @param bool $overflowdiv If set to true the formatted text will be encased in a div
|
||||
* @param bool $blanktarget If true all <a> tags will have target="_blank" added unless target is explicitly specified.
|
||||
* @param bool $allowid If true then id attributes will not be removed, even when using htmlpurifier.
|
||||
* @return string
|
||||
*/
|
||||
public function format_text(
|
||||
?string $text,
|
||||
string $format = FORMAT_MOODLE,
|
||||
array $options = [],
|
||||
?context $context = null,
|
||||
bool $trusted = false,
|
||||
?bool $clean = null,
|
||||
bool $filter = true,
|
||||
bool $para = true,
|
||||
bool $newlines = true,
|
||||
bool $overflowdiv = false,
|
||||
bool $blanktarget = false,
|
||||
bool $allowid = false,
|
||||
): string {
|
||||
global $CFG, $PAGE;
|
||||
|
||||
@ -186,64 +179,29 @@ class formatting {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Detach object, we can not modify it.
|
||||
$options = (array) $options;
|
||||
|
||||
if (!isset($options['trusted'])) {
|
||||
$options['trusted'] = false;
|
||||
}
|
||||
if ($format == FORMAT_MARKDOWN) {
|
||||
// Markdown format cannot be trusted in trusttext areas,
|
||||
// because we do not know how to sanitise it before editing.
|
||||
$options['trusted'] = false;
|
||||
$trusted = false;
|
||||
}
|
||||
if (!isset($options['noclean'])) {
|
||||
if ($options['trusted'] && trusttext_active()) {
|
||||
// No cleaning if text trusted and noclean not specified.
|
||||
$options['noclean'] = true;
|
||||
if ($clean === null) {
|
||||
if ($trusted && trusttext_active()) {
|
||||
// No cleaning if text trusted and clean not specified.
|
||||
$clean = false;
|
||||
} else {
|
||||
$options['noclean'] = false;
|
||||
$clean = true;
|
||||
}
|
||||
}
|
||||
if (!empty($this->get_forceclean())) {
|
||||
// Whatever the caller claims, the admin wants all content cleaned anyway.
|
||||
$options['noclean'] = false;
|
||||
$clean = true;
|
||||
}
|
||||
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 (!$this->should_filter_string()) {
|
||||
// Do not filter anything during installation or before upgrade completes.
|
||||
$context = null;
|
||||
} else if (isset($options['context'])) {
|
||||
// First by explicit passed context option.
|
||||
if ($options['context'] instanceof context) {
|
||||
$context = $options['context'];
|
||||
} else if (is_numeric($options['context'])) {
|
||||
$context = context::instance_by_id($options['context']);
|
||||
} else {
|
||||
debugging(
|
||||
'Unknown context passed to format_text(). Content will not be filtered.',
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
$context = null;
|
||||
}
|
||||
} else {
|
||||
} else if ($context === null) {
|
||||
// Fallback to $PAGE->context this may be problematic in CLI and other non-standard pages.
|
||||
// In future we may want to add debugging here.
|
||||
$context = $PAGE->context;
|
||||
@ -251,16 +209,15 @@ class formatting {
|
||||
|
||||
if (!$context) {
|
||||
// Either install/upgrade or something has gone really wrong because context does not exist (yet?).
|
||||
$options['nocache'] = true;
|
||||
$options['filter'] = false;
|
||||
$filter = false;
|
||||
}
|
||||
|
||||
if ($options['filter']) {
|
||||
if ($filter) {
|
||||
$filtermanager = \filter_manager::instance();
|
||||
$filtermanager->setup_page_for_filters($PAGE, $context); // Setup global stuff filters may have.
|
||||
$filteroptions = [
|
||||
'originalformat' => $format,
|
||||
'noclean' => $options['noclean'],
|
||||
'noclean' => !$clean,
|
||||
];
|
||||
} else {
|
||||
$filtermanager = new \null_filter_manager();
|
||||
@ -274,8 +231,10 @@ class formatting {
|
||||
// Text is already in HTML format, so just continue to the next filtering stage.
|
||||
$filteroptions['stage'] = 'pre_clean';
|
||||
$text = $filtermanager->filter_text($text, $context, $filteroptions);
|
||||
if (!$options['noclean']) {
|
||||
$text = clean_text($text, FORMAT_HTML, $options);
|
||||
if ($clean) {
|
||||
$text = clean_text($text, FORMAT_HTML, [
|
||||
'allowid' => $allowid,
|
||||
]);
|
||||
}
|
||||
$filteroptions['stage'] = 'post_clean';
|
||||
$text = $filtermanager->filter_text($text, $context, $filteroptions);
|
||||
@ -294,8 +253,10 @@ class formatting {
|
||||
$text = markdown_to_html($text);
|
||||
$filteroptions['stage'] = 'pre_clean';
|
||||
$text = $filtermanager->filter_text($text, $context, $filteroptions);
|
||||
if (!$options['noclean']) {
|
||||
$text = clean_text($text, FORMAT_HTML, $options);
|
||||
if ($clean) {
|
||||
$text = clean_text($text, FORMAT_HTML, [
|
||||
'allowid' => $allowid,
|
||||
]);
|
||||
}
|
||||
$filteroptions['stage'] = 'post_clean';
|
||||
$text = $filtermanager->filter_text($text, $context, $filteroptions);
|
||||
@ -304,11 +265,13 @@ class formatting {
|
||||
case FORMAT_MOODLE:
|
||||
$filteroptions['stage'] = 'pre_format';
|
||||
$text = $filtermanager->filter_text($text, $context, $filteroptions);
|
||||
$text = text_to_html($text, null, $options['para'], $options['newlines']);
|
||||
$text = text_to_html($text, null, $para, $newlines);
|
||||
$filteroptions['stage'] = 'pre_clean';
|
||||
$text = $filtermanager->filter_text($text, $context, $filteroptions);
|
||||
if (!$options['noclean']) {
|
||||
$text = clean_text($text, FORMAT_HTML, $options);
|
||||
if ($clean) {
|
||||
$text = clean_text($text, FORMAT_HTML, [
|
||||
'allowid' => $allowid,
|
||||
]);
|
||||
}
|
||||
$filteroptions['stage'] = 'post_clean';
|
||||
$text = $filtermanager->filter_text($text, $context, $filteroptions);
|
||||
@ -317,7 +280,7 @@ class formatting {
|
||||
throw new \coding_exception("Unkown format passed to format_text: {$format}");
|
||||
}
|
||||
|
||||
if ($options['filter']) {
|
||||
if ($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
|
||||
@ -334,11 +297,11 @@ class formatting {
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($options['overflowdiv'])) {
|
||||
if (!empty($overflowdiv)) {
|
||||
$text = \html_writer::tag('div', $text, ['class' => 'no-overflow']);
|
||||
}
|
||||
|
||||
if ($options['blanktarget']) {
|
||||
if ($blanktarget) {
|
||||
$domdoc = new \DOMDocument();
|
||||
libxml_use_internal_errors(true);
|
||||
$domdoc->loadHTML('<?xml version="1.0" encoding="UTF-8" ?>' . $text);
|
||||
|
@ -55,13 +55,16 @@ function atto_equation_params_for_js($elementid, $options, $fpoptions) {
|
||||
|
||||
// Format a string with the active filter set.
|
||||
// If it is modified - we assume that some sort of text filter is working in this context.
|
||||
$result = format_text($texexample, true, $options);
|
||||
$formatoptions = [
|
||||
'context' => $options['context'],
|
||||
'noclean' => $options['noclean'] ?? false,
|
||||
'trusted' => $options['trusted'] ?? false,
|
||||
];
|
||||
|
||||
$result = format_text($texexample, true, $formatoptions);
|
||||
|
||||
$texfilteractive = ($texexample !== $result);
|
||||
$context = $options['context'];
|
||||
if (!$context) {
|
||||
$context = context_system::instance();
|
||||
}
|
||||
|
||||
// Tex example librarys.
|
||||
$library = array(
|
||||
|
@ -75,13 +75,12 @@ class formatting_test extends \advanced_testcase {
|
||||
*/
|
||||
public function test_format_string_values(
|
||||
string $expected,
|
||||
mixed $input,
|
||||
array $options = [],
|
||||
array $params,
|
||||
): void {
|
||||
$formatting = new formatting();
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$formatting->format_string($input, ...$options),
|
||||
$formatting->format_string(...$params),
|
||||
);
|
||||
}
|
||||
|
||||
@ -93,24 +92,65 @@ class formatting_test extends \advanced_testcase {
|
||||
public static function format_string_provider(): array {
|
||||
return [
|
||||
// Ampersands.
|
||||
["& &&&&& &&", "& &&&&& &&"],
|
||||
["ANother & &&&&& Category", "ANother & &&&&& Category"],
|
||||
["ANother & &&&&& Category", "ANother & &&&&& Category", [true]],
|
||||
["Nick's Test Site & Other things", "Nick's Test Site & Other things", [true]],
|
||||
["& < > \" '", "& < > \" '", [true, ['escape' => false]]],
|
||||
[
|
||||
'expected' => "& &&&&& &&",
|
||||
'params' => ["& &&&&& &&"],
|
||||
],
|
||||
[
|
||||
'expected' => "ANother & &&&&& Category",
|
||||
'params' => ["ANother & &&&&& Category"],
|
||||
],
|
||||
[
|
||||
'expected' => "ANother & &&&&& Category",
|
||||
'params' => [
|
||||
'string' => "ANother & &&&&& Category",
|
||||
'striplinks' => true,
|
||||
],
|
||||
],
|
||||
[
|
||||
'expected' => "Nick's Test Site & Other things",
|
||||
'params' => [
|
||||
'string' => "Nick's Test Site & Other things",
|
||||
'striplinks' => true,
|
||||
],
|
||||
],
|
||||
[
|
||||
'expected' => "& < > \" '",
|
||||
'params' => [
|
||||
'string' => "& < > \" '",
|
||||
'striplinks' => true,
|
||||
'escape' => false,
|
||||
],
|
||||
],
|
||||
|
||||
// String entities.
|
||||
[""", """],
|
||||
[
|
||||
'expected' => """,
|
||||
'params' => ["""],
|
||||
],
|
||||
|
||||
// Digital entities.
|
||||
["&11234;", "&11234;"],
|
||||
[
|
||||
'expected' => "&11234;",
|
||||
'params' => ["&11234;"],
|
||||
],
|
||||
|
||||
// Unicode entities.
|
||||
["ᅻ", "ᅻ"],
|
||||
[
|
||||
'expected' => "ᅻ",
|
||||
'params' => ["ᅻ"],
|
||||
],
|
||||
|
||||
// Nulls.
|
||||
['', null],
|
||||
['', null, [true, ['escape' => false]]],
|
||||
['', [null]],
|
||||
[
|
||||
'expected' => '',
|
||||
'params' => [
|
||||
'string' => null,
|
||||
'striplinks' => true,
|
||||
'escape' => false,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -130,9 +170,9 @@ class formatting_test extends \advanced_testcase {
|
||||
$rawstring = '<span lang="en" class="multilang">English</span><span lang="ca" class="multilang">Catalan</span>';
|
||||
$expectednofilter = strip_tags($rawstring);
|
||||
$expectedfilter = 'English';
|
||||
$striplinks = true;
|
||||
$context = \core\context\course::instance($course->id);
|
||||
$options = [
|
||||
'striplinks' => true,
|
||||
'context' => $context,
|
||||
'escape' => true,
|
||||
'filter' => false,
|
||||
@ -144,7 +184,7 @@ class formatting_test extends \advanced_testcase {
|
||||
|
||||
// Format the string without filters. It should just strip the
|
||||
// links.
|
||||
$nofilterresult = $formatting->format_string($rawstring, $striplinks, $options);
|
||||
$nofilterresult = $formatting->format_string($rawstring, ...$options);
|
||||
$this->assertEquals($expectednofilter, $nofilterresult);
|
||||
|
||||
// Add the multilang filter. Make sure it's enabled globally.
|
||||
@ -153,23 +193,23 @@ class formatting_test extends \advanced_testcase {
|
||||
filter_set_local_state('multilang', $context->id, TEXTFILTER_ON);
|
||||
|
||||
// Even after setting the filters, no filters are applied yet.
|
||||
$nofilterresult = $formatting->format_string($rawstring, $striplinks, $options);
|
||||
$nofilterresult = $formatting->format_string($rawstring,...$options);
|
||||
$this->assertEquals($expectednofilter, $nofilterresult);
|
||||
|
||||
// Apply the filter as an option.
|
||||
$options['filter'] = true;
|
||||
$filterresult = $formatting->format_string($rawstring, $striplinks, $options);
|
||||
$filterresult = $formatting->format_string($rawstring, ...$options);
|
||||
$this->assertMatchesRegularExpression("/$expectedfilter/", $filterresult);
|
||||
|
||||
// Apply it as a formatting setting.
|
||||
unset($options['filter']);
|
||||
$formatting->set_filterall(true);
|
||||
$filterresult = $formatting->format_string($rawstring, $striplinks, $options);
|
||||
$filterresult = $formatting->format_string($rawstring, ...$options);
|
||||
$this->assertMatchesRegularExpression("/$expectedfilter/", $filterresult);
|
||||
|
||||
// Unset it and we do not filter.
|
||||
$formatting->set_filterall(false);
|
||||
$nofilterresult = $formatting->format_string($rawstring, $striplinks, $options);
|
||||
$nofilterresult = $formatting->format_string($rawstring, ...$options);
|
||||
$this->assertEquals($expectednofilter, $nofilterresult);
|
||||
|
||||
// Set it again.
|
||||
@ -179,7 +219,7 @@ class formatting_test extends \advanced_testcase {
|
||||
// Confirm that we get back the cached string. The result should be
|
||||
// the same as the filtered text above even though we've disabled the
|
||||
// multilang filter in between.
|
||||
$cachedresult = $formatting->format_string($rawstring, $striplinks, $options);
|
||||
$cachedresult = $formatting->format_string($rawstring, ...$options);
|
||||
$this->assertMatchesRegularExpression("/$expectedfilter/", $cachedresult);
|
||||
}
|
||||
|
||||
@ -205,7 +245,7 @@ class formatting_test extends \advanced_testcase {
|
||||
$formatter = new formatting();
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$formatter->format_text($input, $format, $options),
|
||||
$formatter->format_text($input, $format, ...$options),
|
||||
);
|
||||
}
|
||||
|
||||
@ -333,14 +373,14 @@ class formatting_test extends \advanced_testcase {
|
||||
1,
|
||||
$text,
|
||||
FORMAT_MARKDOWN,
|
||||
['trusted' => true, 'noclean' => true],
|
||||
['trusted' => true, 'clean' => false],
|
||||
],
|
||||
[
|
||||
"<p>lala <object>xx</object></p>\n",
|
||||
1,
|
||||
$text,
|
||||
FORMAT_MARKDOWN,
|
||||
['trusted' => false, 'noclean' => true],
|
||||
['trusted' => false, 'clean' => false],
|
||||
],
|
||||
];
|
||||
}
|
||||
@ -364,7 +404,11 @@ class formatting_test extends \advanced_testcase {
|
||||
filter_set_global_state('emoticon', TEXTFILTER_ON);
|
||||
$this->assertEquals(
|
||||
'<p>:-)</p>',
|
||||
$formatter->format_text('<p>:-)</p>', FORMAT_HTML, ['filter' => false])
|
||||
$formatter->format_text(
|
||||
'<p>:-)</p>',
|
||||
FORMAT_HTML,
|
||||
filter: false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -387,7 +431,11 @@ class formatting_test extends \advanced_testcase {
|
||||
filter_set_global_state('emoticon', TEXTFILTER_ON);
|
||||
$this->assertEquals(
|
||||
':-)',
|
||||
$formatter->format_text(':-)', FORMAT_PLAIN, ['filter' => false])
|
||||
$formatter->format_text(
|
||||
':-)',
|
||||
FORMAT_PLAIN,
|
||||
filter: false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -411,7 +459,7 @@ class formatting_test extends \advanced_testcase {
|
||||
filter_set_global_state('emoticon', TEXTFILTER_ON);
|
||||
$this->assertEquals(
|
||||
"<p><em>:-)</em></p>\n",
|
||||
$formatter->format_text('*:-)*', FORMAT_MARKDOWN, ['filter' => false])
|
||||
$formatter->format_text('*:-)*', FORMAT_MARKDOWN, filter: false)
|
||||
);
|
||||
}
|
||||
|
||||
@ -435,7 +483,7 @@ class formatting_test extends \advanced_testcase {
|
||||
filter_set_global_state('emoticon', TEXTFILTER_ON);
|
||||
$this->assertEquals(
|
||||
'<div class="text_to_html"><p>:-)</p></div>',
|
||||
$formatter->format_text('<p>:-)</p>', FORMAT_MOODLE, ['filter' => false])
|
||||
$formatter->format_text('<p>:-)</p>', FORMAT_MOODLE, filter: false)
|
||||
);
|
||||
}
|
||||
|
||||
@ -460,23 +508,16 @@ class formatting_test extends \advanced_testcase {
|
||||
|
||||
$this->assertSame(
|
||||
'<p>Read <a class="autolink" title="Test 1" href="' . $pageurl . '">Test 1</a>.</p>',
|
||||
$formatter->format_text('<p>Read Test 1.</p>', FORMAT_HTML, ['context' => $context]),
|
||||
$formatter->format_text('<p>Read Test 1.</p>', FORMAT_HTML, context: $context),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'<p>Read <a class="autolink" title="Test 1" href="' . $pageurl . '">Test 1</a>.</p>',
|
||||
$formatter->format_text('<p>Read Test 1.</p>', FORMAT_HTML, ['context' => $context, 'noclean' => true]),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'<p>Read Test 1.</p>',
|
||||
$formatter->format_text(
|
||||
'<p><nolink>Read Test 1.</nolink></p>',
|
||||
'<p>Read Test 1.</p>',
|
||||
FORMAT_HTML,
|
||||
[
|
||||
'context' => $context,
|
||||
'noclean' => false,
|
||||
],
|
||||
context: $context,
|
||||
clean: false,
|
||||
),
|
||||
);
|
||||
|
||||
@ -485,16 +526,28 @@ class formatting_test extends \advanced_testcase {
|
||||
$formatter->format_text(
|
||||
'<p><nolink>Read Test 1.</nolink></p>',
|
||||
FORMAT_HTML,
|
||||
[
|
||||
'context' => $context,
|
||||
'noclean' => true,
|
||||
],
|
||||
context: $context,
|
||||
clean: true,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'<p>Read Test 1.</p>',
|
||||
$formatter->format_text(
|
||||
'<p><nolink>Read Test 1.</nolink></p>',
|
||||
FORMAT_HTML,
|
||||
context: $context,
|
||||
clean: false,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'<p><span class="nolink">Read Test 1.</span></p>',
|
||||
$formatter->format_text('<p><span class="nolink">Read Test 1.</span></p>', FORMAT_HTML, ['context' => $context]),
|
||||
$formatter->format_text(
|
||||
'<p><span class="nolink">Read Test 1.</span></p>',
|
||||
FORMAT_HTML,
|
||||
context: $context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -503,7 +556,11 @@ class formatting_test extends \advanced_testcase {
|
||||
|
||||
$this->assertEquals(
|
||||
'<div class="no-overflow"><p>Hello world</p></div>',
|
||||
$formatter->format_text('<p>Hello world</p>', FORMAT_HTML, ['overflowdiv' => true]),
|
||||
$formatter->format_text(
|
||||
'<p>Hello world</p>',
|
||||
FORMAT_HTML,
|
||||
overflowdiv: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -516,7 +573,13 @@ class formatting_test extends \advanced_testcase {
|
||||
*/
|
||||
public function test_format_text_blanktarget($link, $expected): void {
|
||||
$formatter = new formatting();
|
||||
$actual = $formatter->format_text($link, FORMAT_MOODLE, ['blanktarget' => true, 'filter' => false, 'noclean' => true]);
|
||||
$actual = $formatter->format_text(
|
||||
$link,
|
||||
FORMAT_MOODLE,
|
||||
blanktarget: true,
|
||||
filter: false,
|
||||
clean: false,
|
||||
);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@ -588,19 +651,19 @@ class formatting_test extends \advanced_testcase {
|
||||
$formatter = new formatting();
|
||||
|
||||
$formatter->set_forceclean(false);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => false]);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: true);
|
||||
$this->assertEquals($cleaned, $actual);
|
||||
|
||||
$formatter->set_forceclean(true);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => false]);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: true);
|
||||
$this->assertEquals($cleaned, $actual);
|
||||
|
||||
$formatter->set_forceclean(false);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => true]);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: false);
|
||||
$this->assertEquals($nocleaned, $actual);
|
||||
|
||||
$formatter->set_forceclean(true);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => true]);
|
||||
$actual = $formatter->format_text($input, FORMAT_HTML, filter: false, clean: false);
|
||||
$this->assertEquals($cleaned, $actual);
|
||||
}
|
||||
|
||||
|
145
lib/weblib.php
145
lib/weblib.php
@ -1252,7 +1252,6 @@ function format_text_menu() {
|
||||
* 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]
|
||||
@ -1281,46 +1280,81 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd
|
||||
' Please pass an array with a context key instead.',
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
$options = ['context' => $options];
|
||||
$params['context'] = $options;
|
||||
$options = [];
|
||||
}
|
||||
|
||||
if ($options) {
|
||||
$options = (array) $options;
|
||||
}
|
||||
|
||||
if ($options instanceof \core\context) {
|
||||
// A common mistake has been to call this function with a context object.
|
||||
// This has never been expected, nor supported.
|
||||
debugging(
|
||||
'The options argument should not be a context object directly. ' .
|
||||
' Please pass an array with a context key instead.',
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
$options = ['context' => $options];
|
||||
}
|
||||
|
||||
if (empty($CFG->version) || $CFG->version < 2013051400 || during_initial_install()) {
|
||||
// Do not filter anything during installation or before upgrade completes.
|
||||
$params['context'] = null;
|
||||
} else if ($options && isset($options['context'])) { // First by explicit passed context option.
|
||||
// Do not do anything.
|
||||
if (is_numeric($options['context'])) {
|
||||
// A contextid was passed.
|
||||
$params['context'] = \core\context::instance_by_id($options['context']);
|
||||
} else if ($options['context'] instanceof \core\context) {
|
||||
$params['context'] = $options['context'];
|
||||
} else {
|
||||
debugging(
|
||||
'Unknown context passed to format_text(). Content will not be filtered.',
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
}
|
||||
|
||||
// Unset the context from $options to prevent it overriding the configured value.
|
||||
unset($options['context']);
|
||||
} else if ($courseiddonotuse) {
|
||||
// Legacy courseid.
|
||||
if (!$options) {
|
||||
$options = [];
|
||||
}
|
||||
$options['context'] = \core\context\course::instance($courseiddonotuse);
|
||||
$params['context'] = \core\context\course::instance($courseiddonotuse);
|
||||
debugging(
|
||||
"Passing a courseid to format_text() is deprecated, please pass a context instead.",
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'text' => $text,
|
||||
];
|
||||
$params['text'] = $text;
|
||||
|
||||
if ($options) {
|
||||
$params['options'] = $options;
|
||||
$validoptions = [
|
||||
'text',
|
||||
'format',
|
||||
'context',
|
||||
'trusted',
|
||||
'clean',
|
||||
'filter',
|
||||
'para',
|
||||
'newlines',
|
||||
'overflowdiv',
|
||||
'blanktarget',
|
||||
'allowid',
|
||||
'noclean',
|
||||
'nocache',
|
||||
'smiley',
|
||||
];
|
||||
|
||||
$invalidoptions = array_diff(array_keys($options), $validoptions);
|
||||
if ($invalidoptions) {
|
||||
debugging(sprintf(
|
||||
'The following options are not valid: %s',
|
||||
implode(', ', $invalidoptions),
|
||||
), DEBUG_DEVELOPER);
|
||||
foreach ($invalidoptions as $option) {
|
||||
unset($options[$option]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($options as $option => $value) {
|
||||
$params[$option] = $value;
|
||||
}
|
||||
|
||||
// The noclean option has been renamed to clean.
|
||||
if (array_key_exists('noclean', $params)) {
|
||||
$params['clean'] = !$params['noclean'];
|
||||
unset($params['noclean']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($format !== null) {
|
||||
@ -1402,23 +1436,70 @@ function format_string($string, $striplinks = true, $options = null) {
|
||||
' Please pass an array with a context key instead.',
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
$options = ['context' => $options];
|
||||
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
|
||||
} else if ($options === null || is_numeric($options) || is_array($options) || is_a($options, \stdClass::class)) {
|
||||
// Do nothing. These are accepted options.
|
||||
// Pass to the new method.
|
||||
} else {
|
||||
$params['context'] = $options;
|
||||
$options = [];
|
||||
} else if (is_numeric($options)) {
|
||||
// Legacy courseid usage.
|
||||
$params['context'] = \core\context\course::instance($options);
|
||||
$options = [];
|
||||
} else if (is_array($options) || is_a($options, \stdClass::class)) {
|
||||
$options = (array) $options;
|
||||
if (isset($options['context'])) {
|
||||
if (is_numeric($options['context'])) {
|
||||
// A contextid was passed usage.
|
||||
$params['context'] = \core\context::instance_by_id($options['context']);
|
||||
} else if ($options['context'] instanceof \core\context) {
|
||||
$params['context'] = $options['context'];
|
||||
} else {
|
||||
debugging(
|
||||
'An invalid value for context was provided.',
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if ($options !== null) {
|
||||
// Something else was passed, so we'll just use an empty array.
|
||||
// Attempt to cast to array since we always used to, but throw in some debugging.
|
||||
debugging(sprintf(
|
||||
'The options argument should be an Array, or stdclass. %s passed.',
|
||||
gettype($options),
|
||||
), DEBUG_DEVELOPER);
|
||||
$options = (array) $options;
|
||||
|
||||
// Attempt to cast to array since we always used to, but throw in some debugging.
|
||||
$options = array_filter(
|
||||
(array) $options,
|
||||
fn ($key) => !is_numeric($key),
|
||||
ARRAY_FILTER_USE_KEY,
|
||||
);
|
||||
}
|
||||
|
||||
if ($options !== null) {
|
||||
$params['options'] = $options;
|
||||
if (isset($options['filter'])) {
|
||||
$params['filter'] = (bool) $options['filter'];
|
||||
} else {
|
||||
$params['filter'] = true;
|
||||
}
|
||||
|
||||
if (isset($options['escape'])) {
|
||||
$params['escape'] = (bool) $options['escape'];
|
||||
} else {
|
||||
$params['escape'] = true;
|
||||
}
|
||||
|
||||
$validoptions = [
|
||||
'string',
|
||||
'striplinks',
|
||||
'context',
|
||||
'filter',
|
||||
'escape',
|
||||
];
|
||||
|
||||
if ($options) {
|
||||
$invalidoptions = array_diff(array_keys($options), $validoptions);
|
||||
if ($invalidoptions) {
|
||||
debugging(sprintf(
|
||||
'The following options are not valid: %s',
|
||||
implode(', ', $invalidoptions),
|
||||
), DEBUG_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
return \core\di::get(\core\formatting::class)->format_string(
|
||||
|
@ -4272,12 +4272,16 @@ abstract class lesson_page extends lesson_base {
|
||||
} else if (!$result->isessayquestion) {
|
||||
$class .= ' incorrect'; // CSS over-ride this if they exist (!important).
|
||||
}
|
||||
$options = new stdClass;
|
||||
$options->noclean = true;
|
||||
$options->para = true;
|
||||
$options->overflowdiv = true;
|
||||
$options->context = $context;
|
||||
$options->attemptid = isset($attempt) ? $attempt->id : null;
|
||||
|
||||
$options = [
|
||||
'noclean' => true,
|
||||
'para' => true,
|
||||
'overflowdiv' => true,
|
||||
'context' => $context,
|
||||
];
|
||||
$answeroptions = (object) array_merge($options, [
|
||||
'attemptid' => $attempt->id ?? null,
|
||||
]);
|
||||
|
||||
$result->feedback .= $OUTPUT->box(format_text($this->get_contents(), $this->properties->contentsformat, $options),
|
||||
'generalbox boxaligncenter py-3');
|
||||
@ -4294,7 +4298,7 @@ abstract class lesson_page extends lesson_base {
|
||||
|
||||
foreach ($studentanswerresponse as $answer => $response) {
|
||||
// Add a table row containing the answer.
|
||||
$studentanswer = $this->format_answer($answer, $context, $result->studentanswerformat, $options);
|
||||
$studentanswer = $this->format_answer($answer, $context, $result->studentanswerformat, $answeroptions);
|
||||
$table->data[] = array($studentanswer);
|
||||
// If the response exists, add a table row containing the response. If not, add en empty row.
|
||||
if (!empty(trim($response))) {
|
||||
@ -4309,7 +4313,7 @@ abstract class lesson_page extends lesson_base {
|
||||
}
|
||||
} else {
|
||||
// Add a table row containing the answer.
|
||||
$studentanswer = $this->format_answer($result->studentanswer, $context, $result->studentanswerformat, $options);
|
||||
$studentanswer = $this->format_answer($result->studentanswer, $context, $result->studentanswerformat, $answeroptions);
|
||||
$table->data[] = array($studentanswer);
|
||||
// If the response exists, add a table row containing the response. If not, add en empty row.
|
||||
if (!empty(trim($result->response))) {
|
||||
@ -4339,7 +4343,6 @@ abstract class lesson_page extends lesson_base {
|
||||
* @return string Returns formatted string
|
||||
*/
|
||||
public function format_answer($answer, $context, $answerformat, $options = []) {
|
||||
|
||||
if (is_object($options)) {
|
||||
$options = (array) $options;
|
||||
}
|
||||
@ -4352,6 +4355,9 @@ abstract class lesson_page extends lesson_base {
|
||||
$options['para'] = true;
|
||||
}
|
||||
|
||||
// The attemptid is used by some plugins but is not a valid argument to format_text.
|
||||
unset($options['attemptid']);
|
||||
|
||||
return format_text($answer, $answerformat, $options);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user