See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core; 18 19 /** 20 * Unit tests for format_text defined in weblib.php. 21 * 22 * @covers ::format_text 23 * 24 * @package core 25 * @category test 26 * @copyright 2015 The Open University 27 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 28 * @covers ::format_text 29 */ 30 class weblib_format_text_test extends \advanced_testcase { 31 32 public function test_format_text_format_html() { 33 $this->resetAfterTest(); 34 filter_set_global_state('emoticon', TEXTFILTER_ON); 35 $this->assertMatchesRegularExpression('~^<p><img class="icon emoticon" alt="smile" title="smile" ' . 36 'src="https://www.example.com/moodle/theme/image.php/_s/boost/core/1/s/smiley" /></p>$~', 37 format_text('<p>:-)</p>', FORMAT_HTML)); 38 } 39 40 public function test_format_text_format_html_no_filters() { 41 $this->resetAfterTest(); 42 filter_set_global_state('emoticon', TEXTFILTER_ON); 43 $this->assertEquals('<p>:-)</p>', 44 format_text('<p>:-)</p>', FORMAT_HTML, array('filter' => false))); 45 } 46 47 public function test_format_text_format_plain() { 48 // Note FORMAT_PLAIN does not filter ever, no matter we ask for filtering. 49 $this->resetAfterTest(); 50 filter_set_global_state('emoticon', TEXTFILTER_ON); 51 $this->assertEquals(':-)', 52 format_text(':-)', FORMAT_PLAIN)); 53 } 54 55 public function test_format_text_format_plain_no_filters() { 56 $this->resetAfterTest(); 57 filter_set_global_state('emoticon', TEXTFILTER_ON); 58 $this->assertEquals(':-)', 59 format_text(':-)', FORMAT_PLAIN, array('filter' => false))); 60 } 61 62 public function test_format_text_format_markdown() { 63 $this->resetAfterTest(); 64 filter_set_global_state('emoticon', TEXTFILTER_ON); 65 $this->assertMatchesRegularExpression('~^<p><em><img class="icon emoticon" alt="smile" title="smile" ' . 66 'src="https://www.example.com/moodle/theme/image.php/_s/boost/core/1/s/smiley" />' . 67 '</em></p>\n$~', 68 format_text('*:-)*', FORMAT_MARKDOWN)); 69 } 70 71 public function test_format_text_format_markdown_nofilter() { 72 $this->resetAfterTest(); 73 filter_set_global_state('emoticon', TEXTFILTER_ON); 74 $this->assertEquals("<p><em>:-)</em></p>\n", 75 format_text('*:-)*', FORMAT_MARKDOWN, array('filter' => false))); 76 } 77 78 public function test_format_text_format_moodle() { 79 $this->resetAfterTest(); 80 filter_set_global_state('emoticon', TEXTFILTER_ON); 81 $this->assertMatchesRegularExpression('~^<div class="text_to_html"><p>' . 82 '<img class="icon emoticon" alt="smile" title="smile" ' . 83 'src="https://www.example.com/moodle/theme/image.php/_s/boost/core/1/s/smiley" /></p></div>$~', 84 format_text('<p>:-)</p>', FORMAT_MOODLE)); 85 } 86 87 public function test_format_text_format_moodle_no_filters() { 88 $this->resetAfterTest(); 89 filter_set_global_state('emoticon', TEXTFILTER_ON); 90 $this->assertEquals('<div class="text_to_html"><p>:-)</p></div>', 91 format_text('<p>:-)</p>', FORMAT_MOODLE, array('filter' => false))); 92 } 93 94 /** 95 * Make sure that nolink tags and spans prevent linking in filters that support it. 96 */ 97 public function test_format_text_nolink() { 98 global $CFG; 99 $this->resetAfterTest(); 100 filter_set_global_state('activitynames', TEXTFILTER_ON); 101 102 $course = $this->getDataGenerator()->create_course(); 103 $context = \context_course::instance($course->id); 104 $page = $this->getDataGenerator()->create_module('page', 105 ['course' => $course->id, 'name' => 'Test 1']); 106 $cm = get_coursemodule_from_instance('page', $page->id, $page->course, false, MUST_EXIST); 107 $pageurl = $CFG->wwwroot. '/mod/page/view.php?id=' . $cm->id; 108 109 $this->assertSame( 110 '<p>Read <a class="autolink" title="Test 1" href="' . $pageurl . '">Test 1</a>.</p>', 111 format_text('<p>Read Test 1.</p>', FORMAT_HTML, ['context' => $context])); 112 113 $this->assertSame( 114 '<p>Read <a class="autolink" title="Test 1" href="' . $pageurl . '">Test 1</a>.</p>', 115 format_text('<p>Read Test 1.</p>', FORMAT_HTML, ['context' => $context, 'noclean' => true])); 116 117 $this->assertSame( 118 '<p>Read Test 1.</p>', 119 format_text('<p><nolink>Read Test 1.</nolink></p>', FORMAT_HTML, ['context' => $context, 'noclean' => false])); 120 121 $this->assertSame( 122 '<p>Read Test 1.</p>', 123 format_text('<p><nolink>Read Test 1.</nolink></p>', FORMAT_HTML, ['context' => $context, 'noclean' => true])); 124 125 $this->assertSame( 126 '<p><span class="nolink">Read Test 1.</span></p>', 127 format_text('<p><span class="nolink">Read Test 1.</span></p>', FORMAT_HTML, ['context' => $context])); 128 } 129 130 public function test_format_text_overflowdiv() { 131 $this->assertEquals('<div class="no-overflow"><p>Hello world</p></div>', 132 format_text('<p>Hello world</p>', FORMAT_HTML, array('overflowdiv' => true))); 133 } 134 135 /** 136 * Test adding blank target attribute to links 137 * 138 * @dataProvider format_text_blanktarget_testcases 139 * @param string $link The link to add target="_blank" to 140 * @param string $expected The expected filter value 141 */ 142 public function test_format_text_blanktarget($link, $expected) { 143 $actual = format_text($link, FORMAT_MOODLE, array('blanktarget' => true, 'filter' => false, 'noclean' => true)); 144 $this->assertEquals($expected, $actual); 145 } 146 147 /** 148 * Data provider for the test_format_text_blanktarget testcase 149 * 150 * @return array of testcases 151 */ 152 public function format_text_blanktarget_testcases() { 153 return [ 154 'Simple link' => [ 155 '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4">Hey, that\'s pretty good!</a>', 156 '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' . 157 ' rel="noreferrer">Hey, that\'s pretty good!</a></div>' 158 ], 159 'Link with rel' => [ 160 '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow">Hey, that\'s pretty good!</a>', 161 '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow noreferrer"' . 162 ' target="_blank">Hey, that\'s pretty good!</a></div>' 163 ], 164 'Link with rel noreferrer' => [ 165 '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer">Hey, that\'s pretty good!</a>', 166 '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer"' . 167 ' target="_blank">Hey, that\'s pretty good!</a></div>' 168 ], 169 'Link with target' => [ 170 '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">Hey, that\'s pretty good!</a>', 171 '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">' . 172 'Hey, that\'s pretty good!</a></div>' 173 ], 174 'Link with target blank' => [ 175 '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank">Hey, that\'s pretty good!</a>', 176 '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' . 177 ' rel="noreferrer">Hey, that\'s pretty good!</a></div>' 178 ], 179 'Link with Frank\'s casket inscription' => [ 180 '<a href="https://en.wikipedia.org/wiki/Franks_Casket">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻ' . 181 'ᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a>', 182 '<div class="text_to_html"><a href="https://en.wikipedia.org/wiki/Franks_Casket" target="_blank" ' . 183 'rel="noreferrer">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾ' . 184 'ᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a></div>' 185 ], 186 'No link' => [ 187 'Some very boring text written with the Latin script', 188 '<div class="text_to_html">Some very boring text written with the Latin script</div>' 189 ], 190 'No link with Thror\'s map runes' => [ 191 'ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ', 192 '<div class="text_to_html">ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹ' . 193 'ᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ</div>' 194 ] 195 ]; 196 } 197 198 /** 199 * Test ability to force cleaning of otherwise non-cleaned content. 200 * 201 * @dataProvider format_text_cleaning_testcases 202 * 203 * @param string $input Input text 204 * @param string $nocleaned Expected output of format_text() with noclean=true 205 * @param string $cleaned Expected output of format_text() with noclean=false 206 */ 207 public function test_format_text_cleaning($input, $nocleaned, $cleaned) { 208 global $CFG; 209 $this->resetAfterTest(); 210 211 $CFG->forceclean = false; 212 $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => false]); 213 $this->assertEquals($cleaned, $actual); 214 215 $CFG->forceclean = true; 216 $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => false]); 217 $this->assertEquals($cleaned, $actual); 218 219 $CFG->forceclean = false; 220 $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => true]); 221 $this->assertEquals($nocleaned, $actual); 222 223 $CFG->forceclean = true; 224 $actual = format_text($input, FORMAT_HTML, ['filter' => false, 'noclean' => true]); 225 $this->assertEquals($cleaned, $actual); 226 } 227 228 /** 229 * Data provider for the test_format_text_cleaning testcase 230 * 231 * @return array of testcases (string)testcasename => [(string)input, (string)nocleaned, (string)cleaned] 232 */ 233 public function format_text_cleaning_testcases() { 234 return [ 235 'JavaScript' => [ 236 'Hello <script type="text/javascript">alert("XSS");</script> world', 237 'Hello <script type="text/javascript">alert("XSS");</script> world', 238 'Hello world', 239 ], 240 'Inline frames' => [ 241 'Let us go phishing! <iframe src="https://1.2.3.4/google.com"></iframe>', 242 'Let us go phishing! <iframe src="https://1.2.3.4/google.com"></iframe>', 243 'Let us go phishing! ', 244 ], 245 'Malformed A tags' => [ 246 '<a onmouseover="alert(document.cookie)">xxs link</a>', 247 '<a onmouseover="alert(document.cookie)">xxs link</a>', 248 '<a>xxs link</a>', 249 ], 250 'Malformed IMG tags' => [ 251 '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">', 252 '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">', 253 '">', 254 ], 255 'On error alert' => [ 256 '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>', 257 '<IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img>', 258 '<img src="/" alt="" />', 259 ], 260 'IMG onerror and javascript alert encode' => [ 261 '<img src=x onerror="javascSS')">', 262 '<img src=x onerror="javascSS')">', 263 '<img src="x" alt="x" />', 264 ], 265 'DIV background-image' => [ 266 '<DIV STYLE="background-image: url(javascript:alert(\'XSS\'))">', 267 '<DIV STYLE="background-image: url(javascript:alert(\'XSS\'))">', 268 '<div></div>', 269 ], 270 ]; 271 } 272 273 public function test_with_context_as_options(): void { 274 $this->assertEquals( 275 '<p>Example</p>', 276 format_text('<p>Example</p>', FORMAT_HTML, \context_system::instance()) 277 ); 278 279 $messages = $this->getDebuggingMessages(); 280 $this->assertdebuggingcalledcount(1); 281 $this->assertStringContainsString( 282 'The options argument should not be a context object directly.', 283 $messages[0]->message 284 ); 285 } 286 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body