From 7d94679ff104d7a0d0bb00e4118617d3104e8ff9 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Fri, 5 Jun 2015 15:58:18 +0100 Subject: [PATCH] MDL-50491 filters: option to disable some filters in filter_text. This lets you programmatically prevent certain filters running on certain content. Also, I created some unit tests for the basic format_text functionality since there did not seem to be any yet. --- filter/upgrade.txt | 2 + lib/filterlib.php | 33 ++++++---- lib/tests/filter_manager_test.php | 82 ++++++++++++++++++++++++ lib/tests/weblib_format_text_test.php | 90 +++++++++++++++++++++++++++ lib/weblib.php | 20 +++--- 5 files changed, 203 insertions(+), 24 deletions(-) create mode 100644 lib/tests/filter_manager_test.php create mode 100644 lib/tests/weblib_format_text_test.php diff --git a/filter/upgrade.txt b/filter/upgrade.txt index 2570898291a..ddfbd82c4bc 100644 --- a/filter/upgrade.txt +++ b/filter/upgrade.txt @@ -3,6 +3,8 @@ information provided here is intended especially for developers. === 3.0 === +* New argument $skipfilters to filter_manager::filter_text to allow applying + the filters with a given one omitted. * New admin setting class admin_setting_filter_types which can be used if you want to make the disablefilters value in your code configurable. diff --git a/lib/filterlib.php b/lib/filterlib.php index 3a3ce1d957a..ba447f53705 100644 --- a/lib/filterlib.php +++ b/lib/filterlib.php @@ -54,13 +54,13 @@ define('TEXTFILTER_EXCL_SEPARATOR', '-%-'); class filter_manager { /** * @var moodle_text_filter[][] This list of active filters, by context, for filtering content. - * An array contextid => array of filter objects. + * An array contextid => ordered array of filter name => filter objects. */ protected $textfilters = array(); /** * @var moodle_text_filter[][] This list of active filters, by context, for filtering strings. - * An array contextid => array of filter objects. + * An array contextid => ordered array of filter name => filter objects. */ protected $stringfilters = array(); @@ -127,9 +127,9 @@ class filter_manager { if (is_null($filter)) { continue; } - $this->textfilters[$context->id][] = $filter; + $this->textfilters[$context->id][$filtername] = $filter; if (in_array($filtername, $this->stringfilternames)) { - $this->stringfilters[$context->id][] = $filter; + $this->stringfilters[$context->id][$filtername] = $filter; } } } @@ -162,12 +162,17 @@ class filter_manager { /** * Apply a list of filters to some content. * @param string $text - * @param moodle_text_filter[] $filterchain + * @param moodle_text_filter[] $filterchain array filter name => filter object. * @param array $options options passed to the filters. + * @param array $skipfilters of filter names. Any filters that should not be applied to this text. * @return string $text */ - protected function apply_filter_chain($text, $filterchain, array $options = array()) { - foreach ($filterchain as $filter) { + protected function apply_filter_chain($text, $filterchain, array $options = array(), + array $skipfilters = null) { + foreach ($filterchain as $filtername => $filter) { + if ($skipfilters !== null && in_array($filtername, $skipfilters)) { + continue; + } $text = $filter->filter($text, $options); } return $text; @@ -205,10 +210,12 @@ class filter_manager { * @param string $text The text to filter * @param context $context the context. * @param array $options options passed to the filters + * @param array $skipfilters of filter names. Any filters that should not be applied to this text. * @return string resulting text */ - public function filter_text($text, $context, array $options = array()) { - $text = $this->apply_filter_chain($text, $this->get_text_filters($context), $options); + public function filter_text($text, $context, array $options = array(), + array $skipfilters = null) { + $text = $this->apply_filter_chain($text, $this->get_text_filters($context), $options, $skipfilters); // tags removed for XHTML compatibility $text = str_replace(array('', ''), '', $text); return $text; @@ -275,7 +282,8 @@ class filter_manager { * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class null_filter_manager { - public function filter_text($text, $context, $options) { + public function filter_text($text, $context, array $options = array(), + array $skipfilters = null) { return $text; } @@ -320,9 +328,10 @@ class performance_measuring_filter_manager extends filter_manager { return parent::make_filter_object($filtername, $context, $localconfig); } - public function filter_text($text, $context, array $options = array()) { + public function filter_text($text, $context, array $options = array(), + array $skipfilters = null) { $this->textsfiltered++; - return parent::filter_text($text, $context, $options); + return parent::filter_text($text, $context, $options, $skipfilters); } public function filter_string($string, $context) { diff --git a/lib/tests/filter_manager_test.php b/lib/tests/filter_manager_test.php new file mode 100644 index 00000000000..85b7632e4d3 --- /dev/null +++ b/lib/tests/filter_manager_test.php @@ -0,0 +1,82 @@ +. + +/** + * Unit tests for the {@link filter_manager} class. + * + * @package core + * @category test + * @copyright 2015 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Unit tests for the {@link filter_manager} class. + * + * @copyright 2015 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ +class core_filter_manager_testcase extends advanced_testcase { + + /** + * Helper method to apply filters to some text and return the result. + * @param string $text the text to filter. + * @param array $skipfilters any filters not to apply, even if they are configured. + * @return string the filtered text. + */ + protected function filter_text($text, $skipfilters) { + global $PAGE; + $filtermanager = filter_manager::instance(); + $filtermanager->setup_page_for_filters($PAGE, $PAGE->context); + $filteroptions = array( + 'originalformat' => FORMAT_HTML, + 'noclean' => false, + ); + return $filtermanager->filter_text($text, $PAGE->context, $filteroptions, $skipfilters); + } + + public function test_filter_normal() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertRegExp('~^

smile]+)>

$~', + $this->filter_text('

:-)

', array())); + } + + public function test_one_filter_disabled() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertEquals('

:-)

', + $this->filter_text('

:-)

', array('emoticon'))); + } + + public function test_disabling_other_filter_does_not_break_it() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertRegExp('~^

smile]+)>

