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