Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 * Test for content bank contenttype class. 19 * 20 * @package core_contentbank 21 * @category test 22 * @copyright 2020 Amaia Anabitarte <amaia@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace core_contentbank; 27 28 use stdClass; 29 use context_system; 30 use context_user; 31 use Exception; 32 use contenttype_testable\contenttype as contenttype; 33 /** 34 * Test for content bank contenttype class. 35 * 36 * @package core_contentbank 37 * @category test 38 * @copyright 2020 Amaia Anabitarte <amaia@moodle.com> 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 * @coversDefaultClass \core_contentbank\contenttype 41 * 42 */ 43 class contenttype_test extends \advanced_testcase { 44 45 /** @var int Identifier for the manager role. */ 46 protected $managerroleid; 47 48 /** @var stdClass Manager user. */ 49 protected $manager1; 50 51 /** @var stdClass Manager user. */ 52 protected $manager2; 53 54 /** @var stdClass User. */ 55 protected $user; 56 57 /** @var array List of contents created (every user has a key with contents created by her). */ 58 protected $contents = []; 59 60 /** @var contenttype The contenttype instance. */ 61 protected $contenttype; 62 63 /** 64 * Setup to ensure that fixtures are loaded. 65 */ 66 public static function setupBeforeClass(): void { 67 global $CFG; 68 69 require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php'); 70 require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php'); 71 } 72 73 /** 74 * Tests get_contenttype_name result. 75 * 76 * @covers ::get_contenttype_name 77 */ 78 public function test_get_contenttype_name() { 79 $this->resetAfterTest(); 80 81 $systemcontext = \context_system::instance(); 82 $testable = new contenttype($systemcontext); 83 84 $this->assertEquals('contenttype_testable', $testable->get_contenttype_name()); 85 } 86 87 /** 88 * Tests get_plugin_name result. 89 * 90 * @covers ::get_plugin_name 91 */ 92 public function test_get_plugin_name() { 93 $this->resetAfterTest(); 94 95 $systemcontext = \context_system::instance(); 96 $testable = new contenttype($systemcontext); 97 98 $this->assertEquals('testable', $testable->get_plugin_name()); 99 } 100 101 /** 102 * Tests get_icon result. 103 * 104 * @covers ::get_icon 105 */ 106 public function test_get_icon() { 107 $this->resetAfterTest(); 108 109 $systemcontext = \context_system::instance(); 110 $testable = new contenttype($systemcontext); 111 $record = new stdClass(); 112 $record->name = 'New content'; 113 $content = $testable->create_content($record); 114 $icon = $testable->get_icon($content); 115 $this->assertStringContainsString('archive', $icon); 116 } 117 118 /** 119 * Tests is_feature_supported behavior . 120 * 121 * @covers ::is_feature_supported 122 */ 123 public function test_is_feature_supported() { 124 $this->resetAfterTest(); 125 126 $systemcontext = \context_system::instance(); 127 $testable = new contenttype($systemcontext); 128 129 $this->assertTrue($testable->is_feature_supported(contenttype::CAN_TEST)); 130 $this->assertFalse($testable->is_feature_supported(contenttype::CAN_UPLOAD)); 131 } 132 133 /** 134 * Tests can_upload behavior with no implemented upload feature. 135 * 136 * @covers ::can_upload 137 */ 138 public function test_no_upload_feature_supported() { 139 $this->resetAfterTest(); 140 141 $systemcontext = \context_system::instance(); 142 $testable = new contenttype($systemcontext); 143 144 $this->setAdminUser(); 145 $this->assertFalse($testable->is_feature_supported(contenttype::CAN_UPLOAD)); 146 $this->assertFalse($testable->can_upload()); 147 } 148 149 /** 150 * Test create_content() with empty data. 151 * 152 * @covers ::create_content 153 */ 154 public function test_create_empty_content() { 155 $this->resetAfterTest(); 156 157 // Create empty content. 158 $record = new stdClass(); 159 160 $contenttype = new contenttype(context_system::instance()); 161 $content = $contenttype->create_content($record); 162 163 $this->assertEquals('contenttype_testable', $content->get_content_type()); 164 $this->assertInstanceOf('\\contenttype_testable\\content', $content); 165 } 166 167 /** 168 * Tests for behaviour of create_content() with data. 169 * 170 * @covers ::create_content 171 */ 172 public function test_create_content() { 173 $this->resetAfterTest(); 174 175 // Create content. 176 $record = new stdClass(); 177 $record->name = 'Test content'; 178 $record->configdata = ''; 179 $record->contenttype = ''; 180 181 $contenttype = new contenttype(context_system::instance()); 182 $content = $contenttype->create_content($record); 183 184 $this->assertEquals('contenttype_testable', $content->get_content_type()); 185 $this->assertInstanceOf('\\contenttype_testable\\content', $content); 186 } 187 188 /** 189 * Tests for behaviour of upload_content() with a file and a record. 190 * 191 * @dataProvider upload_content_provider 192 * @param bool $userecord if a predefined record has to be used. 193 * 194 * @covers ::upload_content 195 */ 196 public function test_upload_content(bool $userecord): void { 197 global $USER; 198 199 $this->resetAfterTest(); 200 $this->setAdminUser(); 201 202 $dummy = [ 203 'contextid' => context_user::instance($USER->id)->id, 204 'component' => 'user', 205 'filearea' => 'draft', 206 'itemid' => 1, 207 'filepath' => '/', 208 'filename' => 'file.h5p', 209 'userid' => $USER->id, 210 ]; 211 $fs = get_file_storage(); 212 $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content'); 213 214 // Create content. 215 if ($userecord) { 216 $record = new stdClass(); 217 $record->name = 'Test content'; 218 $record->configdata = ''; 219 $record->contenttype = ''; 220 $checkname = $record->name; 221 } else { 222 $record = null; 223 $checkname = $dummyfile->get_filename(); 224 } 225 226 $contenttype = new contenttype(context_system::instance()); 227 $content = $contenttype->upload_content($dummyfile, $record); 228 229 $this->assertEquals('contenttype_testable', $content->get_content_type()); 230 $this->assertEquals($checkname, $content->get_name()); 231 $this->assertInstanceOf('\\contenttype_testable\\content', $content); 232 233 $file = $content->get_file(); 234 $this->assertEquals($dummyfile->get_filename(), $file->get_filename()); 235 $this->assertEquals($dummyfile->get_userid(), $file->get_userid()); 236 $this->assertEquals($dummyfile->get_mimetype(), $file->get_mimetype()); 237 $this->assertEquals($dummyfile->get_contenthash(), $file->get_contenthash()); 238 $this->assertEquals('contentbank', $file->get_component()); 239 $this->assertEquals('public', $file->get_filearea()); 240 $this->assertEquals('/', $file->get_filepath()); 241 } 242 243 /** 244 * Data provider for test_rename_content. 245 * 246 * @return array 247 */ 248 public function upload_content_provider() { 249 return [ 250 'With record' => [true], 251 'Without record' => [false], 252 ]; 253 } 254 255 /** 256 * Tests for behaviour of upload_content() with a file wrong file. 257 * 258 * @covers ::upload_content 259 */ 260 public function test_upload_content_exception(): void { 261 global $USER, $DB; 262 263 $this->resetAfterTest(); 264 $this->setAdminUser(); 265 266 // The testing contenttype thows exception if filename is "error.*". 267 $dummy = [ 268 'contextid' => context_user::instance($USER->id)->id, 269 'component' => 'user', 270 'filearea' => 'draft', 271 'itemid' => 1, 272 'filepath' => '/', 273 'filename' => 'error.txt', 274 'userid' => $USER->id, 275 ]; 276 $fs = get_file_storage(); 277 $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content'); 278 279 $contenttype = new contenttype(context_system::instance()); 280 $cbcontents = $DB->count_records('contentbank_content'); 281 282 // We need to capture the exception to check no content is created. 283 try { 284 $content = $contenttype->upload_content($dummyfile); 285 $this->assertTrue(false); 286 } catch (Exception $e) { 287 $this->assertTrue(true); 288 } 289 $this->assertEquals($cbcontents, $DB->count_records('contentbank_content')); 290 $this->assertEquals(1, $DB->count_records('files', ['contenthash' => $dummyfile->get_contenthash()])); 291 } 292 293 /** 294 * Tests for behaviour of replace_content() using a dummy file. 295 * 296 * @covers ::replace_content 297 */ 298 public function test_replace_content(): void { 299 global $USER; 300 301 $this->resetAfterTest(); 302 $this->setAdminUser(); 303 $context = context_system::instance(); 304 305 // Add some content to the content bank. 306 $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); 307 $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context); 308 $content = reset($contents); 309 310 $dummy = [ 311 'contextid' => context_user::instance($USER->id)->id, 312 'component' => 'user', 313 'filearea' => 'draft', 314 'itemid' => 1, 315 'filepath' => '/', 316 'filename' => 'file.h5p', 317 'userid' => $USER->id, 318 ]; 319 $fs = get_file_storage(); 320 $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content'); 321 322 $contenttype = new contenttype(context_system::instance()); 323 $content = $contenttype->replace_content($dummyfile, $content); 324 325 $this->assertEquals('contenttype_testable', $content->get_content_type()); 326 $this->assertInstanceOf('\\contenttype_testable\\content', $content); 327 328 $file = $content->get_file(); 329 $this->assertEquals($dummyfile->get_userid(), $file->get_userid()); 330 $this->assertEquals($dummyfile->get_contenthash(), $file->get_contenthash()); 331 $this->assertEquals('contentbank', $file->get_component()); 332 $this->assertEquals('public', $file->get_filearea()); 333 $this->assertEquals('/', $file->get_filepath()); 334 } 335 336 /** 337 * Tests for behaviour of replace_content() using an error file. 338 * 339 * @covers ::replace_content 340 */ 341 public function test_replace_content_exception(): void { 342 global $USER; 343 344 $this->resetAfterTest(); 345 $this->setAdminUser(); 346 $context = context_system::instance(); 347 348 // Add some content to the content bank. 349 $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); 350 $contents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $context); 351 $content = reset($contents); 352 353 $dummy = [ 354 'contextid' => context_user::instance($USER->id)->id, 355 'component' => 'user', 356 'filearea' => 'draft', 357 'itemid' => 1, 358 'filepath' => '/', 359 'filename' => 'error.txt', 360 'userid' => $USER->id, 361 ]; 362 $fs = get_file_storage(); 363 $dummyfile = $fs->create_file_from_string($dummy, 'Dummy content'); 364 365 $contenttype = new contenttype(context_system::instance()); 366 367 $this->expectException(Exception::class); 368 $content = $contenttype->replace_content($dummyfile, $content); 369 } 370 371 /** 372 * Test the behaviour of can_delete(). 373 */ 374 public function test_can_delete() { 375 global $DB; 376 377 $this->resetAfterTest(); 378 $this->contenttype_setup_scenario_data(); 379 380 $managercontent = array_shift($this->contents[$this->manager1->id]); 381 $usercontent = array_shift($this->contents[$this->user->id]); 382 383 // Check the content has been created as expected. 384 $records = $DB->count_records('contentbank_content'); 385 $this->assertEquals(4, $records); 386 387 // Check user can only delete records created by her. 388 $this->setUser($this->user); 389 $this->assertFalse($this->contenttype->can_delete($managercontent)); 390 $this->assertTrue($this->contenttype->can_delete($usercontent)); 391 392 // Check manager can delete records all the records created. 393 $this->setUser($this->manager1); 394 $this->assertTrue($this->contenttype->can_delete($managercontent)); 395 $this->assertTrue($this->contenttype->can_delete($usercontent)); 396 397 // Unassign capability to manager role and check not can only delete their own records. 398 unassign_capability('moodle/contentbank:deleteanycontent', $this->managerroleid); 399 $this->assertTrue($this->contenttype->can_delete($managercontent)); 400 $this->assertFalse($this->contenttype->can_delete($usercontent)); 401 $this->setUser($this->manager2); 402 $this->assertFalse($this->contenttype->can_delete($managercontent)); 403 $this->assertFalse($this->contenttype->can_delete($usercontent)); 404 } 405 406 /** 407 * Test the behaviour of delete_content(). 408 */ 409 public function test_delete_content() { 410 global $DB; 411 412 $this->resetAfterTest(); 413 $this->contenttype_setup_scenario_data(); 414 415 // Check the content has been created as expected. 416 $this->assertEquals(4, $DB->count_records('contentbank_content')); 417 418 // Check the content is deleted as expected. 419 $this->setUser($this->manager1); 420 $content = array_shift($this->contents[$this->manager1->id]); 421 $deleted = $this->contenttype->delete_content($content); 422 $this->assertTrue($deleted); 423 $this->assertEquals(3, $DB->count_records('contentbank_content')); 424 } 425 426 /** 427 * Helper function to setup 3 users (manager1, manager2 and user) and 4 contents (3 created by manager1 and 1 by user). 428 */ 429 protected function contenttype_setup_scenario_data(string $contenttype = 'contenttype_testable'): void { 430 global $DB; 431 $systemcontext = context_system::instance(); 432 433 // Create users. 434 $this->manager1 = $this->getDataGenerator()->create_user(); 435 $this->manager2 = $this->getDataGenerator()->create_user(); 436 $this->managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager')); 437 $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager1->id); 438 $this->getDataGenerator()->role_assign($this->managerroleid, $this->manager2->id); 439 $editingteacherrolerid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']); 440 $this->user = $this->getDataGenerator()->create_user(); 441 $this->getDataGenerator()->role_assign($editingteacherrolerid, $this->user->id); 442 443 // Add some content to the content bank. 444 $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); 445 $this->contents[$this->manager1->id] = $generator->generate_contentbank_data($contenttype, 3, $this->manager1->id); 446 $this->contents[$this->user->id] = $generator->generate_contentbank_data($contenttype, 1, $this->user->id); 447 448 $contenttypeclass = "\\$contenttype\\contenttype"; 449 $this->contenttype = new $contenttypeclass($systemcontext); 450 } 451 452 /** 453 * Data provider for test_rename_content. 454 * 455 * @return array 456 */ 457 public function rename_content_provider() { 458 return [ 459 'Standard name' => ['New name', 'New name', true], 460 'Name with digits' => ['Today is 17/04/2017', 'Today is 17/04/2017', true], 461 'Name with symbols' => ['Follow us: @moodle', 'Follow us: @moodle', true], 462 'Name with tags' => ['This is <b>bold</b>', 'This is bold', true], 463 'Long name' => [str_repeat('a', 100), str_repeat('a', 100), true], 464 'Too long name' => [str_repeat('a', 300), str_repeat('a', 255), true], 465 'Empty name' => ['', 'Test content ', false], 466 'Blanks only' => [' ', 'Test content ', false], 467 'Zero name' => ['0', '0', true], 468 ]; 469 } 470 471 /** 472 * Test the behaviour of rename_content(). 473 * 474 * @dataProvider rename_content_provider 475 * @param string $newname The name to set 476 * @param string $expected The name result 477 * @param bool $result The bolean result expected when renaming 478 * 479 * @covers ::rename_content 480 */ 481 public function test_rename_content(string $newname, string $expected, bool $result) { 482 global $DB; 483 484 $this->resetAfterTest(); 485 486 // Create course and teacher user. 487 $course = $this->getDataGenerator()->create_course(); 488 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 489 $coursecontext = \context_course::instance($course->id); 490 $contenttype = new contenttype($coursecontext); 491 492 // Add some content to the content bank as teacher. 493 $this->setUser($teacher); 494 $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); 495 $contents = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id); 496 $content = array_shift($contents); 497 498 $oldname = $content->get_name(); 499 500 // Check the content is renamed as expected by a user with permission. 501 $renamed = $contenttype->rename_content($content, $newname); 502 $this->assertEquals($result, $renamed); 503 $record = $DB->get_record('contentbank_content', ['id' => $content->get_id()]); 504 $this->assertEquals($expected, $record->name); 505 } 506 507 /** 508 * Test the behaviour of move_content(). 509 */ 510 public function test_move_content() { 511 global $DB; 512 513 $this->resetAfterTest(); 514 $systemcontext = context_system::instance(); 515 $course = $this->getDataGenerator()->create_course(); 516 $coursecontext = \context_course::instance($course->id); 517 518 // Add some content to the content bank. 519 $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); 520 $systemcontents = $generator->generate_contentbank_data('contenttype_testable', 3, 0, $systemcontext); 521 $generator->generate_contentbank_data('contenttype_testable', 3, 0, $coursecontext); 522 $systemcontent = reset($systemcontents); 523 524 // Check the content has been created as expected. 525 $this->assertEquals(6, $DB->count_records('contentbank_content')); 526 $this->assertEquals(3, $DB->count_records('contentbank_content', ['contextid' => $systemcontext->id])); 527 $this->assertEquals(3, $DB->count_records('contentbank_content', ['contextid' => $coursecontext->id])); 528 529 // Check the content files has been created as expected. 530 $this->assertEquals(12, $DB->count_records('files', ['component' => 'contentbank'])); 531 $this->assertEquals(6, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $systemcontext->id])); 532 $this->assertEquals(6, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $coursecontext->id])); 533 534 // Check the content is moved as expected. 535 $contenttype = new contenttype($systemcontext); 536 $this->assertTrue($contenttype->move_content($systemcontent, $coursecontext)); 537 $this->assertEquals(6, $DB->count_records('contentbank_content')); 538 $this->assertEquals(2, $DB->count_records('contentbank_content', ['contextid' => $systemcontext->id])); 539 $this->assertEquals(4, $DB->count_records('contentbank_content', ['contextid' => $coursecontext->id])); 540 541 // Check the content files were moved as expected. 542 $this->assertEquals(12, $DB->count_records('files', ['component' => 'contentbank'])); 543 $this->assertEquals(4, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $systemcontext->id])); 544 $this->assertEquals(8, $DB->count_records('files', ['component' => 'contentbank', 'contextid' => $coursecontext->id])); 545 } 546 547 /** 548 * Test the behaviour of can_manage(). 549 * 550 * @covers ::can_manage 551 */ 552 public function test_can_manage() { 553 global $DB, $USER; 554 555 $this->resetAfterTest(); 556 $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); 557 558 // Create course and teacher user. 559 $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']); 560 $course = $this->getDataGenerator()->create_course(); 561 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); 562 $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager'); 563 $coursecontext = \context_course::instance($course->id); 564 565 $contenttype = new contenttype($coursecontext); 566 567 // Add some content to the content bank as admin. 568 $this->setAdminUser(); 569 $contentsbyadmin = $generator->generate_contentbank_data('contenttype_testable', 1, $USER->id, $coursecontext); 570 $contentbyadmin = array_shift($contentsbyadmin); 571 572 // Add some content to the content bank as teacher. 573 $contentsbyteacher = $generator->generate_contentbank_data('contenttype_testable', 1, $teacher->id, $coursecontext); 574 $contentbyteacher = array_shift($contentsbyteacher); 575 576 // Check the content has been created as expected. 577 $records = $DB->count_records('contentbank_content'); 578 $this->assertEquals(2, $records); 579 580 // Check manager can manage by default all the contents created. 581 $this->setUser($manager); 582 $this->assertTrue($contenttype->can_manage($contentbyteacher)); 583 $this->assertTrue($contenttype->can_manage($contentbyadmin)); 584 585 // Check teacher can only edit their own content. 586 $this->setUser($teacher); 587 $this->assertTrue($contenttype->can_manage($contentbyteacher)); 588 $this->assertFalse($contenttype->can_manage($contentbyadmin)); 589 590 // Unassign capability to teacher role and check they not can not edit any content. 591 unassign_capability('moodle/contentbank:manageowncontent', $teacherroleid); 592 $this->assertFalse($contenttype->can_manage($contentbyteacher)); 593 $this->assertFalse($contenttype->can_manage($contentbyadmin)); 594 } 595 596 /** 597 * Test the behaviour of can_download(). 598 * 599 * @covers ::can_download 600 */ 601 public function test_can_download() { 602 global $DB; 603 604 $this->resetAfterTest(); 605 $this->contenttype_setup_scenario_data('contenttype_h5p'); 606 607 $managercontent = array_shift($this->contents[$this->manager1->id]); 608 $usercontent = array_shift($this->contents[$this->user->id]); 609 610 // Check the content has been created as expected. 611 $records = $DB->count_records('contentbank_content'); 612 $this->assertEquals(4, $records); 613 614 // Check user can download content created by anybody. 615 $this->setUser($this->user); 616 $this->assertTrue($this->contenttype->can_download($usercontent)); 617 $this->assertTrue($this->contenttype->can_download($managercontent)); 618 619 // Check manager can download all the content too. 620 $this->setUser($this->manager1); 621 $this->assertTrue($this->contenttype->can_download($managercontent)); 622 $this->assertTrue($this->contenttype->can_download($usercontent)); 623 624 // Unassign capability to manager role and check she cannot download content anymore. 625 unassign_capability('moodle/contentbank:downloadcontent', $this->managerroleid); 626 $this->assertFalse($this->contenttype->can_download($managercontent)); 627 $this->assertFalse($this->contenttype->can_download($usercontent)); 628 } 629 630 /** 631 * Tests get_download_url result. 632 * 633 * @covers ::get_download_url 634 */ 635 public function test_get_download_url() { 636 global $CFG; 637 638 $this->resetAfterTest(); 639 $this->setAdminUser(); 640 $systemcontext = context_system::instance(); 641 642 // Add some content to the content bank. 643 $filename = 'filltheblanks.h5p'; 644 $filepath = $CFG->dirroot . '/h5p/tests/fixtures/' . $filename; 645 $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank'); 646 $contents = $generator->generate_contentbank_data('contenttype_testable', 1, 0, $systemcontext, true, $filepath); 647 $content = array_shift($contents); 648 649 // Check the URL is returned OK for a content with file. 650 $contenttype = new contenttype($systemcontext); 651 $url = $contenttype->get_download_url($content); 652 $this->assertNotEmpty($url); 653 $this->assertStringContainsString($filename, $url); 654 655 // Check the URL is empty when the content hasn't any file. 656 $record = new stdClass(); 657 $content = $contenttype->create_content($record); 658 $url = $contenttype->get_download_url($content); 659 $this->assertEmpty($url); 660 } 661 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body