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_external; 18 19 /** 20 * Unit tests for core_external\util. 21 * 22 * @package core_external 23 * @category test 24 * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk> 25 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 26 * @covers \core_external\util 27 */ 28 class util_test extends \advanced_testcase { 29 /** @var \moodle_database The database connection */ 30 protected $db; 31 32 /** 33 * Store the global DB for restore between tests. 34 */ 35 public function setUp(): void { 36 global $DB; 37 38 $this->db = $DB; 39 external_settings::reset(); 40 } 41 42 /** 43 * A helper to include the legacy external functions. 44 */ 45 protected function include_legacy_functions(): void { 46 global $CFG; 47 48 $this->assertTrue( 49 $this->isInIsolation(), 50 'Inclusion of the legacy test functions requires the test to be run in isolation.', 51 ); 52 53 // Note: This is retained for testing of the old functions. 54 require_once("{$CFG->libdir}/externallib.php"); 55 } 56 57 /** 58 * Reset the global DB between tests. 59 */ 60 public function tearDown(): void { 61 global $DB; 62 if ($this->db !== null) { 63 $DB = $this->db; 64 } 65 external_settings::reset(); 66 } 67 68 /** 69 * Validate courses, but still return courses even if they fail validation. 70 * 71 * @covers \core_external\util::validate_courses 72 */ 73 public function test_validate_courses_keepfails(): void { 74 $this->resetAfterTest(true); 75 76 $c1 = $this->getDataGenerator()->create_course(); 77 $c2 = $this->getDataGenerator()->create_course(); 78 $c3 = $this->getDataGenerator()->create_course(); 79 $u1 = $this->getDataGenerator()->create_user(); 80 $this->getDataGenerator()->enrol_user($u1->id, $c1->id); 81 $courseids = [$c1->id, $c2->id, $c3->id]; 82 83 $this->setUser($u1); 84 [$courses, $warnings] = util::validate_courses($courseids, [], false, true); 85 $this->assertCount(2, $warnings); 86 $this->assertEquals($c2->id, $warnings[0]['itemid']); 87 $this->assertEquals($c3->id, $warnings[1]['itemid']); 88 $this->assertCount(3, $courses); 89 $this->assertTrue($courses[$c1->id]->contextvalidated); 90 $this->assertFalse($courses[$c2->id]->contextvalidated); 91 $this->assertFalse($courses[$c3->id]->contextvalidated); 92 } 93 94 /** 95 * Validate courses can re-use an array of prefetched courses. 96 * 97 * @covers \core_external\util::validate_courses 98 */ 99 public function test_validate_courses_prefetch(): void { 100 $this->resetAfterTest(true); 101 102 $c1 = $this->getDataGenerator()->create_course(); 103 $c2 = $this->getDataGenerator()->create_course(); 104 $c3 = $this->getDataGenerator()->create_course(); 105 $c4 = $this->getDataGenerator()->create_course(); 106 $u1 = $this->getDataGenerator()->create_user(); 107 $this->getDataGenerator()->enrol_user($u1->id, $c1->id); 108 $this->getDataGenerator()->enrol_user($u1->id, $c2->id); 109 110 $courseids = [$c1->id, $c2->id, $c3->id]; 111 $courses = [$c2->id => $c2, $c3->id => $c3, $c4->id => $c4]; 112 113 $this->setUser($u1); 114 [$courses, $warnings] = util::validate_courses($courseids, $courses); 115 $this->assertCount(2, $courses); 116 $this->assertCount(1, $warnings); 117 $this->assertArrayHasKey($c1->id, $courses); 118 $this->assertSame($c2, $courses[$c2->id]); 119 $this->assertArrayNotHasKey($c3->id, $courses); 120 // The extra course passed is not returned. 121 $this->assertArrayNotHasKey($c4->id, $courses); 122 } 123 124 /** 125 * Test the Validate courses standard functionality. 126 * 127 * @covers \core_external\util::validate_courses 128 */ 129 public function test_validate_courses(): void { 130 $this->resetAfterTest(true); 131 132 $c1 = $this->getDataGenerator()->create_course(); 133 $c2 = $this->getDataGenerator()->create_course(); 134 $c3 = $this->getDataGenerator()->create_course(); 135 $u1 = $this->getDataGenerator()->create_user(); 136 $this->getDataGenerator()->enrol_user($u1->id, $c1->id); 137 $courseids = [$c1->id, $c2->id, $c3->id]; 138 139 $this->setAdminUser(); 140 [$courses, $warnings] = util::validate_courses($courseids); 141 $this->assertEmpty($warnings); 142 $this->assertCount(3, $courses); 143 $this->assertArrayHasKey($c1->id, $courses); 144 $this->assertArrayHasKey($c2->id, $courses); 145 $this->assertArrayHasKey($c3->id, $courses); 146 $this->assertEquals($c1->id, $courses[$c1->id]->id); 147 $this->assertEquals($c2->id, $courses[$c2->id]->id); 148 $this->assertEquals($c3->id, $courses[$c3->id]->id); 149 150 $this->setUser($u1); 151 [$courses, $warnings] = util::validate_courses($courseids); 152 $this->assertCount(2, $warnings); 153 $this->assertEquals($c2->id, $warnings[0]['itemid']); 154 $this->assertEquals($c3->id, $warnings[1]['itemid']); 155 $this->assertCount(1, $courses); 156 $this->assertArrayHasKey($c1->id, $courses); 157 $this->assertArrayNotHasKey($c2->id, $courses); 158 $this->assertArrayNotHasKey($c3->id, $courses); 159 $this->assertEquals($c1->id, $courses[$c1->id]->id); 160 } 161 162 /** 163 * Text util::get_area_files 164 * 165 * @covers \core_external\util::get_area_files 166 */ 167 public function test_get_area_files(): void { 168 global $CFG, $DB; 169 170 $this->db = $DB; 171 $DB = $this->getMockBuilder('moodle_database')->getMock(); 172 173 $content = base64_encode("Let us create a nice simple file."); 174 $timemodified = 102030405; 175 $itemid = 42; 176 $filesize = strlen($content); 177 178 $DB->method('get_records_sql')->willReturn([ 179 (object) [ 180 'filename' => 'example.txt', 181 'filepath' => '/', 182 'mimetype' => 'text/plain', 183 'filesize' => $filesize, 184 'timemodified' => $timemodified, 185 'itemid' => $itemid, 186 'pathnamehash' => sha1('/example.txt'), 187 ], 188 ]); 189 190 $component = 'mod_foo'; 191 $filearea = 'area'; 192 $context = 12345; 193 194 $expectedfiles = [[ 195 'filename' => 'example.txt', 196 'filepath' => '/', 197 'fileurl' => "{$CFG->wwwroot}/webservice/pluginfile.php/{$context}/{$component}/{$filearea}/{$itemid}/example.txt", 198 'timemodified' => $timemodified, 199 'filesize' => $filesize, 200 'mimetype' => 'text/plain', 201 'isexternalfile' => false, 202 ], 203 ]; 204 // Get all the files for the area. 205 $files = util::get_area_files($context, $component, $filearea, false); 206 $this->assertEquals($expectedfiles, $files); 207 208 $DB->method('get_in_or_equal')->willReturn([ 209 '= :mock1', 210 ['mock1' => $itemid], 211 ]); 212 213 // Get just the file indicated by $itemid. 214 $files = util::get_area_files($context, $component, $filearea, $itemid); 215 $this->assertEquals($expectedfiles, $files); 216 } 217 218 /** 219 * Test default time for user created tokens. 220 * 221 * @covers \core_external\util::generate_token_for_current_user 222 */ 223 public function test_user_created_tokens_duration(): void { 224 global $CFG, $DB; 225 $this->resetAfterTest(true); 226 227 $CFG->enablewebservices = 1; 228 $CFG->enablemobilewebservice = 1; 229 $user1 = $this->getDataGenerator()->create_user(); 230 $user2 = $this->getDataGenerator()->create_user(); 231 $service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]); 232 233 $this->setUser($user1); 234 $timenow = time(); 235 $token = util::generate_token_for_current_user($service); 236 $this->assertGreaterThanOrEqual($timenow + $CFG->tokenduration, $token->validuntil); 237 238 // Change token default time. 239 $this->setUser($user2); 240 set_config('tokenduration', DAYSECS); 241 $token = util::generate_token_for_current_user($service); 242 $timenow = time(); 243 $this->assertLessThanOrEqual($timenow + DAYSECS, $token->validuntil); 244 } 245 246 247 /** 248 * Test the format_text function. 249 * 250 * @covers \core_external\util::format_text 251 * @runInSeparateProcess 252 */ 253 public function test_format_text(): void { 254 $this->include_legacy_functions(); 255 $settings = external_settings::get_instance(); 256 257 $settings->set_raw(true); 258 $settings->set_filter(false); 259 $context = \context_system::instance(); 260 261 $test = '$$ \pi $$'; 262 $testformat = FORMAT_MARKDOWN; 263 $correct = [$test, $testformat]; 264 $this->assertSame($correct, util::format_text($test, $testformat, $context, 'core', '', 0)); 265 266 // Function external_format_text should work with context id or context instance. 267 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct); 268 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct); 269 270 $settings->set_raw(false); 271 $settings->set_filter(true); 272 273 $test = '$$ \pi $$'; 274 $testformat = FORMAT_MARKDOWN; 275 $correct = ['<span class="filter_mathjaxloader_equation"><p><span class="nolink">$$ \pi $$</span></p> 276 </span>', FORMAT_HTML, 277 ]; 278 $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0), $correct); 279 280 // Function external_format_text should work with context id or context instance. 281 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct); 282 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct); 283 284 // Filters can be opted out from by the developer. 285 $test = '$$ \pi $$'; 286 $testformat = FORMAT_MARKDOWN; 287 $correct = ['<p>$$ \pi $$</p> 288 ', FORMAT_HTML, 289 ]; 290 $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct); 291 292 // Function external_format_text should work with context id or context instance. 293 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, ['filter' => false]), $correct); 294 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct); 295 296 $test = '<p><a id="test"></a><a href="#test">Text</a></p>'; 297 $testformat = FORMAT_HTML; 298 $correct = [$test, FORMAT_HTML]; 299 $options = ['allowid' => true]; 300 $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 301 // Function external_format_text should work with context id or context instance. 302 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); 303 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 304 305 $test = '<p><a id="test"></a><a href="#test">Text</a></p>'; 306 $testformat = FORMAT_HTML; 307 $correct = ['<p><a></a><a href="#test">Text</a></p>', FORMAT_HTML]; 308 $options = new \stdClass(); 309 $options->allowid = false; 310 $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 311 312 // Function external_format_text should work with context id or context instance. 313 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); 314 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 315 316 $test = '<p><a id="test"></a><a href="#test">Text</a></p>' . "\n" . 'Newline'; 317 $testformat = FORMAT_MOODLE; 318 $correct = ['<p><a id="test"></a><a href="#test">Text</a></p> Newline', FORMAT_HTML]; 319 $options = new \stdClass(); 320 $options->newlines = false; 321 $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 322 323 // Function external_format_text should work with context id or context instance. 324 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); 325 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 326 327 $test = '<p><a id="test"></a><a href="#test">Text</a></p>'; 328 $testformat = FORMAT_MOODLE; 329 $correct = ['<div class="text_to_html">' . $test . '</div>', FORMAT_HTML]; 330 $options = new \stdClass(); 331 $options->para = true; 332 $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 333 334 // Function external_format_text should work with context id or context instance. 335 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); 336 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 337 338 $test = '<p><a id="test"></a><a href="#test">Text</a></p>'; 339 $testformat = FORMAT_MOODLE; 340 $correct = [$test, FORMAT_HTML]; 341 $options = new \stdClass(); 342 $options->context = $context; 343 $this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 344 345 // Function external_format_text should work with context id or context instance. 346 $this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct); 347 $this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct); 348 } 349 /** 350 * Teset the format_string function. 351 * 352 * @covers \core_external\util::format_string 353 * @runInSeparateProcess 354 */ 355 public function test_external_format_string(): void { 356 $this->resetAfterTest(); 357 $this->include_legacy_functions(); 358 $settings = external_settings::get_instance(); 359 360 // Enable multilang filter to on content and heading. 361 filter_set_global_state('multilang', TEXTFILTER_ON); 362 filter_set_applies_to_strings('multilang', 1); 363 $filtermanager = \filter_manager::instance(); 364 $filtermanager->reset_caches(); 365 366 $settings->set_raw(true); 367 $settings->set_filter(true); 368 $context = \context_system::instance(); 369 370 $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> '; 371 $test .= '<script>hi</script> <h3>there</h3>!'; 372 $correct = $test; 373 $this->assertSame($correct, util::format_string($test, $context)); 374 375 // Function external_format_string should work with context id or context instance. 376 $this->assertSame($correct, external_format_string($test, $context)); 377 $this->assertSame($correct, external_format_string($test, $context->id)); 378 379 $settings->set_raw(false); 380 $settings->set_filter(false); 381 382 $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> '; 383 $test .= '<script>hi</script> <h3>there</h3>?'; 384 $correct = 'ENFR hi there?'; 385 $this->assertSame($correct, util::format_string($test, $context)); 386 387 // Function external_format_string should work with context id or context instance. 388 $this->assertSame($correct, external_format_string($test, $context)); 389 $this->assertSame($correct, external_format_string($test, $context->id)); 390 391 $settings->set_filter(true); 392 393 $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> '; 394 $test .= '<script>hi</script> <h3>there</h3>@'; 395 $correct = 'EN hi there@'; 396 $this->assertSame($correct, util::format_string($test, $context)); 397 398 // Function external_format_string should work with context id or context instance. 399 $this->assertSame($correct, external_format_string($test, $context)); 400 $this->assertSame($correct, external_format_string($test, $context->id)); 401 402 // Filters can be opted out. 403 $test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> '; 404 $test .= '<script>hi</script> <h3>there</h3>%'; 405 $correct = 'ENFR hi there%'; 406 $this->assertSame($correct, util::format_string($test, $context, false, ['filter' => false])); 407 408 // Function external_format_string should work with context id or context instance. 409 $this->assertSame($correct, external_format_string($test, $context->id, false, ['filter' => false])); 410 $this->assertSame($correct, external_format_string($test, $context, false, ['filter' => false])); 411 412 $this->assertSame("& < > \" '", format_string("& < > \" '", true, ['escape' => false])); 413 } 414 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body