$~', + $this->filter_text('

:-)

', array('urltolink'))); + } + + public function test_one_filter_of_two_disabled() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + filter_set_global_state('urltolink', TEXTFILTER_ON); + $this->assertRegExp('~^

smile]+)> http://google.com/

$~', + $this->filter_text('

:-) http://google.com/

', array('glossary', 'urltolink'))); + } +} diff --git a/lib/tests/weblib_format_text_test.php b/lib/tests/weblib_format_text_test.php new file mode 100644 index 00000000000..dce2330a22b --- /dev/null +++ b/lib/tests/weblib_format_text_test.php @@ -0,0 +1,90 @@ +. + +/** + * Unit tests for format_text defined in weblib.php. + * + * @package core + * @category test + * @copyright 2015 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Unit tests for format_text defined in weblib.php. + * + * @copyright 2015 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ +class core_weblib_format_text_testcase extends advanced_testcase { + + public function test_format_text_format_html() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertRegExp('~^

smile]+)>

$~', + format_text('

:-)

', FORMAT_HTML)); + } + + public function test_format_text_format_html_no_filters() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertEquals('

:-)

', + format_text('

:-)

', FORMAT_HTML, array('filter' => false))); + } + + public function test_format_text_format_plain() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertEquals(':-)', + format_text(':-)', FORMAT_PLAIN, array('filter' => false))); + } + + public function test_format_text_format_markdown() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertRegExp('~^

smile]+)>

\n$~', + format_text('*:-)*', FORMAT_MARKDOWN)); + } + + public function test_format_text_format_markdown_nofilter() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertEquals("

:-)

\n", + format_text('*:-)*', FORMAT_MARKDOWN, array('filter' => false))); + } + + public function test_format_text_format_moodle() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertRegExp('~^

smile]+)>

$~', + format_text('

:-)

', FORMAT_MOODLE)); + } + + public function test_format_text_format_moodle_no_filters() { + $this->resetAfterTest(); + filter_set_global_state('emoticon', TEXTFILTER_ON); + $this->assertEquals('

:-)

', + format_text('

:-)

', FORMAT_MOODLE, array('filter' => false))); + } + + public function test_format_text_overflowdiv() { + $this->assertEquals('

:-)

', + format_text('

:-)

', FORMAT_HTML, array('overflowdiv' => true))); + } +} diff --git a/lib/weblib.php b/lib/weblib.php index 8f060a06552..696caa53e0c 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -1204,8 +1204,13 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd 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) { @@ -1213,10 +1218,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd if (!$options['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } - $text = $filtermanager->filter_text($text, $context, array( - 'originalformat' => FORMAT_HTML, - 'noclean' => $options['noclean'] - )); + $text = $filtermanager->filter_text($text, $context, $filteroptions); break; case FORMAT_PLAIN: @@ -1239,10 +1241,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd if (!$options['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } - $text = $filtermanager->filter_text($text, $context, array( - 'originalformat' => FORMAT_MARKDOWN, - 'noclean' => $options['noclean'] - )); + $text = $filtermanager->filter_text($text, $context, $filteroptions); break; default: // FORMAT_MOODLE or anything else. @@ -1250,10 +1249,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd if (!$options['noclean']) { $text = clean_text($text, FORMAT_HTML, $options); } - $text = $filtermanager->filter_text($text, $context, array( - 'originalformat' => $format, - 'noclean' => $options['noclean'] - )); + $text = $filtermanager->filter_text($text, $context, $filteroptions); break; } if ($options['filter']) {