Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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 mod_forum; 18 19 use externallib_advanced_testcase; 20 use mod_forum_external; 21 22 defined('MOODLE_INTERNAL') || die(); 23 24 global $CFG; 25 26 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 27 require_once($CFG->dirroot . '/mod/forum/lib.php'); 28 29 /** 30 * The module forums external functions unit tests 31 * 32 * @package mod_forum 33 * @category external 34 * @copyright 2012 Mark Nelson <markn@moodle.com> 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class externallib_test extends externallib_advanced_testcase { 38 39 /** 40 * Tests set up 41 */ 42 protected function setUp(): void { 43 global $CFG; 44 45 // We must clear the subscription caches. This has to be done both before each test, and after in case of other 46 // tests using these functions. 47 \mod_forum\subscriptions::reset_forum_cache(); 48 49 require_once($CFG->dirroot . '/mod/forum/externallib.php'); 50 } 51 52 public function tearDown(): void { 53 // We must clear the subscription caches. This has to be done both before each test, and after in case of other 54 // tests using these functions. 55 \mod_forum\subscriptions::reset_forum_cache(); 56 } 57 58 /** 59 * Get the expected attachment. 60 * 61 * @param stored_file $file 62 * @param array $values 63 * @param moodle_url|null $url 64 * @return array 65 */ 66 protected function get_expected_attachment(\stored_file $file, array $values = [], ?\moodle_url $url = null): array { 67 if (!$url) { 68 $url = \moodle_url::make_pluginfile_url( 69 $file->get_contextid(), 70 $file->get_component(), 71 $file->get_filearea(), 72 $file->get_itemid(), 73 $file->get_filepath(), 74 $file->get_filename() 75 ); 76 $url->param('forcedownload', 1); 77 } 78 79 return array_merge( 80 [ 81 'contextid' => $file->get_contextid(), 82 'component' => $file->get_component(), 83 'filearea' => $file->get_filearea(), 84 'itemid' => $file->get_itemid(), 85 'filepath' => $file->get_filepath(), 86 'filename' => $file->get_filename(), 87 'isdir' => $file->is_directory(), 88 'isimage' => $file->is_valid_image(), 89 'timemodified' => $file->get_timemodified(), 90 'timecreated' => $file->get_timecreated(), 91 'filesize' => $file->get_filesize(), 92 'author' => $file->get_author(), 93 'license' => $file->get_license(), 94 'filenameshort' => $file->get_filename(), 95 'filesizeformatted' => display_size((int) $file->get_filesize()), 96 'icon' => $file->is_directory() ? file_folder_icon(128) : file_file_icon($file, 128), 97 'timecreatedformatted' => userdate($file->get_timecreated()), 98 'timemodifiedformatted' => userdate($file->get_timemodified()), 99 'url' => $url->out(), 100 ], $values); 101 } 102 103 /** 104 * Test get forums 105 */ 106 public function test_mod_forum_get_forums_by_courses() { 107 global $USER, $CFG, $DB; 108 109 $this->resetAfterTest(true); 110 111 // Create a user. 112 $user = self::getDataGenerator()->create_user(array('trackforums' => 1)); 113 114 // Set to the user. 115 self::setUser($user); 116 117 // Create courses to add the modules. 118 $course1 = self::getDataGenerator()->create_course(); 119 $course2 = self::getDataGenerator()->create_course(); 120 121 // First forum. 122 $record = new \stdClass(); 123 $record->introformat = FORMAT_HTML; 124 $record->course = $course1->id; 125 $record->trackingtype = FORUM_TRACKING_FORCED; 126 $forum1 = self::getDataGenerator()->create_module('forum', $record); 127 128 // Second forum. 129 $record = new \stdClass(); 130 $record->introformat = FORMAT_HTML; 131 $record->course = $course2->id; 132 $record->trackingtype = FORUM_TRACKING_OFF; 133 $forum2 = self::getDataGenerator()->create_module('forum', $record); 134 $forum2->introfiles = []; 135 136 // Add discussions to the forums. 137 $record = new \stdClass(); 138 $record->course = $course1->id; 139 $record->userid = $user->id; 140 $record->forum = $forum1->id; 141 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 142 // Expect one discussion. 143 $forum1->numdiscussions = 1; 144 $forum1->cancreatediscussions = true; 145 $forum1->istracked = true; 146 $forum1->unreadpostscount = 0; 147 $forum1->introfiles = []; 148 149 $record = new \stdClass(); 150 $record->course = $course2->id; 151 $record->userid = $user->id; 152 $record->forum = $forum2->id; 153 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 154 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 155 // Expect two discussions. 156 $forum2->numdiscussions = 2; 157 // Default limited role, no create discussion capability enabled. 158 $forum2->cancreatediscussions = false; 159 $forum2->istracked = false; 160 161 // Check the forum was correctly created. 162 $this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2', 163 array('forum1' => $forum1->id, 'forum2' => $forum2->id))); 164 165 // Enrol the user in two courses. 166 // DataGenerator->enrol_user automatically sets a role for the user with the permission mod/form:viewdiscussion. 167 $this->getDataGenerator()->enrol_user($user->id, $course1->id, null, 'manual'); 168 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later. 169 $enrol = enrol_get_plugin('manual'); 170 $enrolinstances = enrol_get_instances($course2->id, true); 171 foreach ($enrolinstances as $courseenrolinstance) { 172 if ($courseenrolinstance->enrol == "manual") { 173 $instance2 = $courseenrolinstance; 174 break; 175 } 176 } 177 $enrol->enrol_user($instance2, $user->id); 178 179 // Assign capabilities to view forums for forum 2. 180 $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST); 181 $context2 = \context_module::instance($cm2->id); 182 $newrole = create_role('Role 2', 'role2', 'Role 2 description'); 183 $roleid2 = $this->assignUserCapability('mod/forum:viewdiscussion', $context2->id, $newrole); 184 185 // Create what we expect to be returned when querying the two courses. 186 unset($forum1->displaywordcount); 187 unset($forum2->displaywordcount); 188 189 $expectedforums = array(); 190 $expectedforums[$forum1->id] = (array) $forum1; 191 $expectedforums[$forum2->id] = (array) $forum2; 192 193 // Call the external function passing course ids. 194 $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id)); 195 $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums); 196 $this->assertCount(2, $forums); 197 foreach ($forums as $forum) { 198 $this->assertEquals($expectedforums[$forum['id']], $forum); 199 } 200 201 // Call the external function without passing course id. 202 $forums = mod_forum_external::get_forums_by_courses(); 203 $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums); 204 $this->assertCount(2, $forums); 205 foreach ($forums as $forum) { 206 $this->assertEquals($expectedforums[$forum['id']], $forum); 207 } 208 209 // Unenrol user from second course and alter expected forums. 210 $enrol->unenrol_user($instance2, $user->id); 211 unset($expectedforums[$forum2->id]); 212 213 // Call the external function without passing course id. 214 $forums = mod_forum_external::get_forums_by_courses(); 215 $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums); 216 $this->assertCount(1, $forums); 217 $this->assertEquals($expectedforums[$forum1->id], $forums[0]); 218 $this->assertTrue($forums[0]['cancreatediscussions']); 219 220 // Change the type of the forum, the user shouldn't be able to add discussions. 221 $DB->set_field('forum', 'type', 'news', array('id' => $forum1->id)); 222 $forums = mod_forum_external::get_forums_by_courses(); 223 $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums); 224 $this->assertFalse($forums[0]['cancreatediscussions']); 225 226 // Call for the second course we unenrolled the user from. 227 $forums = mod_forum_external::get_forums_by_courses(array($course2->id)); 228 $forums = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums); 229 $this->assertCount(0, $forums); 230 } 231 232 /** 233 * Test the toggle favourite state 234 */ 235 public function test_mod_forum_toggle_favourite_state() { 236 global $USER, $CFG, $DB; 237 238 $this->resetAfterTest(true); 239 240 // Create a user. 241 $user = self::getDataGenerator()->create_user(array('trackforums' => 1)); 242 243 // Set to the user. 244 self::setUser($user); 245 246 // Create courses to add the modules. 247 $course1 = self::getDataGenerator()->create_course(); 248 $this->getDataGenerator()->enrol_user($user->id, $course1->id); 249 250 $record = new \stdClass(); 251 $record->introformat = FORMAT_HTML; 252 $record->course = $course1->id; 253 $record->trackingtype = FORUM_TRACKING_OFF; 254 $forum1 = self::getDataGenerator()->create_module('forum', $record); 255 $forum1->introfiles = []; 256 257 // Add discussions to the forums. 258 $record = new \stdClass(); 259 $record->course = $course1->id; 260 $record->userid = $user->id; 261 $record->forum = $forum1->id; 262 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 263 264 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 1); 265 $response = \external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response); 266 $this->assertTrue($response['userstate']['favourited']); 267 268 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0); 269 $response = \external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response); 270 $this->assertFalse($response['userstate']['favourited']); 271 272 $this->setUser(0); 273 try { 274 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0); 275 } catch (\moodle_exception $e) { 276 $this->assertEquals('requireloginerror', $e->errorcode); 277 } 278 } 279 280 /** 281 * Test the toggle pin state 282 */ 283 public function test_mod_forum_set_pin_state() { 284 $this->resetAfterTest(true); 285 286 // Create a user. 287 $user = self::getDataGenerator()->create_user(array('trackforums' => 1)); 288 289 // Set to the user. 290 self::setUser($user); 291 292 // Create courses to add the modules. 293 $course1 = self::getDataGenerator()->create_course(); 294 $this->getDataGenerator()->enrol_user($user->id, $course1->id); 295 296 $record = new \stdClass(); 297 $record->introformat = FORMAT_HTML; 298 $record->course = $course1->id; 299 $record->trackingtype = FORUM_TRACKING_OFF; 300 $forum1 = self::getDataGenerator()->create_module('forum', $record); 301 $forum1->introfiles = []; 302 303 // Add discussions to the forums. 304 $record = new \stdClass(); 305 $record->course = $course1->id; 306 $record->userid = $user->id; 307 $record->forum = $forum1->id; 308 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 309 310 try { 311 $response = mod_forum_external::set_pin_state($discussion1->id, 1); 312 } catch (\Exception $e) { 313 $this->assertEquals('cannotpindiscussions', $e->errorcode); 314 } 315 316 self::setAdminUser(); 317 $response = mod_forum_external::set_pin_state($discussion1->id, 1); 318 $response = \external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response); 319 $this->assertTrue($response['pinned']); 320 321 $response = mod_forum_external::set_pin_state($discussion1->id, 0); 322 $response = \external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response); 323 $this->assertFalse($response['pinned']); 324 } 325 326 /** 327 * Test get forum posts 328 * 329 * Tests is similar to the get_forum_discussion_posts only utilizing the new return structure and entities 330 */ 331 public function test_mod_forum_get_discussion_posts() { 332 global $CFG; 333 334 $this->resetAfterTest(true); 335 336 // Set the CFG variable to allow track forums. 337 $CFG->forum_trackreadposts = true; 338 339 $urlfactory = \mod_forum\local\container::get_url_factory(); 340 $legacyfactory = \mod_forum\local\container::get_legacy_data_mapper_factory(); 341 $entityfactory = \mod_forum\local\container::get_entity_factory(); 342 343 // Create course to add the module. 344 $course1 = self::getDataGenerator()->create_course(); 345 346 // Create a user who can track forums. 347 $record = new \stdClass(); 348 $record->trackforums = true; 349 $user1 = self::getDataGenerator()->create_user($record); 350 // Create a bunch of other users to post. 351 $user2 = self::getDataGenerator()->create_user(); 352 $user2entity = $entityfactory->get_author_from_stdClass($user2); 353 $exporteduser2 = [ 354 'id' => (int) $user2->id, 355 'fullname' => fullname($user2), 356 'isdeleted' => false, 357 'groups' => [], 358 'urls' => [ 359 'profile' => $urlfactory->get_author_profile_url($user2entity, $course1->id)->out(false), 360 'profileimage' => $urlfactory->get_author_profile_image_url($user2entity), 361 ] 362 ]; 363 $user2->fullname = $exporteduser2['fullname']; 364 365 $user3 = self::getDataGenerator()->create_user(['fullname' => "Mr Pants 1"]); 366 $user3entity = $entityfactory->get_author_from_stdClass($user3); 367 $exporteduser3 = [ 368 'id' => (int) $user3->id, 369 'fullname' => fullname($user3), 370 'groups' => [], 371 'isdeleted' => false, 372 'urls' => [ 373 'profile' => $urlfactory->get_author_profile_url($user3entity, $course1->id)->out(false), 374 'profileimage' => $urlfactory->get_author_profile_image_url($user3entity), 375 ] 376 ]; 377 $user3->fullname = $exporteduser3['fullname']; 378 $forumgenerator = self::getDataGenerator()->get_plugin_generator('mod_forum'); 379 380 // Set the first created user to the test user. 381 self::setUser($user1); 382 383 // Forum with tracking off. 384 $record = new \stdClass(); 385 $record->course = $course1->id; 386 $record->trackingtype = FORUM_TRACKING_OFF; 387 // Display word count. Otherwise, word and char counts will be set to null by the forum post exporter. 388 $record->displaywordcount = true; 389 $forum1 = self::getDataGenerator()->create_module('forum', $record); 390 $forum1context = \context_module::instance($forum1->cmid); 391 392 // Forum with tracking enabled. 393 $record = new \stdClass(); 394 $record->course = $course1->id; 395 $forum2 = self::getDataGenerator()->create_module('forum', $record); 396 $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid); 397 $forum2context = \context_module::instance($forum2->cmid); 398 399 // Add discussions to the forums. 400 $record = new \stdClass(); 401 $record->course = $course1->id; 402 $record->userid = $user1->id; 403 $record->forum = $forum1->id; 404 $discussion1 = $forumgenerator->create_discussion($record); 405 406 $record = new \stdClass(); 407 $record->course = $course1->id; 408 $record->userid = $user2->id; 409 $record->forum = $forum1->id; 410 $discussion2 = $forumgenerator->create_discussion($record); 411 412 $record = new \stdClass(); 413 $record->course = $course1->id; 414 $record->userid = $user2->id; 415 $record->forum = $forum2->id; 416 $discussion3 = $forumgenerator->create_discussion($record); 417 418 // Add 2 replies to the discussion 1 from different users. 419 $record = new \stdClass(); 420 $record->discussion = $discussion1->id; 421 $record->parent = $discussion1->firstpost; 422 $record->userid = $user2->id; 423 $discussion1reply1 = $forumgenerator->create_post($record); 424 $filename = 'shouldbeanimage.jpg'; 425 // Add a fake inline image to the post. 426 $filerecordinline = array( 427 'contextid' => $forum1context->id, 428 'component' => 'mod_forum', 429 'filearea' => 'post', 430 'itemid' => $discussion1reply1->id, 431 'filepath' => '/', 432 'filename' => $filename, 433 ); 434 $fs = get_file_storage(); 435 $timepost = time(); 436 $file = $fs->create_file_from_string($filerecordinline, 'image contents (not really)'); 437 438 $record->parent = $discussion1reply1->id; 439 $record->userid = $user3->id; 440 $discussion1reply2 = $forumgenerator->create_post($record); 441 442 // Enrol the user in the course. 443 $enrol = enrol_get_plugin('manual'); 444 // Following line enrol and assign default role id to the user. 445 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course. 446 $this->getDataGenerator()->enrol_user($user1->id, $course1->id); 447 $this->getDataGenerator()->enrol_user($user2->id, $course1->id); 448 449 // Delete one user, to test that we still receive posts by this user. 450 delete_user($user3); 451 $exporteduser3 = [ 452 'id' => (int) $user3->id, 453 'fullname' => get_string('deleteduser', 'mod_forum'), 454 'groups' => [], 455 'isdeleted' => true, 456 'urls' => [ 457 'profile' => $urlfactory->get_author_profile_url($user3entity, $course1->id)->out(false), 458 'profileimage' => $urlfactory->get_author_profile_image_url($user3entity), 459 ] 460 ]; 461 462 // Create what we expect to be returned when querying the discussion. 463 $expectedposts = array( 464 'posts' => array(), 465 'courseid' => $course1->id, 466 'forumid' => $forum1->id, 467 'ratinginfo' => array( 468 'contextid' => $forum1context->id, 469 'component' => 'mod_forum', 470 'ratingarea' => 'post', 471 'canviewall' => null, 472 'canviewany' => null, 473 'scales' => array(), 474 'ratings' => array(), 475 ), 476 'warnings' => array(), 477 ); 478 479 // User pictures are initially empty, we should get the links once the external function is called. 480 $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion); 481 $isolatedurl->params(['parent' => $discussion1reply2->id]); 482 $message = file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php', 483 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id); 484 $expectedposts['posts'][] = array( 485 'id' => $discussion1reply2->id, 486 'discussionid' => $discussion1reply2->discussion, 487 'parentid' => $discussion1reply2->parent, 488 'hasparent' => true, 489 'timecreated' => $discussion1reply2->created, 490 'timemodified' => $discussion1reply2->modified, 491 'subject' => $discussion1reply2->subject, 492 'replysubject' => get_string('re', 'mod_forum') . " {$discussion1reply2->subject}", 493 'message' => $message, 494 'messageformat' => 1, // This value is usually changed by external_format_text() function. 495 'unread' => null, 496 'isdeleted' => false, 497 'isprivatereply' => false, 498 'haswordcount' => true, 499 'wordcount' => count_words($message), 500 'charcount' => count_letters($message), 501 'author'=> $exporteduser3, 502 'attachments' => [], 503 'messageinlinefiles' => [], 504 'tags' => [], 505 'html' => [ 506 'rating' => null, 507 'taglist' => null, 508 'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser3, $discussion1reply2->created) 509 ], 510 'capabilities' => [ 511 'view' => 1, 512 'edit' => 0, 513 'delete' => 0, 514 'split' => 0, 515 'reply' => 1, 516 'export' => 0, 517 'controlreadstatus' => 0, 518 'canreplyprivately' => 0, 519 'selfenrol' => 0 520 ], 521 'urls' => [ 522 'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->id), 523 'viewisolated' => $isolatedurl->out(false), 524 'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->parent), 525 'edit' => null, 526 'delete' =>null, 527 'split' => null, 528 'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [ 529 'reply' => $discussion1reply2->id 530 ]))->out(false), 531 'export' => null, 532 'markasread' => null, 533 'markasunread' => null, 534 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion), 535 ], 536 ); 537 538 539 $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion); 540 $isolatedurl->params(['parent' => $discussion1reply1->id]); 541 $message = file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php', 542 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id); 543 $expectedposts['posts'][] = array( 544 'id' => $discussion1reply1->id, 545 'discussionid' => $discussion1reply1->discussion, 546 'parentid' => $discussion1reply1->parent, 547 'hasparent' => true, 548 'timecreated' => $discussion1reply1->created, 549 'timemodified' => $discussion1reply1->modified, 550 'subject' => $discussion1reply1->subject, 551 'replysubject' => get_string('re', 'mod_forum') . " {$discussion1reply1->subject}", 552 'message' => $message, 553 'messageformat' => 1, // This value is usually changed by external_format_text() function. 554 'unread' => null, 555 'isdeleted' => false, 556 'isprivatereply' => false, 557 'haswordcount' => true, 558 'wordcount' => count_words($message), 559 'charcount' => count_letters($message), 560 'author'=> $exporteduser2, 561 'attachments' => [], 562 'messageinlinefiles' => [ 563 0 => $this->get_expected_attachment($file) 564 ], 565 'tags' => [], 566 'html' => [ 567 'rating' => null, 568 'taglist' => null, 569 'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser2, $discussion1reply1->created) 570 ], 571 'capabilities' => [ 572 'view' => 1, 573 'edit' => 0, 574 'delete' => 0, 575 'split' => 0, 576 'reply' => 1, 577 'export' => 0, 578 'controlreadstatus' => 0, 579 'canreplyprivately' => 0, 580 'selfenrol' => 0 581 ], 582 'urls' => [ 583 'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->id), 584 'viewisolated' => $isolatedurl->out(false), 585 'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->parent), 586 'edit' => null, 587 'delete' =>null, 588 'split' => null, 589 'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [ 590 'reply' => $discussion1reply1->id 591 ]))->out(false), 592 'export' => null, 593 'markasread' => null, 594 'markasunread' => null, 595 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion), 596 ], 597 ); 598 599 // Test a discussion with two additional posts (total 3 posts). 600 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC', true); 601 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 602 $this->assertEquals(3, count($posts['posts'])); 603 604 // Unset the initial discussion post. 605 array_pop($posts['posts']); 606 $this->assertEquals($expectedposts, $posts); 607 608 // Check we receive the unread count correctly on tracked forum. 609 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache. 610 $result = mod_forum_external::get_forums_by_courses(array($course1->id)); 611 $result = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result); 612 foreach ($result as $f) { 613 if ($f['id'] == $forum2->id) { 614 $this->assertEquals(1, $f['unreadpostscount']); 615 } 616 } 617 618 // Test discussion without additional posts. There should be only one post (the one created by the discussion). 619 $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'DESC'); 620 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 621 $this->assertEquals(1, count($posts['posts'])); 622 623 // Test discussion tracking on not tracked forum. 624 $result = mod_forum_external::view_forum_discussion($discussion1->id); 625 $result = \external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result); 626 $this->assertTrue($result['status']); 627 $this->assertEmpty($result['warnings']); 628 629 // Test posts have not been marked as read. 630 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC'); 631 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 632 foreach ($posts['posts'] as $post) { 633 $this->assertNull($post['unread']); 634 } 635 636 // Test discussion tracking on tracked forum. 637 $result = mod_forum_external::view_forum_discussion($discussion3->id); 638 $result = \external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result); 639 $this->assertTrue($result['status']); 640 $this->assertEmpty($result['warnings']); 641 642 // Test posts have been marked as read. 643 $posts = mod_forum_external::get_discussion_posts($discussion3->id, 'modified', 'DESC'); 644 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 645 foreach ($posts['posts'] as $post) { 646 $this->assertFalse($post['unread']); 647 } 648 649 // Check we receive 0 unread posts. 650 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache. 651 $result = mod_forum_external::get_forums_by_courses(array($course1->id)); 652 $result = \external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result); 653 foreach ($result as $f) { 654 if ($f['id'] == $forum2->id) { 655 $this->assertEquals(0, $f['unreadpostscount']); 656 } 657 } 658 } 659 660 /** 661 * Test get forum posts 662 */ 663 public function test_mod_forum_get_discussion_posts_deleted() { 664 global $CFG, $PAGE; 665 666 $this->resetAfterTest(true); 667 $generator = self::getDataGenerator()->get_plugin_generator('mod_forum'); 668 669 // Create a course and enrol some users in it. 670 $course1 = self::getDataGenerator()->create_course(); 671 672 // Create users. 673 $user1 = self::getDataGenerator()->create_user(); 674 $this->getDataGenerator()->enrol_user($user1->id, $course1->id); 675 $user2 = self::getDataGenerator()->create_user(); 676 $this->getDataGenerator()->enrol_user($user2->id, $course1->id); 677 678 // Set the first created user to the test user. 679 self::setUser($user1); 680 681 // Create test data. 682 $forum1 = self::getDataGenerator()->create_module('forum', (object) [ 683 'course' => $course1->id, 684 ]); 685 $forum1context = \context_module::instance($forum1->cmid); 686 687 // Add discussions to the forum. 688 $discussion = $generator->create_discussion((object) [ 689 'course' => $course1->id, 690 'userid' => $user1->id, 691 'forum' => $forum1->id, 692 ]); 693 694 $discussion2 = $generator->create_discussion((object) [ 695 'course' => $course1->id, 696 'userid' => $user2->id, 697 'forum' => $forum1->id, 698 ]); 699 700 // Add replies to the discussion. 701 $discussionreply1 = $generator->create_post((object) [ 702 'discussion' => $discussion->id, 703 'parent' => $discussion->firstpost, 704 'userid' => $user2->id, 705 ]); 706 $discussionreply2 = $generator->create_post((object) [ 707 'discussion' => $discussion->id, 708 'parent' => $discussionreply1->id, 709 'userid' => $user2->id, 710 'subject' => '', 711 'message' => '', 712 'messageformat' => FORMAT_PLAIN, 713 'deleted' => 1, 714 ]); 715 $discussionreply3 = $generator->create_post((object) [ 716 'discussion' => $discussion->id, 717 'parent' => $discussion->firstpost, 718 'userid' => $user2->id, 719 ]); 720 721 // Test where some posts have been marked as deleted. 722 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'modified', 'DESC'); 723 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 724 $deletedsubject = get_string('forumsubjectdeleted', 'mod_forum'); 725 $deletedmessage = get_string('forumbodydeleted', 'mod_forum'); 726 727 foreach ($posts['posts'] as $post) { 728 if ($post['id'] == $discussionreply2->id) { 729 $this->assertTrue($post['isdeleted']); 730 $this->assertEquals($deletedsubject, $post['subject']); 731 $this->assertEquals($deletedmessage, $post['message']); 732 } else { 733 $this->assertFalse($post['isdeleted']); 734 $this->assertNotEquals($deletedsubject, $post['subject']); 735 $this->assertNotEquals($deletedmessage, $post['message']); 736 } 737 } 738 } 739 740 /** 741 * Test get forum posts returns inline attachments. 742 */ 743 public function test_mod_forum_get_discussion_posts_inline_attachments() { 744 global $CFG; 745 746 $this->resetAfterTest(true); 747 748 // Create a course and enrol some users in it. 749 $course = self::getDataGenerator()->create_course(); 750 751 // Create users. 752 $user = self::getDataGenerator()->create_user(); 753 $this->getDataGenerator()->enrol_user($user->id, $course->id); 754 755 756 // Set the first created user to the test user. 757 self::setUser($user); 758 759 // Create test data. 760 $forum = self::getDataGenerator()->create_module('forum', (object) [ 761 'course' => $course->id, 762 ]); 763 764 // Create a file in a draft area for inline attachments. 765 $draftidinlineattach = file_get_unused_draft_itemid(); 766 $draftidattach = file_get_unused_draft_itemid(); 767 self::setUser($user); 768 $usercontext = \context_user::instance($user->id); 769 $filepath = '/'; 770 $filearea = 'draft'; 771 $component = 'user'; 772 $filenameimg = 'fakeimage.png'; 773 $filerecordinline = [ 774 'contextid' => $usercontext->id, 775 'component' => $component, 776 'filearea' => $filearea, 777 'itemid' => $draftidinlineattach, 778 'filepath' => $filepath, 779 'filename' => $filenameimg, 780 ]; 781 $fs = get_file_storage(); 782 $fs->create_file_from_string($filerecordinline, 'image contents (not really)'); 783 784 // Create discussion. 785 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot . 786 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" . 787 '" alt="inlineimage">.'; 788 $options = [ 789 [ 790 'name' => 'inlineattachmentsid', 791 'value' => $draftidinlineattach 792 ], 793 [ 794 'name' => 'attachmentsid', 795 'value' => $draftidattach 796 ] 797 ]; 798 $discussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject', $dummytext, 799 -1, $options); 800 801 $posts = mod_forum_external::get_discussion_posts($discussion['discussionid'], 'modified', 'DESC'); 802 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 803 $post = $posts['posts'][0]; 804 $this->assertCount(0, $post['messageinlinefiles']); 805 $this->assertEmpty($post['messageinlinefiles']); 806 807 $posts = mod_forum_external::get_discussion_posts($discussion['discussionid'], 'modified', 'DESC', 808 true); 809 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 810 $post = $posts['posts'][0]; 811 $this->assertCount(1, $post['messageinlinefiles']); 812 $this->assertEquals('fakeimage.png', $post['messageinlinefiles'][0]['filename']); 813 } 814 815 /** 816 * Test get forum posts (qanda forum) 817 */ 818 public function test_mod_forum_get_discussion_posts_qanda() { 819 global $CFG, $DB; 820 821 $this->resetAfterTest(true); 822 823 $record = new \stdClass(); 824 $user1 = self::getDataGenerator()->create_user($record); 825 $user2 = self::getDataGenerator()->create_user(); 826 827 // Set the first created user to the test user. 828 self::setUser($user1); 829 830 // Create course to add the module. 831 $course1 = self::getDataGenerator()->create_course(); 832 $this->getDataGenerator()->enrol_user($user1->id, $course1->id); 833 $this->getDataGenerator()->enrol_user($user2->id, $course1->id); 834 835 // Forum with tracking off. 836 $record = new \stdClass(); 837 $record->course = $course1->id; 838 $record->type = 'qanda'; 839 $forum1 = self::getDataGenerator()->create_module('forum', $record); 840 $forum1context = \context_module::instance($forum1->cmid); 841 842 // Add discussions to the forums. 843 $record = new \stdClass(); 844 $record->course = $course1->id; 845 $record->userid = $user2->id; 846 $record->forum = $forum1->id; 847 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 848 849 // Add 1 reply (not the actual user). 850 $record = new \stdClass(); 851 $record->discussion = $discussion1->id; 852 $record->parent = $discussion1->firstpost; 853 $record->userid = $user2->id; 854 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 855 856 // We still see only the original post. 857 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC'); 858 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 859 $this->assertEquals(1, count($posts['posts'])); 860 861 // Add a new reply, the user is going to be able to see only the original post and their new post. 862 $record = new \stdClass(); 863 $record->discussion = $discussion1->id; 864 $record->parent = $discussion1->firstpost; 865 $record->userid = $user1->id; 866 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 867 868 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC'); 869 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 870 $this->assertEquals(2, count($posts['posts'])); 871 872 // Now, we can fake the time of the user post, so he can se the rest of the discussion posts. 873 $discussion1reply2->created -= $CFG->maxeditingtime * 2; 874 $DB->update_record('forum_posts', $discussion1reply2); 875 876 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC'); 877 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 878 $this->assertEquals(3, count($posts['posts'])); 879 } 880 881 /** 882 * Test get forum discussions paginated 883 */ 884 public function test_mod_forum_get_forum_discussions_paginated() { 885 global $USER, $CFG, $DB, $PAGE; 886 887 $this->resetAfterTest(true); 888 889 // Set the CFG variable to allow track forums. 890 $CFG->forum_trackreadposts = true; 891 892 // Create a user who can track forums. 893 $record = new \stdClass(); 894 $record->trackforums = true; 895 $user1 = self::getDataGenerator()->create_user($record); 896 // Create a bunch of other users to post. 897 $user2 = self::getDataGenerator()->create_user(); 898 $user3 = self::getDataGenerator()->create_user(); 899 $user4 = self::getDataGenerator()->create_user(); 900 901 // Set the first created user to the test user. 902 self::setUser($user1); 903 904 // Create courses to add the modules. 905 $course1 = self::getDataGenerator()->create_course(); 906 907 // First forum with tracking off. 908 $record = new \stdClass(); 909 $record->course = $course1->id; 910 $record->trackingtype = FORUM_TRACKING_OFF; 911 $forum1 = self::getDataGenerator()->create_module('forum', $record); 912 913 // Add discussions to the forums. 914 $record = new \stdClass(); 915 $record->course = $course1->id; 916 $record->userid = $user1->id; 917 $record->forum = $forum1->id; 918 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 919 920 // Add three replies to the discussion 1 from different users. 921 $record = new \stdClass(); 922 $record->discussion = $discussion1->id; 923 $record->parent = $discussion1->firstpost; 924 $record->userid = $user2->id; 925 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 926 927 $record->parent = $discussion1reply1->id; 928 $record->userid = $user3->id; 929 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 930 931 $record->userid = $user4->id; 932 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 933 934 // Enrol the user in the first course. 935 $enrol = enrol_get_plugin('manual'); 936 937 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later. 938 $enrolinstances = enrol_get_instances($course1->id, true); 939 foreach ($enrolinstances as $courseenrolinstance) { 940 if ($courseenrolinstance->enrol == "manual") { 941 $instance1 = $courseenrolinstance; 942 break; 943 } 944 } 945 $enrol->enrol_user($instance1, $user1->id); 946 947 // Delete one user. 948 delete_user($user4); 949 950 // Assign capabilities to view discussions for forum 1. 951 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST); 952 $context = \context_module::instance($cm->id); 953 $newrole = create_role('Role 2', 'role2', 'Role 2 description'); 954 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole); 955 956 // Create what we expect to be returned when querying the forums. 957 958 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST); 959 960 // User pictures are initially empty, we should get the links once the external function is called. 961 $expecteddiscussions = array( 962 'id' => $discussion1->firstpost, 963 'name' => $discussion1->name, 964 'groupid' => (int) $discussion1->groupid, 965 'timemodified' => $discussion1reply3->created, 966 'usermodified' => (int) $discussion1reply3->userid, 967 'timestart' => (int) $discussion1->timestart, 968 'timeend' => (int) $discussion1->timeend, 969 'discussion' => $discussion1->id, 970 'parent' => 0, 971 'userid' => (int) $discussion1->userid, 972 'created' => (int) $post1->created, 973 'modified' => (int) $post1->modified, 974 'mailed' => (int) $post1->mailed, 975 'subject' => $post1->subject, 976 'message' => $post1->message, 977 'messageformat' => (int) $post1->messageformat, 978 'messagetrust' => (int) $post1->messagetrust, 979 'attachment' => $post1->attachment, 980 'totalscore' => (int) $post1->totalscore, 981 'mailnow' => (int) $post1->mailnow, 982 'userfullname' => fullname($user1), 983 'usermodifiedfullname' => fullname($user4), 984 'userpictureurl' => '', 985 'usermodifiedpictureurl' => '', 986 'numreplies' => 3, 987 'numunread' => 0, 988 'pinned' => (bool) FORUM_DISCUSSION_UNPINNED, 989 'locked' => false, 990 'canreply' => false, 991 'canlock' => false 992 ); 993 994 // Call the external function passing forum id. 995 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id); 996 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 997 $expectedreturn = array( 998 'discussions' => array($expecteddiscussions), 999 'warnings' => array() 1000 ); 1001 1002 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles. 1003 $userpicture = new \user_picture($user1); 1004 $userpicture->size = 1; // Size f1. 1005 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false); 1006 1007 $userpicture = new \user_picture($user4); 1008 $userpicture->size = 1; // Size f1. 1009 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false); 1010 1011 $this->assertEquals($expectedreturn, $discussions); 1012 1013 // Call without required view discussion capability. 1014 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole); 1015 try { 1016 mod_forum_external::get_forum_discussions_paginated($forum1->id); 1017 $this->fail('Exception expected due to missing capability.'); 1018 } catch (\moodle_exception $e) { 1019 $this->assertEquals('noviewdiscussionspermission', $e->errorcode); 1020 } 1021 1022 // Unenrol user from second course. 1023 $enrol->unenrol_user($instance1, $user1->id); 1024 1025 // Call for the second course we unenrolled the user from, make sure exception thrown. 1026 try { 1027 mod_forum_external::get_forum_discussions_paginated($forum1->id); 1028 $this->fail('Exception expected due to being unenrolled from the course.'); 1029 } catch (\moodle_exception $e) { 1030 $this->assertEquals('requireloginerror', $e->errorcode); 1031 } 1032 1033 $this->setAdminUser(); 1034 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id); 1035 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1036 $this->assertTrue($discussions['discussions'][0]['canlock']); 1037 } 1038 1039 /** 1040 * Test get forum discussions paginated (qanda forums) 1041 */ 1042 public function test_mod_forum_get_forum_discussions_paginated_qanda() { 1043 1044 $this->resetAfterTest(true); 1045 1046 // Create courses to add the modules. 1047 $course = self::getDataGenerator()->create_course(); 1048 1049 $user1 = self::getDataGenerator()->create_user(); 1050 $user2 = self::getDataGenerator()->create_user(); 1051 1052 // First forum with tracking off. 1053 $record = new \stdClass(); 1054 $record->course = $course->id; 1055 $record->type = 'qanda'; 1056 $forum = self::getDataGenerator()->create_module('forum', $record); 1057 1058 // Add discussions to the forums. 1059 $discussionrecord = new \stdClass(); 1060 $discussionrecord->course = $course->id; 1061 $discussionrecord->userid = $user2->id; 1062 $discussionrecord->forum = $forum->id; 1063 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord); 1064 1065 self::setAdminUser(); 1066 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1067 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1068 1069 $this->assertCount(1, $discussions['discussions']); 1070 $this->assertCount(0, $discussions['warnings']); 1071 1072 self::setUser($user1); 1073 $this->getDataGenerator()->enrol_user($user1->id, $course->id); 1074 1075 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1076 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1077 1078 $this->assertCount(1, $discussions['discussions']); 1079 $this->assertCount(0, $discussions['warnings']); 1080 1081 } 1082 1083 /** 1084 * Test get forum discussions 1085 */ 1086 public function test_mod_forum_get_forum_discussions() { 1087 global $CFG, $DB, $PAGE; 1088 1089 $this->resetAfterTest(true); 1090 1091 // Set the CFG variable to allow track forums. 1092 $CFG->forum_trackreadposts = true; 1093 1094 // Create a user who can track forums. 1095 $record = new \stdClass(); 1096 $record->trackforums = true; 1097 $user1 = self::getDataGenerator()->create_user($record); 1098 // Create a bunch of other users to post. 1099 $user2 = self::getDataGenerator()->create_user(); 1100 $user3 = self::getDataGenerator()->create_user(); 1101 $user4 = self::getDataGenerator()->create_user(); 1102 1103 // Set the first created user to the test user. 1104 self::setUser($user1); 1105 1106 // Create courses to add the modules. 1107 $course1 = self::getDataGenerator()->create_course(); 1108 1109 // First forum with tracking off. 1110 $record = new \stdClass(); 1111 $record->course = $course1->id; 1112 $record->trackingtype = FORUM_TRACKING_OFF; 1113 $forum1 = self::getDataGenerator()->create_module('forum', $record); 1114 1115 // Add discussions to the forums. 1116 $record = new \stdClass(); 1117 $record->course = $course1->id; 1118 $record->userid = $user1->id; 1119 $record->forum = $forum1->id; 1120 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1121 1122 // Add three replies to the discussion 1 from different users. 1123 $record = new \stdClass(); 1124 $record->discussion = $discussion1->id; 1125 $record->parent = $discussion1->firstpost; 1126 $record->userid = $user2->id; 1127 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 1128 1129 $record->parent = $discussion1reply1->id; 1130 $record->userid = $user3->id; 1131 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 1132 1133 $record->userid = $user4->id; 1134 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 1135 1136 // Enrol the user in the first course. 1137 $enrol = enrol_get_plugin('manual'); 1138 1139 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later. 1140 $enrolinstances = enrol_get_instances($course1->id, true); 1141 foreach ($enrolinstances as $courseenrolinstance) { 1142 if ($courseenrolinstance->enrol == "manual") { 1143 $instance1 = $courseenrolinstance; 1144 break; 1145 } 1146 } 1147 $enrol->enrol_user($instance1, $user1->id); 1148 1149 // Delete one user. 1150 delete_user($user4); 1151 1152 // Assign capabilities to view discussions for forum 1. 1153 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST); 1154 $context = \context_module::instance($cm->id); 1155 $newrole = create_role('Role 2', 'role2', 'Role 2 description'); 1156 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole); 1157 1158 // Create what we expect to be returned when querying the forums. 1159 1160 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST); 1161 1162 // User pictures are initially empty, we should get the links once the external function is called. 1163 $expecteddiscussions = array( 1164 'id' => $discussion1->firstpost, 1165 'name' => $discussion1->name, 1166 'groupid' => (int) $discussion1->groupid, 1167 'timemodified' => (int) $discussion1reply3->created, 1168 'usermodified' => (int) $discussion1reply3->userid, 1169 'timestart' => (int) $discussion1->timestart, 1170 'timeend' => (int) $discussion1->timeend, 1171 'discussion' => (int) $discussion1->id, 1172 'parent' => 0, 1173 'userid' => (int) $discussion1->userid, 1174 'created' => (int) $post1->created, 1175 'modified' => (int) $post1->modified, 1176 'mailed' => (int) $post1->mailed, 1177 'subject' => $post1->subject, 1178 'message' => $post1->message, 1179 'messageformat' => (int) $post1->messageformat, 1180 'messagetrust' => (int) $post1->messagetrust, 1181 'attachment' => $post1->attachment, 1182 'totalscore' => (int) $post1->totalscore, 1183 'mailnow' => (int) $post1->mailnow, 1184 'userfullname' => fullname($user1), 1185 'usermodifiedfullname' => fullname($user4), 1186 'userpictureurl' => '', 1187 'usermodifiedpictureurl' => '', 1188 'numreplies' => 3, 1189 'numunread' => 0, 1190 'pinned' => (bool) FORUM_DISCUSSION_UNPINNED, 1191 'locked' => false, 1192 'canreply' => false, 1193 'canlock' => false, 1194 'starred' => false, 1195 'canfavourite' => true 1196 ); 1197 1198 // Call the external function passing forum id. 1199 $discussions = mod_forum_external::get_forum_discussions($forum1->id); 1200 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1201 $expectedreturn = array( 1202 'discussions' => array($expecteddiscussions), 1203 'warnings' => array() 1204 ); 1205 1206 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles. 1207 $userpicture = new \user_picture($user1); 1208 $userpicture->size = 2; // Size f2. 1209 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false); 1210 1211 $userpicture = new \user_picture($user4); 1212 $userpicture->size = 2; // Size f2. 1213 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false); 1214 1215 $this->assertEquals($expectedreturn, $discussions); 1216 1217 // Test the starring functionality return. 1218 $t = mod_forum_external::toggle_favourite_state($discussion1->id, 1); 1219 $expectedreturn['discussions'][0]['starred'] = true; 1220 $discussions = mod_forum_external::get_forum_discussions($forum1->id); 1221 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1222 $this->assertEquals($expectedreturn, $discussions); 1223 1224 // Call without required view discussion capability. 1225 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole); 1226 try { 1227 mod_forum_external::get_forum_discussions($forum1->id); 1228 $this->fail('Exception expected due to missing capability.'); 1229 } catch (\moodle_exception $e) { 1230 $this->assertEquals('noviewdiscussionspermission', $e->errorcode); 1231 } 1232 1233 // Unenrol user from second course. 1234 $enrol->unenrol_user($instance1, $user1->id); 1235 1236 // Call for the second course we unenrolled the user from, make sure exception thrown. 1237 try { 1238 mod_forum_external::get_forum_discussions($forum1->id); 1239 $this->fail('Exception expected due to being unenrolled from the course.'); 1240 } catch (\moodle_exception $e) { 1241 $this->assertEquals('requireloginerror', $e->errorcode); 1242 } 1243 1244 $this->setAdminUser(); 1245 $discussions = mod_forum_external::get_forum_discussions($forum1->id); 1246 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1247 $this->assertTrue($discussions['discussions'][0]['canlock']); 1248 } 1249 1250 /** 1251 * Test the sorting in get forum discussions 1252 */ 1253 public function test_mod_forum_get_forum_discussions_sorting() { 1254 global $CFG, $DB, $PAGE; 1255 1256 $this->resetAfterTest(true); 1257 1258 // Set the CFG variable to allow track forums. 1259 $CFG->forum_trackreadposts = true; 1260 1261 // Create a user who can track forums. 1262 $record = new \stdClass(); 1263 $record->trackforums = true; 1264 $user1 = self::getDataGenerator()->create_user($record); 1265 // Create a bunch of other users to post. 1266 $user2 = self::getDataGenerator()->create_user(); 1267 $user3 = self::getDataGenerator()->create_user(); 1268 $user4 = self::getDataGenerator()->create_user(); 1269 1270 // Set the first created user to the test user. 1271 self::setUser($user1); 1272 1273 // Create courses to add the modules. 1274 $course1 = self::getDataGenerator()->create_course(); 1275 1276 // Enrol the user in the first course. 1277 $enrol = enrol_get_plugin('manual'); 1278 1279 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later. 1280 $enrolinstances = enrol_get_instances($course1->id, true); 1281 foreach ($enrolinstances as $courseenrolinstance) { 1282 if ($courseenrolinstance->enrol == "manual") { 1283 $instance1 = $courseenrolinstance; 1284 break; 1285 } 1286 } 1287 $enrol->enrol_user($instance1, $user1->id); 1288 1289 // First forum with tracking off. 1290 $record = new \stdClass(); 1291 $record->course = $course1->id; 1292 $record->trackingtype = FORUM_TRACKING_OFF; 1293 $forum1 = self::getDataGenerator()->create_module('forum', $record); 1294 1295 // Assign capabilities to view discussions for forum 1. 1296 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST); 1297 $context = \context_module::instance($cm->id); 1298 $newrole = create_role('Role 2', 'role2', 'Role 2 description'); 1299 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole); 1300 1301 // Add discussions to the forums. 1302 $record = new \stdClass(); 1303 $record->course = $course1->id; 1304 $record->userid = $user1->id; 1305 $record->forum = $forum1->id; 1306 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1307 sleep(1); 1308 1309 // Add three replies to the discussion 1 from different users. 1310 $record = new \stdClass(); 1311 $record->discussion = $discussion1->id; 1312 $record->parent = $discussion1->firstpost; 1313 $record->userid = $user2->id; 1314 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 1315 sleep(1); 1316 1317 $record->parent = $discussion1reply1->id; 1318 $record->userid = $user3->id; 1319 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 1320 sleep(1); 1321 1322 $record->userid = $user4->id; 1323 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 1324 sleep(1); 1325 1326 // Create discussion2. 1327 $record2 = new \stdClass(); 1328 $record2->course = $course1->id; 1329 $record2->userid = $user1->id; 1330 $record2->forum = $forum1->id; 1331 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record2); 1332 sleep(1); 1333 1334 // Add one reply to the discussion 2. 1335 $record2 = new \stdClass(); 1336 $record2->discussion = $discussion2->id; 1337 $record2->parent = $discussion2->firstpost; 1338 $record2->userid = $user2->id; 1339 $discussion2reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record2); 1340 sleep(1); 1341 1342 // Create discussion 3. 1343 $record3 = new \stdClass(); 1344 $record3->course = $course1->id; 1345 $record3->userid = $user1->id; 1346 $record3->forum = $forum1->id; 1347 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record3); 1348 sleep(1); 1349 1350 // Add two replies to the discussion 3. 1351 $record3 = new \stdClass(); 1352 $record3->discussion = $discussion3->id; 1353 $record3->parent = $discussion3->firstpost; 1354 $record3->userid = $user2->id; 1355 $discussion3reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record3); 1356 sleep(1); 1357 1358 $record3->parent = $discussion3reply1->id; 1359 $record3->userid = $user3->id; 1360 $discussion3reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record3); 1361 1362 // Call the external function passing forum id. 1363 $discussions = mod_forum_external::get_forum_discussions($forum1->id); 1364 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1365 // Discussions should be ordered by last post date in descending order by default. 1366 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion3->id); 1367 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id); 1368 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id); 1369 1370 $vaultfactory = \mod_forum\local\container::get_vault_factory(); 1371 $discussionlistvault = $vaultfactory->get_discussions_in_forum_vault(); 1372 1373 // Call the external function passing forum id and sort order parameter. 1374 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_LASTPOST_ASC); 1375 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1376 // Discussions should be ordered by last post date in ascending order. 1377 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id); 1378 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id); 1379 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id); 1380 1381 // Call the external function passing forum id and sort order parameter. 1382 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_CREATED_DESC); 1383 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1384 // Discussions should be ordered by discussion creation date in descending order. 1385 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion3->id); 1386 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id); 1387 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id); 1388 1389 // Call the external function passing forum id and sort order parameter. 1390 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_CREATED_ASC); 1391 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1392 // Discussions should be ordered by discussion creation date in ascending order. 1393 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id); 1394 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id); 1395 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id); 1396 1397 // Call the external function passing forum id and sort order parameter. 1398 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_REPLIES_DESC); 1399 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1400 // Discussions should be ordered by the number of replies in descending order. 1401 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id); 1402 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id); 1403 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion2->id); 1404 1405 // Call the external function passing forum id and sort order parameter. 1406 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_REPLIES_ASC); 1407 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1408 // Discussions should be ordered by the number of replies in ascending order. 1409 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id); 1410 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id); 1411 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id); 1412 1413 // Pin discussion2. 1414 $DB->update_record('forum_discussions', 1415 (object) array('id' => $discussion2->id, 'pinned' => FORUM_DISCUSSION_PINNED)); 1416 1417 // Call the external function passing forum id. 1418 $discussions = mod_forum_external::get_forum_discussions($forum1->id); 1419 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1420 // Discussions should be ordered by last post date in descending order by default. 1421 // Pinned discussions should be at the top of the list. 1422 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id); 1423 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id); 1424 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id); 1425 1426 // Call the external function passing forum id and sort order parameter. 1427 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_LASTPOST_ASC); 1428 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 1429 // Discussions should be ordered by last post date in ascending order. 1430 // Pinned discussions should be at the top of the list. 1431 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id); 1432 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion1->id); 1433 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id); 1434 } 1435 1436 /** 1437 * Test add_discussion_post 1438 */ 1439 public function test_add_discussion_post() { 1440 global $CFG; 1441 1442 $this->resetAfterTest(true); 1443 1444 $user = self::getDataGenerator()->create_user(); 1445 $otheruser = self::getDataGenerator()->create_user(); 1446 1447 self::setAdminUser(); 1448 1449 // Create course to add the module. 1450 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0)); 1451 1452 // Forum with tracking off. 1453 $record = new \stdClass(); 1454 $record->course = $course->id; 1455 $forum = self::getDataGenerator()->create_module('forum', $record); 1456 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST); 1457 $forumcontext = \context_module::instance($forum->cmid); 1458 1459 // Add discussions to the forums. 1460 $record = new \stdClass(); 1461 $record->course = $course->id; 1462 $record->userid = $user->id; 1463 $record->forum = $forum->id; 1464 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1465 1466 // Try to post (user not enrolled). 1467 self::setUser($user); 1468 try { 1469 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...'); 1470 $this->fail('Exception expected due to being unenrolled from the course.'); 1471 } catch (\moodle_exception $e) { 1472 $this->assertEquals('requireloginerror', $e->errorcode); 1473 } 1474 1475 $this->getDataGenerator()->enrol_user($user->id, $course->id); 1476 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id); 1477 1478 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...'); 1479 $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost); 1480 1481 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'modified', 'ASC'); 1482 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 1483 // We receive the discussion and the post. 1484 $this->assertEquals(2, count($posts['posts'])); 1485 1486 $tested = false; 1487 foreach ($posts['posts'] as $thispost) { 1488 if ($createdpost['postid'] == $thispost['id']) { 1489 $this->assertEquals('some subject', $thispost['subject']); 1490 $this->assertEquals('some text here...', $thispost['message']); 1491 $this->assertEquals(FORMAT_HTML, $thispost['messageformat']); // This is the default if format was not specified. 1492 $tested = true; 1493 } 1494 } 1495 $this->assertTrue($tested); 1496 1497 // Let's simulate a call with any other format, it should be stored that way. 1498 global $DB; // Yes, we are going to use DB facilities too, because cannot rely on other functions for checking 1499 // the format. They eat it completely (going back to FORMAT_HTML. So we only can trust DB for further 1500 // processing. 1501 $formats = [FORMAT_PLAIN, FORMAT_MOODLE, FORMAT_MARKDOWN, FORMAT_HTML]; 1502 $options = []; 1503 foreach ($formats as $format) { 1504 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 1505 'with some format', 'some formatted here...', $options, $format); 1506 $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost); 1507 $dbformat = $DB->get_field('forum_posts', 'messageformat', ['id' => $createdpost['postid']]); 1508 $this->assertEquals($format, $dbformat); 1509 } 1510 1511 // Now let's try the 'topreferredformat' option. That should end with the content 1512 // transformed and the format being FORMAT_HTML (when, like in this case, user preferred 1513 // format is HTML, inferred from editor in preferences). 1514 $options = [['name' => 'topreferredformat', 'value' => true]]; 1515 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 1516 'interesting subject', 'with some https://example.com link', $options, FORMAT_MOODLE); 1517 $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost); 1518 $dbpost = $DB->get_record('forum_posts', ['id' => $createdpost['postid']]); 1519 // Format HTML and content converted, we should get. 1520 $this->assertEquals(FORMAT_HTML, $dbpost->messageformat); 1521 $this->assertEquals('<div class="text_to_html">with some https://example.com link</div>', $dbpost->message); 1522 1523 // Test inline and regular attachment in post 1524 // Create a file in a draft area for inline attachments. 1525 $draftidinlineattach = file_get_unused_draft_itemid(); 1526 $draftidattach = file_get_unused_draft_itemid(); 1527 self::setUser($user); 1528 $usercontext = \context_user::instance($user->id); 1529 $filepath = '/'; 1530 $filearea = 'draft'; 1531 $component = 'user'; 1532 $filenameimg = 'shouldbeanimage.txt'; 1533 $filerecordinline = array( 1534 'contextid' => $usercontext->id, 1535 'component' => $component, 1536 'filearea' => $filearea, 1537 'itemid' => $draftidinlineattach, 1538 'filepath' => $filepath, 1539 'filename' => $filenameimg, 1540 ); 1541 $fs = get_file_storage(); 1542 1543 // Create a file in a draft area for regular attachments. 1544 $filerecordattach = $filerecordinline; 1545 $attachfilename = 'attachment.txt'; 1546 $filerecordattach['filename'] = $attachfilename; 1547 $filerecordattach['itemid'] = $draftidattach; 1548 $fs->create_file_from_string($filerecordinline, 'image contents (not really)'); 1549 $fs->create_file_from_string($filerecordattach, 'simple text attachment'); 1550 1551 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach), 1552 array('name' => 'attachmentsid', 'value' => $draftidattach)); 1553 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot 1554 . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" 1555 . '" alt="inlineimage">.'; 1556 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment', 1557 $dummytext, $options); 1558 $createdpost = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost); 1559 1560 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'modified', 'ASC'); 1561 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 1562 // We receive the discussion and the post. 1563 // Can't guarantee order of posts during tests. 1564 $postfound = false; 1565 foreach ($posts['posts'] as $thispost) { 1566 if ($createdpost['postid'] == $thispost['id']) { 1567 $this->assertEquals($createdpost['postid'], $thispost['id']); 1568 $this->assertCount(1, $thispost['attachments']); 1569 $this->assertEquals('attachment.txt', $thispost['attachments'][0]['filename']); 1570 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment"); 1571 $this->assertStringContainsString('pluginfile.php', $thispost['message']); 1572 $postfound = true; 1573 break; 1574 } 1575 } 1576 1577 $this->assertTrue($postfound); 1578 1579 // Check not posting in groups the user is not member of. 1580 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); 1581 groups_add_member($group->id, $otheruser->id); 1582 1583 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS)); 1584 $record->forum = $forum->id; 1585 $record->userid = $otheruser->id; 1586 $record->groupid = $group->id; 1587 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1588 1589 try { 1590 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...'); 1591 $this->fail('Exception expected due to invalid permissions for posting.'); 1592 } catch (\moodle_exception $e) { 1593 $this->assertEquals('nopostforum', $e->errorcode); 1594 } 1595 } 1596 1597 /** 1598 * Test add_discussion_post and auto subscription to a discussion. 1599 */ 1600 public function test_add_discussion_post_subscribe_discussion() { 1601 global $USER; 1602 1603 $this->resetAfterTest(true); 1604 1605 self::setAdminUser(); 1606 1607 $user = self::getDataGenerator()->create_user(); 1608 $admin = get_admin(); 1609 // Create course to add the module. 1610 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0)); 1611 1612 $this->getDataGenerator()->enrol_user($user->id, $course->id); 1613 1614 // Forum with tracking off. 1615 $record = new \stdClass(); 1616 $record->course = $course->id; 1617 $forum = self::getDataGenerator()->create_module('forum', $record); 1618 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST); 1619 1620 // Add discussions to the forums. 1621 $record = new \stdClass(); 1622 $record->course = $course->id; 1623 $record->userid = $admin->id; 1624 $record->forum = $forum->id; 1625 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1626 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1627 1628 // Try to post as user. 1629 self::setUser($user); 1630 // Enable auto subscribe discussion. 1631 $USER->autosubscribe = true; 1632 // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference enabled). 1633 mod_forum_external::add_discussion_post($discussion1->firstpost, 'some subject', 'some text here...'); 1634 1635 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'ASC'); 1636 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 1637 // We receive the discussion and the post. 1638 $this->assertEquals(2, count($posts['posts'])); 1639 // The user should be subscribed to the discussion after adding a discussion post. 1640 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm)); 1641 1642 // Disable auto subscribe discussion. 1643 $USER->autosubscribe = false; 1644 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm)); 1645 // Add a discussion post in a forum discussion where the user is subscribed (auto-subscribe preference disabled). 1646 mod_forum_external::add_discussion_post($discussion1->firstpost, 'some subject 1', 'some text here 1...'); 1647 1648 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'ASC'); 1649 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 1650 // We receive the discussion and the post. 1651 $this->assertEquals(3, count($posts['posts'])); 1652 // The user should still be subscribed to the discussion after adding a discussion post. 1653 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm)); 1654 1655 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm)); 1656 // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference disabled). 1657 mod_forum_external::add_discussion_post($discussion2->firstpost, 'some subject 2', 'some text here 2...'); 1658 1659 $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'ASC'); 1660 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 1661 // We receive the discussion and the post. 1662 $this->assertEquals(2, count($posts['posts'])); 1663 // The user should still not be subscribed to the discussion after adding a discussion post. 1664 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm)); 1665 1666 // Passing a value for the discussionsubscribe option parameter. 1667 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm)); 1668 // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference disabled), 1669 // and the option parameter 'discussionsubscribe' => true in the webservice. 1670 $option = array('name' => 'discussionsubscribe', 'value' => true); 1671 $options[] = $option; 1672 mod_forum_external::add_discussion_post($discussion2->firstpost, 'some subject 2', 'some text here 2...', 1673 $options); 1674 1675 $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'ASC'); 1676 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 1677 // We receive the discussion and the post. 1678 $this->assertEquals(3, count($posts['posts'])); 1679 // The user should now be subscribed to the discussion after adding a discussion post. 1680 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm)); 1681 } 1682 1683 /* 1684 * Test add_discussion. A basic test since all the API functions are already covered by unit tests. 1685 */ 1686 public function test_add_discussion() { 1687 global $CFG, $USER; 1688 $this->resetAfterTest(true); 1689 1690 // Create courses to add the modules. 1691 $course = self::getDataGenerator()->create_course(); 1692 1693 $user1 = self::getDataGenerator()->create_user(); 1694 $user2 = self::getDataGenerator()->create_user(); 1695 1696 // First forum with tracking off. 1697 $record = new \stdClass(); 1698 $record->course = $course->id; 1699 $record->type = 'news'; 1700 $forum = self::getDataGenerator()->create_module('forum', $record); 1701 1702 self::setUser($user1); 1703 $this->getDataGenerator()->enrol_user($user1->id, $course->id); 1704 1705 try { 1706 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...'); 1707 $this->fail('Exception expected due to invalid permissions.'); 1708 } catch (\moodle_exception $e) { 1709 $this->assertEquals('cannotcreatediscussion', $e->errorcode); 1710 } 1711 1712 self::setAdminUser(); 1713 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...'); 1714 $createddiscussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion); 1715 1716 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1717 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1718 1719 $this->assertCount(1, $discussions['discussions']); 1720 $this->assertCount(0, $discussions['warnings']); 1721 1722 $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']); 1723 $this->assertEquals(-1, $discussions['discussions'][0]['groupid']); 1724 $this->assertEquals('the subject', $discussions['discussions'][0]['subject']); 1725 $this->assertEquals('some text here...', $discussions['discussions'][0]['message']); 1726 1727 $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1, 1728 array('options' => array('name' => 'discussionpinned', 1729 'value' => true))); 1730 $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...'); 1731 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1732 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1733 $this->assertCount(3, $discussions['discussions']); 1734 $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']); 1735 1736 // Test inline and regular attachment in new discussion 1737 // Create a file in a draft area for inline attachments. 1738 1739 $fs = get_file_storage(); 1740 1741 $draftidinlineattach = file_get_unused_draft_itemid(); 1742 $draftidattach = file_get_unused_draft_itemid(); 1743 1744 $usercontext = \context_user::instance($USER->id); 1745 $filepath = '/'; 1746 $filearea = 'draft'; 1747 $component = 'user'; 1748 $filenameimg = 'shouldbeanimage.txt'; 1749 $filerecord = array( 1750 'contextid' => $usercontext->id, 1751 'component' => $component, 1752 'filearea' => $filearea, 1753 'itemid' => $draftidinlineattach, 1754 'filepath' => $filepath, 1755 'filename' => $filenameimg, 1756 ); 1757 1758 // Create a file in a draft area for regular attachments. 1759 $filerecordattach = $filerecord; 1760 $attachfilename = 'attachment.txt'; 1761 $filerecordattach['filename'] = $attachfilename; 1762 $filerecordattach['itemid'] = $draftidattach; 1763 $fs->create_file_from_string($filerecord, 'image contents (not really)'); 1764 $fs->create_file_from_string($filerecordattach, 'simple text attachment'); 1765 1766 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot . 1767 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" . 1768 '" alt="inlineimage">.'; 1769 1770 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach), 1771 array('name' => 'attachmentsid', 'value' => $draftidattach)); 1772 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject', 1773 $dummytext, -1, $options); 1774 $createddiscussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion); 1775 1776 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1777 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1778 1779 $this->assertCount(4, $discussions['discussions']); 1780 $this->assertCount(0, $createddiscussion['warnings']); 1781 // Can't guarantee order of posts during tests. 1782 $postfound = false; 1783 foreach ($discussions['discussions'] as $thisdiscussion) { 1784 if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) { 1785 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment"); 1786 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment"); 1787 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment"); 1788 $this->assertStringNotContainsString('draftfile.php', $thisdiscussion['message']); 1789 $this->assertStringContainsString('pluginfile.php', $thisdiscussion['message']); 1790 $postfound = true; 1791 break; 1792 } 1793 } 1794 1795 $this->assertTrue($postfound); 1796 } 1797 1798 /** 1799 * Test adding discussions in a course with gorups 1800 */ 1801 public function test_add_discussion_in_course_with_groups() { 1802 global $CFG; 1803 1804 $this->resetAfterTest(true); 1805 1806 // Create course to add the module. 1807 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0)); 1808 $user = self::getDataGenerator()->create_user(); 1809 $this->getDataGenerator()->enrol_user($user->id, $course->id); 1810 1811 // Forum forcing separate gropus. 1812 $record = new \stdClass(); 1813 $record->course = $course->id; 1814 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS)); 1815 1816 // Try to post (user not enrolled). 1817 self::setUser($user); 1818 1819 // The user is not enroled in any group, try to post in a forum with separate groups. 1820 try { 1821 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...'); 1822 $this->fail('Exception expected due to invalid group permissions.'); 1823 } catch (\moodle_exception $e) { 1824 $this->assertEquals('cannotcreatediscussion', $e->errorcode); 1825 } 1826 1827 try { 1828 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0); 1829 $this->fail('Exception expected due to invalid group permissions.'); 1830 } catch (\moodle_exception $e) { 1831 $this->assertEquals('cannotcreatediscussion', $e->errorcode); 1832 } 1833 1834 // Create a group. 1835 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); 1836 1837 // Try to post in a group the user is not enrolled. 1838 try { 1839 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id); 1840 $this->fail('Exception expected due to invalid group permissions.'); 1841 } catch (\moodle_exception $e) { 1842 $this->assertEquals('cannotcreatediscussion', $e->errorcode); 1843 } 1844 1845 // Add the user to a group. 1846 groups_add_member($group->id, $user->id); 1847 1848 // Try to post in a group the user is not enrolled. 1849 try { 1850 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1); 1851 $this->fail('Exception expected due to invalid group.'); 1852 } catch (\moodle_exception $e) { 1853 $this->assertEquals('cannotcreatediscussion', $e->errorcode); 1854 } 1855 1856 // Nost add the discussion using a valid group. 1857 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id); 1858 $discussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion); 1859 1860 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1861 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1862 1863 $this->assertCount(1, $discussions['discussions']); 1864 $this->assertCount(0, $discussions['warnings']); 1865 $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']); 1866 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']); 1867 1868 // Now add a discussions without indicating a group. The function should guess the correct group. 1869 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...'); 1870 $discussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion); 1871 1872 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1873 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1874 1875 $this->assertCount(2, $discussions['discussions']); 1876 $this->assertCount(0, $discussions['warnings']); 1877 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']); 1878 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']); 1879 1880 // Enrol the same user in other group. 1881 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); 1882 groups_add_member($group2->id, $user->id); 1883 1884 // Now add a discussions without indicating a group. The function should guess the correct group (the first one). 1885 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...'); 1886 $discussion = \external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion); 1887 1888 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 1889 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 1890 1891 $this->assertCount(3, $discussions['discussions']); 1892 $this->assertCount(0, $discussions['warnings']); 1893 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']); 1894 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']); 1895 $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']); 1896 1897 } 1898 1899 /* 1900 * Test set_lock_state. 1901 */ 1902 public function test_set_lock_state() { 1903 global $DB; 1904 $this->resetAfterTest(true); 1905 1906 // Create courses to add the modules. 1907 $course = self::getDataGenerator()->create_course(); 1908 $user = self::getDataGenerator()->create_user(); 1909 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 1910 1911 // First forum with tracking off. 1912 $record = new \stdClass(); 1913 $record->course = $course->id; 1914 $record->type = 'news'; 1915 $forum = self::getDataGenerator()->create_module('forum', $record); 1916 1917 $record = new \stdClass(); 1918 $record->course = $course->id; 1919 $record->userid = $user->id; 1920 $record->forum = $forum->id; 1921 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 1922 1923 // User who is a student. 1924 self::setUser($user); 1925 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual'); 1926 1927 // Only a teacher should be able to lock a discussion. 1928 try { 1929 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0); 1930 $this->fail('Exception expected due to missing capability.'); 1931 } catch (\moodle_exception $e) { 1932 $this->assertEquals('errorcannotlock', $e->errorcode); 1933 } 1934 1935 // Set the lock. 1936 self::setAdminUser(); 1937 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0); 1938 $result = \external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result); 1939 $this->assertTrue($result['locked']); 1940 $this->assertNotEquals(0, $result['times']['locked']); 1941 1942 // Unset the lock. 1943 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, time()); 1944 $result = \external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result); 1945 $this->assertFalse($result['locked']); 1946 $this->assertEquals('0', $result['times']['locked']); 1947 } 1948 1949 /* 1950 * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests. 1951 */ 1952 public function test_can_add_discussion() { 1953 global $DB; 1954 $this->resetAfterTest(true); 1955 1956 // Create courses to add the modules. 1957 $course = self::getDataGenerator()->create_course(); 1958 1959 $user = self::getDataGenerator()->create_user(); 1960 1961 // First forum with tracking off. 1962 $record = new \stdClass(); 1963 $record->course = $course->id; 1964 $record->type = 'news'; 1965 $forum = self::getDataGenerator()->create_module('forum', $record); 1966 1967 // User with no permissions to add in a news forum. 1968 self::setUser($user); 1969 $this->getDataGenerator()->enrol_user($user->id, $course->id); 1970 1971 $result = mod_forum_external::can_add_discussion($forum->id); 1972 $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result); 1973 $this->assertFalse($result['status']); 1974 $this->assertFalse($result['canpindiscussions']); 1975 $this->assertTrue($result['cancreateattachment']); 1976 1977 // Disable attachments. 1978 $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id)); 1979 $result = mod_forum_external::can_add_discussion($forum->id); 1980 $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result); 1981 $this->assertFalse($result['status']); 1982 $this->assertFalse($result['canpindiscussions']); 1983 $this->assertFalse($result['cancreateattachment']); 1984 $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id)); // Enable attachments again. 1985 1986 self::setAdminUser(); 1987 $result = mod_forum_external::can_add_discussion($forum->id); 1988 $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result); 1989 $this->assertTrue($result['status']); 1990 $this->assertTrue($result['canpindiscussions']); 1991 $this->assertTrue($result['cancreateattachment']); 1992 } 1993 1994 /* 1995 * A basic test to make sure users cannot post to forum after the cutoff date. 1996 */ 1997 public function test_can_add_discussion_after_cutoff() { 1998 $this->resetAfterTest(true); 1999 2000 // Create courses to add the modules. 2001 $course = self::getDataGenerator()->create_course(); 2002 2003 $user = self::getDataGenerator()->create_user(); 2004 2005 // Create a forum with cutoff date set to a past date. 2006 $forum = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 'cutoffdate' => time() - 1]); 2007 2008 // User with no mod/forum:canoverridecutoff capability. 2009 self::setUser($user); 2010 $this->getDataGenerator()->enrol_user($user->id, $course->id); 2011 2012 $result = mod_forum_external::can_add_discussion($forum->id); 2013 $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result); 2014 $this->assertFalse($result['status']); 2015 2016 self::setAdminUser(); 2017 $result = mod_forum_external::can_add_discussion($forum->id); 2018 $result = \external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result); 2019 $this->assertTrue($result['status']); 2020 } 2021 2022 /** 2023 * Test get posts discussions including rating information. 2024 */ 2025 public function test_mod_forum_get_discussion_rating_information() { 2026 global $DB, $CFG, $PAGE; 2027 require_once($CFG->dirroot . '/rating/lib.php'); 2028 $PAGE->set_url('/my/index.php'); // Need this because some internal API calls require the $PAGE url to be set. 2029 $this->resetAfterTest(true); 2030 2031 $user1 = self::getDataGenerator()->create_user(); 2032 $user2 = self::getDataGenerator()->create_user(); 2033 $user3 = self::getDataGenerator()->create_user(); 2034 $teacher = self::getDataGenerator()->create_user(); 2035 2036 // Create course to add the module. 2037 $course = self::getDataGenerator()->create_course(); 2038 2039 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2040 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 2041 $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id, 'manual'); 2042 $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id, 'manual'); 2043 $this->getDataGenerator()->enrol_user($user3->id, $course->id, $studentrole->id, 'manual'); 2044 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual'); 2045 2046 // Create the forum. 2047 $record = new \stdClass(); 2048 $record->course = $course->id; 2049 // Set Aggregate type = Average of ratings. 2050 $record->assessed = RATING_AGGREGATE_AVERAGE; 2051 $record->scale = 100; 2052 $forum = self::getDataGenerator()->create_module('forum', $record); 2053 $context = \context_module::instance($forum->cmid); 2054 2055 // Add discussion to the forum. 2056 $record = new \stdClass(); 2057 $record->course = $course->id; 2058 $record->userid = $user1->id; 2059 $record->forum = $forum->id; 2060 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 2061 2062 // Retrieve the first post. 2063 $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); 2064 2065 // Rate the discussion as user2. 2066 $rating1 = new \stdClass(); 2067 $rating1->contextid = $context->id; 2068 $rating1->component = 'mod_forum'; 2069 $rating1->ratingarea = 'post'; 2070 $rating1->itemid = $post->id; 2071 $rating1->rating = 50; 2072 $rating1->scaleid = 100; 2073 $rating1->userid = $user2->id; 2074 $rating1->timecreated = time(); 2075 $rating1->timemodified = time(); 2076 $rating1->id = $DB->insert_record('rating', $rating1); 2077 2078 // Rate the discussion as user3. 2079 $rating2 = new \stdClass(); 2080 $rating2->contextid = $context->id; 2081 $rating2->component = 'mod_forum'; 2082 $rating2->ratingarea = 'post'; 2083 $rating2->itemid = $post->id; 2084 $rating2->rating = 100; 2085 $rating2->scaleid = 100; 2086 $rating2->userid = $user3->id; 2087 $rating2->timecreated = time() + 1; 2088 $rating2->timemodified = time() + 1; 2089 $rating2->id = $DB->insert_record('rating', $rating2); 2090 2091 // Retrieve the rating for the post as student. 2092 $this->setUser($user1); 2093 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'id', 'DESC'); 2094 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2095 $this->assertCount(1, $posts['ratinginfo']['ratings']); 2096 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']); 2097 $this->assertFalse($posts['ratinginfo']['canviewall']); 2098 $this->assertFalse($posts['ratinginfo']['ratings'][0]['canrate']); 2099 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']); 2100 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']); 2101 2102 // Retrieve the rating for the post as teacher. 2103 $this->setUser($teacher); 2104 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'id', 'DESC'); 2105 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2106 $this->assertCount(1, $posts['ratinginfo']['ratings']); 2107 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']); 2108 $this->assertTrue($posts['ratinginfo']['canviewall']); 2109 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canrate']); 2110 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']); 2111 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']); 2112 } 2113 2114 /** 2115 * Test mod_forum_get_forum_access_information. 2116 */ 2117 public function test_mod_forum_get_forum_access_information() { 2118 global $DB; 2119 2120 $this->resetAfterTest(true); 2121 2122 $student = self::getDataGenerator()->create_user(); 2123 $course = self::getDataGenerator()->create_course(); 2124 // Create the forum. 2125 $record = new \stdClass(); 2126 $record->course = $course->id; 2127 $forum = self::getDataGenerator()->create_module('forum', $record); 2128 2129 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 2130 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); 2131 2132 self::setUser($student); 2133 $result = mod_forum_external::get_forum_access_information($forum->id); 2134 $result = \external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result); 2135 2136 // Check default values for capabilities. 2137 $enabledcaps = array('canviewdiscussion', 'canstartdiscussion', 'canreplypost', 'canviewrating', 'cancreateattachment', 2138 'canexportownpost', 'cancantogglefavourite', 'candeleteownpost', 'canallowforcesubscribe'); 2139 2140 unset($result['warnings']); 2141 foreach ($result as $capname => $capvalue) { 2142 if (in_array($capname, $enabledcaps)) { 2143 $this->assertTrue($capvalue); 2144 } else { 2145 $this->assertFalse($capvalue); 2146 } 2147 } 2148 // Now, unassign some capabilities. 2149 unassign_capability('mod/forum:deleteownpost', $studentrole->id); 2150 unassign_capability('mod/forum:allowforcesubscribe', $studentrole->id); 2151 array_pop($enabledcaps); 2152 array_pop($enabledcaps); 2153 accesslib_clear_all_caches_for_unit_testing(); 2154 2155 $result = mod_forum_external::get_forum_access_information($forum->id); 2156 $result = \external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result); 2157 unset($result['warnings']); 2158 foreach ($result as $capname => $capvalue) { 2159 if (in_array($capname, $enabledcaps)) { 2160 $this->assertTrue($capvalue); 2161 } else { 2162 $this->assertFalse($capvalue); 2163 } 2164 } 2165 } 2166 2167 /** 2168 * Test add_discussion_post 2169 */ 2170 public function test_add_discussion_post_private() { 2171 global $DB; 2172 2173 $this->resetAfterTest(true); 2174 2175 self::setAdminUser(); 2176 2177 // Create course to add the module. 2178 $course = self::getDataGenerator()->create_course(); 2179 2180 // Standard forum. 2181 $record = new \stdClass(); 2182 $record->course = $course->id; 2183 $forum = self::getDataGenerator()->create_module('forum', $record); 2184 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST); 2185 $forumcontext = \context_module::instance($forum->cmid); 2186 $generator = self::getDataGenerator()->get_plugin_generator('mod_forum'); 2187 2188 // Create an enrol users. 2189 $student1 = self::getDataGenerator()->create_user(); 2190 $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student'); 2191 $student2 = self::getDataGenerator()->create_user(); 2192 $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student'); 2193 $teacher1 = self::getDataGenerator()->create_user(); 2194 $this->getDataGenerator()->enrol_user($teacher1->id, $course->id, 'editingteacher'); 2195 $teacher2 = self::getDataGenerator()->create_user(); 2196 $this->getDataGenerator()->enrol_user($teacher2->id, $course->id, 'editingteacher'); 2197 2198 // Add a new discussion to the forum. 2199 self::setUser($student1); 2200 $record = new \stdClass(); 2201 $record->course = $course->id; 2202 $record->userid = $student1->id; 2203 $record->forum = $forum->id; 2204 $discussion = $generator->create_discussion($record); 2205 2206 // Have the teacher reply privately. 2207 self::setUser($teacher1); 2208 $post = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...', [ 2209 [ 2210 'name' => 'private', 2211 'value' => true, 2212 ], 2213 ]); 2214 $post = \external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $post); 2215 $privatereply = $DB->get_record('forum_posts', array('id' => $post['postid'])); 2216 $this->assertEquals($student1->id, $privatereply->privatereplyto); 2217 // Bump the time of the private reply to ensure order. 2218 $privatereply->created++; 2219 $privatereply->modified = $privatereply->created; 2220 $DB->update_record('forum_posts', $privatereply); 2221 2222 // The teacher will receive their private reply. 2223 self::setUser($teacher1); 2224 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'id', 'DESC'); 2225 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2226 $this->assertEquals(2, count($posts['posts'])); 2227 $this->assertTrue($posts['posts'][0]['isprivatereply']); 2228 2229 // Another teacher on the course will also receive the private reply. 2230 self::setUser($teacher2); 2231 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'id', 'DESC'); 2232 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2233 $this->assertEquals(2, count($posts['posts'])); 2234 $this->assertTrue($posts['posts'][0]['isprivatereply']); 2235 2236 // The student will receive the private reply. 2237 self::setUser($student1); 2238 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'id', 'DESC'); 2239 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2240 $this->assertEquals(2, count($posts['posts'])); 2241 $this->assertTrue($posts['posts'][0]['isprivatereply']); 2242 2243 // Another student will not receive the private reply. 2244 self::setUser($student2); 2245 $posts = mod_forum_external::get_discussion_posts($discussion->id, 'id', 'ASC'); 2246 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2247 $this->assertEquals(1, count($posts['posts'])); 2248 $this->assertFalse($posts['posts'][0]['isprivatereply']); 2249 2250 // A user cannot reply to a private reply. 2251 self::setUser($teacher2); 2252 $this->expectException('coding_exception'); 2253 $post = mod_forum_external::add_discussion_post($privatereply->id, 'some subject', 'some text here...', [ 2254 'options' => [ 2255 'name' => 'private', 2256 'value' => false, 2257 ], 2258 ]); 2259 } 2260 2261 /** 2262 * Test trusted text enabled. 2263 */ 2264 public function test_trusted_text_enabled() { 2265 global $USER, $CFG; 2266 2267 $this->resetAfterTest(true); 2268 $CFG->enabletrusttext = 1; 2269 2270 $dangeroustext = '<button>Untrusted text</button>'; 2271 $cleantext = 'Untrusted text'; 2272 2273 // Create courses to add the modules. 2274 $course = self::getDataGenerator()->create_course(); 2275 $user1 = self::getDataGenerator()->create_user(); 2276 2277 // First forum with tracking off. 2278 $record = new \stdClass(); 2279 $record->course = $course->id; 2280 $record->type = 'qanda'; 2281 $forum = self::getDataGenerator()->create_module('forum', $record); 2282 $context = \context_module::instance($forum->cmid); 2283 2284 // Add discussions to the forums. 2285 $discussionrecord = new \stdClass(); 2286 $discussionrecord->course = $course->id; 2287 $discussionrecord->userid = $user1->id; 2288 $discussionrecord->forum = $forum->id; 2289 $discussionrecord->message = $dangeroustext; 2290 $discussionrecord->messagetrust = trusttext_trusted($context); 2291 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord); 2292 2293 self::setAdminUser(); 2294 $discussionrecord->userid = $USER->id; 2295 $discussionrecord->messagetrust = trusttext_trusted($context); 2296 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord); 2297 2298 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id); 2299 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions); 2300 2301 $this->assertCount(2, $discussions['discussions']); 2302 $this->assertCount(0, $discussions['warnings']); 2303 // Admin message is fully trusted. 2304 $this->assertEquals(1, $discussions['discussions'][0]['messagetrust']); 2305 $this->assertEquals($dangeroustext, $discussions['discussions'][0]['message']); 2306 // Student message is not trusted. 2307 $this->assertEquals(0, $discussions['discussions'][1]['messagetrust']); 2308 $this->assertEquals($cleantext, $discussions['discussions'][1]['message']); 2309 2310 // Get posts now. 2311 $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'DESC'); 2312 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2313 // Admin message is fully trusted. 2314 $this->assertEquals($dangeroustext, $posts['posts'][0]['message']); 2315 2316 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'ASC'); 2317 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2318 // Student message is not trusted. 2319 $this->assertEquals($cleantext, $posts['posts'][0]['message']); 2320 } 2321 2322 /** 2323 * Test trusted text disabled. 2324 */ 2325 public function test_trusted_text_disabled() { 2326 global $USER, $CFG; 2327 2328 $this->resetAfterTest(true); 2329 $CFG->enabletrusttext = 0; 2330 2331 $dangeroustext = '<button>Untrusted text</button>'; 2332 $cleantext = 'Untrusted text'; 2333 2334 // Create courses to add the modules. 2335 $course = self::getDataGenerator()->create_course(); 2336 $user1 = self::getDataGenerator()->create_user(); 2337 2338 // First forum with tracking off. 2339 $record = new \stdClass(); 2340 $record->course = $course->id; 2341 $record->type = 'qanda'; 2342 $forum = self::getDataGenerator()->create_module('forum', $record); 2343 $context = \context_module::instance($forum->cmid); 2344 2345 // Add discussions to the forums. 2346 $discussionrecord = new \stdClass(); 2347 $discussionrecord->course = $course->id; 2348 $discussionrecord->userid = $user1->id; 2349 $discussionrecord->forum = $forum->id; 2350 $discussionrecord->message = $dangeroustext; 2351 $discussionrecord->messagetrust = trusttext_trusted($context); 2352 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord); 2353 2354 self::setAdminUser(); 2355 $discussionrecord->userid = $USER->id; 2356 $discussionrecord->messagetrust = trusttext_trusted($context); 2357 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord); 2358 2359 $discussions = mod_forum_external::get_forum_discussions($forum->id); 2360 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions); 2361 2362 $this->assertCount(2, $discussions['discussions']); 2363 $this->assertCount(0, $discussions['warnings']); 2364 // Admin message is not trusted because enabletrusttext is disabled. 2365 $this->assertEquals(0, $discussions['discussions'][0]['messagetrust']); 2366 $this->assertEquals($cleantext, $discussions['discussions'][0]['message']); 2367 // Student message is not trusted. 2368 $this->assertEquals(0, $discussions['discussions'][1]['messagetrust']); 2369 $this->assertEquals($cleantext, $discussions['discussions'][1]['message']); 2370 2371 // Get posts now. 2372 $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'ASC'); 2373 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2374 // Admin message is not trusted because enabletrusttext is disabled. 2375 $this->assertEquals($cleantext, $posts['posts'][0]['message']); 2376 2377 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'ASC'); 2378 $posts = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts); 2379 // Student message is not trusted. 2380 $this->assertEquals($cleantext, $posts['posts'][0]['message']); 2381 } 2382 2383 /** 2384 * Test delete a discussion. 2385 */ 2386 public function test_delete_post_discussion() { 2387 global $DB; 2388 $this->resetAfterTest(true); 2389 2390 // Setup test data. 2391 $course = $this->getDataGenerator()->create_course(); 2392 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 2393 $user = $this->getDataGenerator()->create_user(); 2394 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 2395 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 2396 2397 // Add a discussion. 2398 $record = new \stdClass(); 2399 $record->course = $course->id; 2400 $record->userid = $user->id; 2401 $record->forum = $forum->id; 2402 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 2403 2404 $this->setUser($user); 2405 $result = mod_forum_external::delete_post($discussion->firstpost); 2406 $result = \external_api::clean_returnvalue(mod_forum_external::delete_post_returns(), $result); 2407 $this->assertTrue($result['status']); 2408 $this->assertEquals(0, $DB->count_records('forum_posts', array('id' => $discussion->firstpost))); 2409 $this->assertEquals(0, $DB->count_records('forum_discussions', array('id' => $discussion->id))); 2410 } 2411 2412 /** 2413 * Test delete a post. 2414 */ 2415 public function test_delete_post_post() { 2416 global $DB; 2417 $this->resetAfterTest(true); 2418 2419 // Setup test data. 2420 $course = $this->getDataGenerator()->create_course(); 2421 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 2422 $user = $this->getDataGenerator()->create_user(); 2423 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 2424 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 2425 2426 // Add a discussion. 2427 $record = new \stdClass(); 2428 $record->course = $course->id; 2429 $record->userid = $user->id; 2430 $record->forum = $forum->id; 2431 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 2432 $parentpost = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); 2433 2434 // Add a post. 2435 $record = new \stdClass(); 2436 $record->course = $course->id; 2437 $record->userid = $user->id; 2438 $record->forum = $forum->id; 2439 $record->discussion = $discussion->id; 2440 $record->parent = $parentpost->id; 2441 $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 2442 2443 $this->setUser($user); 2444 $result = mod_forum_external::delete_post($post->id); 2445 $result = \external_api::clean_returnvalue(mod_forum_external::delete_post_returns(), $result); 2446 $this->assertTrue($result['status']); 2447 $this->assertEquals(1, $DB->count_records('forum_posts', array('discussion' => $discussion->id))); 2448 $this->assertEquals(1, $DB->count_records('forum_discussions', array('id' => $discussion->id))); 2449 } 2450 2451 /** 2452 * Test delete a different user post. 2453 */ 2454 public function test_delete_post_other_user_post() { 2455 global $DB; 2456 $this->resetAfterTest(true); 2457 2458 // Setup test data. 2459 $course = $this->getDataGenerator()->create_course(); 2460 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 2461 $user = $this->getDataGenerator()->create_user(); 2462 $otheruser = $this->getDataGenerator()->create_user(); 2463 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 2464 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 2465 self::getDataGenerator()->enrol_user($otheruser->id, $course->id, $role->id); 2466 2467 // Add a discussion. 2468 $record = array(); 2469 $record['course'] = $course->id; 2470 $record['forum'] = $forum->id; 2471 $record['userid'] = $user->id; 2472 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 2473 $parentpost = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); 2474 2475 // Add a post. 2476 $record = new \stdClass(); 2477 $record->course = $course->id; 2478 $record->userid = $user->id; 2479 $record->forum = $forum->id; 2480 $record->discussion = $discussion->id; 2481 $record->parent = $parentpost->id; 2482 $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 2483 2484 $this->setUser($otheruser); 2485 $this->expectExceptionMessage(get_string('cannotdeletepost', 'forum')); 2486 mod_forum_external::delete_post($post->id); 2487 } 2488 2489 /* 2490 * Test get forum posts by user id. 2491 */ 2492 public function test_mod_forum_get_discussion_posts_by_userid() { 2493 global $DB; 2494 $this->resetAfterTest(true); 2495 2496 $urlfactory = \mod_forum\local\container::get_url_factory(); 2497 $entityfactory = \mod_forum\local\container::get_entity_factory(); 2498 $vaultfactory = \mod_forum\local\container::get_vault_factory(); 2499 $postvault = $vaultfactory->get_post_vault(); 2500 $legacydatamapper = \mod_forum\local\container::get_legacy_data_mapper_factory(); 2501 $legacypostmapper = $legacydatamapper->get_post_data_mapper(); 2502 2503 // Create course to add the module. 2504 $course1 = self::getDataGenerator()->create_course(); 2505 2506 $user1 = self::getDataGenerator()->create_user(); 2507 $user1entity = $entityfactory->get_author_from_stdClass($user1); 2508 $exporteduser1 = [ 2509 'id' => (int) $user1->id, 2510 'fullname' => fullname($user1), 2511 'groups' => [], 2512 'urls' => [ 2513 'profile' => $urlfactory->get_author_profile_url($user1entity, $course1->id)->out(false), 2514 'profileimage' => $urlfactory->get_author_profile_image_url($user1entity), 2515 ], 2516 'isdeleted' => false, 2517 ]; 2518 // Create a bunch of other users to post. 2519 $user2 = self::getDataGenerator()->create_user(); 2520 $user2entity = $entityfactory->get_author_from_stdClass($user2); 2521 $exporteduser2 = [ 2522 'id' => (int) $user2->id, 2523 'fullname' => fullname($user2), 2524 'groups' => [], 2525 'urls' => [ 2526 'profile' => $urlfactory->get_author_profile_url($user2entity, $course1->id)->out(false), 2527 'profileimage' => $urlfactory->get_author_profile_image_url($user2entity), 2528 ], 2529 'isdeleted' => false, 2530 ]; 2531 $user2->fullname = $exporteduser2['fullname']; 2532 2533 $forumgenerator = self::getDataGenerator()->get_plugin_generator('mod_forum'); 2534 2535 // Set the first created user to the test user. 2536 self::setUser($user1); 2537 2538 // Forum with tracking off. 2539 $record = new \stdClass(); 2540 $record->course = $course1->id; 2541 $forum1 = self::getDataGenerator()->create_module('forum', $record); 2542 $forum1context = \context_module::instance($forum1->cmid); 2543 2544 // Add discussions to the forums. 2545 $time = time(); 2546 $record = new \stdClass(); 2547 $record->course = $course1->id; 2548 $record->userid = $user1->id; 2549 $record->forum = $forum1->id; 2550 $record->timemodified = $time + 100; 2551 $discussion1 = $forumgenerator->create_discussion($record); 2552 $discussion1firstpost = $postvault->get_first_post_for_discussion_ids([$discussion1->id]); 2553 $discussion1firstpost = $discussion1firstpost[$discussion1->firstpost]; 2554 $discussion1firstpostobject = $legacypostmapper->to_legacy_object($discussion1firstpost); 2555 2556 $record = new \stdClass(); 2557 $record->course = $course1->id; 2558 $record->userid = $user1->id; 2559 $record->forum = $forum1->id; 2560 $record->timemodified = $time + 200; 2561 $discussion2 = $forumgenerator->create_discussion($record); 2562 $discussion2firstpost = $postvault->get_first_post_for_discussion_ids([$discussion2->id]); 2563 $discussion2firstpost = $discussion2firstpost[$discussion2->firstpost]; 2564 $discussion2firstpostobject = $legacypostmapper->to_legacy_object($discussion2firstpost); 2565 2566 // Add 1 reply to the discussion 1 from a different user. 2567 $record = new \stdClass(); 2568 $record->discussion = $discussion1->id; 2569 $record->parent = $discussion1->firstpost; 2570 $record->userid = $user2->id; 2571 $discussion1reply1 = $forumgenerator->create_post($record); 2572 $filename = 'shouldbeanimage.jpg'; 2573 // Add a fake inline image to the post. 2574 $filerecordinline = array( 2575 'contextid' => $forum1context->id, 2576 'component' => 'mod_forum', 2577 'filearea' => 'post', 2578 'itemid' => $discussion1reply1->id, 2579 'filepath' => '/', 2580 'filename' => $filename, 2581 ); 2582 $fs = get_file_storage(); 2583 $file1 = $fs->create_file_from_string($filerecordinline, 'image contents (not really)'); 2584 2585 // Add 1 reply to the discussion 2 from a different user. 2586 $record = new \stdClass(); 2587 $record->discussion = $discussion2->id; 2588 $record->parent = $discussion2->firstpost; 2589 $record->userid = $user2->id; 2590 $discussion2reply1 = $forumgenerator->create_post($record); 2591 $filename = 'shouldbeanimage.jpg'; 2592 // Add a fake inline image to the post. 2593 $filerecordinline = array( 2594 'contextid' => $forum1context->id, 2595 'component' => 'mod_forum', 2596 'filearea' => 'post', 2597 'itemid' => $discussion2reply1->id, 2598 'filepath' => '/', 2599 'filename' => $filename, 2600 ); 2601 $fs = get_file_storage(); 2602 $file2 = $fs->create_file_from_string($filerecordinline, 'image contents (not really)'); 2603 2604 // Following line enrol and assign default role id to the user. 2605 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course. 2606 $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher'); 2607 $this->getDataGenerator()->enrol_user($user2->id, $course1->id); 2608 // Changed display period for the discussions in past. 2609 $discussion = new \stdClass(); 2610 $discussion->id = $discussion1->id; 2611 $discussion->timestart = $time - 200; 2612 $discussion->timeend = $time - 100; 2613 $DB->update_record('forum_discussions', $discussion); 2614 $discussion = new \stdClass(); 2615 $discussion->id = $discussion2->id; 2616 $discussion->timestart = $time - 200; 2617 $discussion->timeend = $time - 100; 2618 $DB->update_record('forum_discussions', $discussion); 2619 // Create what we expect to be returned when querying the discussion. 2620 $expectedposts = array( 2621 'discussions' => array(), 2622 'warnings' => array(), 2623 ); 2624 2625 $isolatedurluser = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion); 2626 $isolatedurluser->params(['parent' => $discussion1reply1->id]); 2627 $isolatedurlparent = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1firstpostobject->discussion); 2628 $isolatedurlparent->params(['parent' => $discussion1firstpostobject->id]); 2629 2630 $expectedposts['discussions'][0] = [ 2631 'name' => $discussion1->name, 2632 'id' => $discussion1->id, 2633 'timecreated' => $discussion1firstpost->get_time_created(), 2634 'authorfullname' => $user1entity->get_full_name(), 2635 'posts' => [ 2636 'userposts' => [ 2637 [ 2638 'id' => $discussion1reply1->id, 2639 'discussionid' => $discussion1reply1->discussion, 2640 'parentid' => $discussion1reply1->parent, 2641 'hasparent' => true, 2642 'timecreated' => $discussion1reply1->created, 2643 'timemodified' => $discussion1reply1->modified, 2644 'subject' => $discussion1reply1->subject, 2645 'replysubject' => get_string('re', 'mod_forum') . " {$discussion1reply1->subject}", 2646 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php', 2647 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id), 2648 'messageformat' => 1, // This value is usually changed by external_format_text() function. 2649 'unread' => null, 2650 'isdeleted' => false, 2651 'isprivatereply' => false, 2652 'haswordcount' => false, 2653 'wordcount' => null, 2654 'author' => $exporteduser2, 2655 'attachments' => [], 2656 'messageinlinefiles' => [], 2657 'tags' => [], 2658 'html' => [ 2659 'rating' => null, 2660 'taglist' => null, 2661 'authorsubheading' => $forumgenerator->get_author_subheading_html( 2662 (object)$exporteduser2, $discussion1reply1->created) 2663 ], 2664 'charcount' => null, 2665 'capabilities' => [ 2666 'view' => true, 2667 'edit' => true, 2668 'delete' => true, 2669 'split' => true, 2670 'reply' => true, 2671 'export' => false, 2672 'controlreadstatus' => false, 2673 'canreplyprivately' => true, 2674 'selfenrol' => false 2675 ], 2676 'urls' => [ 2677 'view' => $urlfactory->get_view_post_url_from_post_id( 2678 $discussion1reply1->discussion, $discussion1reply1->id)->out(false), 2679 'viewisolated' => $isolatedurluser->out(false), 2680 'viewparent' => $urlfactory->get_view_post_url_from_post_id( 2681 $discussion1reply1->discussion, $discussion1reply1->parent)->out(false), 2682 'edit' => (new \moodle_url('/mod/forum/post.php', [ 2683 'edit' => $discussion1reply1->id 2684 ]))->out(false), 2685 'delete' => (new \moodle_url('/mod/forum/post.php', [ 2686 'delete' => $discussion1reply1->id 2687 ]))->out(false), 2688 'split' => (new \moodle_url('/mod/forum/post.php', [ 2689 'prune' => $discussion1reply1->id 2690 ]))->out(false), 2691 'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [ 2692 'reply' => $discussion1reply1->id 2693 ]))->out(false), 2694 'export' => null, 2695 'markasread' => null, 2696 'markasunread' => null, 2697 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id( 2698 $discussion1reply1->discussion)->out(false), 2699 ], 2700 ] 2701 ], 2702 'parentposts' => [ 2703 [ 2704 'id' => $discussion1firstpostobject->id, 2705 'discussionid' => $discussion1firstpostobject->discussion, 2706 'parentid' => null, 2707 'hasparent' => false, 2708 'timecreated' => $discussion1firstpostobject->created, 2709 'timemodified' => $discussion1firstpostobject->modified, 2710 'subject' => $discussion1firstpostobject->subject, 2711 'replysubject' => get_string('re', 'mod_forum') . " {$discussion1firstpostobject->subject}", 2712 'message' => file_rewrite_pluginfile_urls($discussion1firstpostobject->message, 'pluginfile.php', 2713 $forum1context->id, 'mod_forum', 'post', $discussion1firstpostobject->id), 2714 'messageformat' => 1, // This value is usually changed by external_format_text() function. 2715 'unread' => null, 2716 'isdeleted' => false, 2717 'isprivatereply' => false, 2718 'haswordcount' => false, 2719 'wordcount' => null, 2720 'author' => $exporteduser1, 2721 'attachments' => [], 2722 'messageinlinefiles' => [], 2723 'tags' => [], 2724 'html' => [ 2725 'rating' => null, 2726 'taglist' => null, 2727 'authorsubheading' => $forumgenerator->get_author_subheading_html( 2728 (object)$exporteduser1, $discussion1firstpostobject->created) 2729 ], 2730 'charcount' => null, 2731 'capabilities' => [ 2732 'view' => true, 2733 'edit' => true, 2734 'delete' => true, 2735 'split' => false, 2736 'reply' => true, 2737 'export' => false, 2738 'controlreadstatus' => false, 2739 'canreplyprivately' => true, 2740 'selfenrol' => false 2741 ], 2742 'urls' => [ 2743 'view' => $urlfactory->get_view_post_url_from_post_id( 2744 $discussion1firstpostobject->discussion, $discussion1firstpostobject->id)->out(false), 2745 'viewisolated' => $isolatedurlparent->out(false), 2746 'viewparent' => null, 2747 'edit' => (new \moodle_url('/mod/forum/post.php', [ 2748 'edit' => $discussion1firstpostobject->id 2749 ]))->out(false), 2750 'delete' => (new \moodle_url('/mod/forum/post.php', [ 2751 'delete' => $discussion1firstpostobject->id 2752 ]))->out(false), 2753 'split' => null, 2754 'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [ 2755 'reply' => $discussion1firstpostobject->id 2756 ]))->out(false), 2757 'export' => null, 2758 'markasread' => null, 2759 'markasunread' => null, 2760 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id( 2761 $discussion1firstpostobject->discussion)->out(false), 2762 ], 2763 ] 2764 ], 2765 ], 2766 ]; 2767 2768 $isolatedurluser = $urlfactory->get_discussion_view_url_from_discussion_id($discussion2reply1->discussion); 2769 $isolatedurluser->params(['parent' => $discussion2reply1->id]); 2770 $isolatedurlparent = $urlfactory->get_discussion_view_url_from_discussion_id($discussion2firstpostobject->discussion); 2771 $isolatedurlparent->params(['parent' => $discussion2firstpostobject->id]); 2772 2773 $expectedposts['discussions'][1] = [ 2774 'name' => $discussion2->name, 2775 'id' => $discussion2->id, 2776 'timecreated' => $discussion2firstpost->get_time_created(), 2777 'authorfullname' => $user1entity->get_full_name(), 2778 'posts' => [ 2779 'userposts' => [ 2780 [ 2781 'id' => $discussion2reply1->id, 2782 'discussionid' => $discussion2reply1->discussion, 2783 'parentid' => $discussion2reply1->parent, 2784 'hasparent' => true, 2785 'timecreated' => $discussion2reply1->created, 2786 'timemodified' => $discussion2reply1->modified, 2787 'subject' => $discussion2reply1->subject, 2788 'replysubject' => get_string('re', 'mod_forum') . " {$discussion2reply1->subject}", 2789 'message' => file_rewrite_pluginfile_urls($discussion2reply1->message, 'pluginfile.php', 2790 $forum1context->id, 'mod_forum', 'post', $discussion2reply1->id), 2791 'messageformat' => 1, // This value is usually changed by external_format_text() function. 2792 'unread' => null, 2793 'isdeleted' => false, 2794 'isprivatereply' => false, 2795 'haswordcount' => false, 2796 'wordcount' => null, 2797 'author' => $exporteduser2, 2798 'attachments' => [], 2799 'messageinlinefiles' => [], 2800 'tags' => [], 2801 'html' => [ 2802 'rating' => null, 2803 'taglist' => null, 2804 'authorsubheading' => $forumgenerator->get_author_subheading_html( 2805 (object)$exporteduser2, $discussion2reply1->created) 2806 ], 2807 'charcount' => null, 2808 'capabilities' => [ 2809 'view' => true, 2810 'edit' => true, 2811 'delete' => true, 2812 'split' => true, 2813 'reply' => true, 2814 'export' => false, 2815 'controlreadstatus' => false, 2816 'canreplyprivately' => true, 2817 'selfenrol' => false 2818 ], 2819 'urls' => [ 2820 'view' => $urlfactory->get_view_post_url_from_post_id( 2821 $discussion2reply1->discussion, $discussion2reply1->id)->out(false), 2822 'viewisolated' => $isolatedurluser->out(false), 2823 'viewparent' => $urlfactory->get_view_post_url_from_post_id( 2824 $discussion2reply1->discussion, $discussion2reply1->parent)->out(false), 2825 'edit' => (new \moodle_url('/mod/forum/post.php', [ 2826 'edit' => $discussion2reply1->id 2827 ]))->out(false), 2828 'delete' => (new \moodle_url('/mod/forum/post.php', [ 2829 'delete' => $discussion2reply1->id 2830 ]))->out(false), 2831 'split' => (new \moodle_url('/mod/forum/post.php', [ 2832 'prune' => $discussion2reply1->id 2833 ]))->out(false), 2834 'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [ 2835 'reply' => $discussion2reply1->id 2836 ]))->out(false), 2837 'export' => null, 2838 'markasread' => null, 2839 'markasunread' => null, 2840 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id( 2841 $discussion2reply1->discussion)->out(false), 2842 ], 2843 ] 2844 ], 2845 'parentposts' => [ 2846 [ 2847 'id' => $discussion2firstpostobject->id, 2848 'discussionid' => $discussion2firstpostobject->discussion, 2849 'parentid' => null, 2850 'hasparent' => false, 2851 'timecreated' => $discussion2firstpostobject->created, 2852 'timemodified' => $discussion2firstpostobject->modified, 2853 'subject' => $discussion2firstpostobject->subject, 2854 'replysubject' => get_string('re', 'mod_forum') . " {$discussion2firstpostobject->subject}", 2855 'message' => file_rewrite_pluginfile_urls($discussion2firstpostobject->message, 'pluginfile.php', 2856 $forum1context->id, 'mod_forum', 'post', $discussion2firstpostobject->id), 2857 'messageformat' => 1, // This value is usually changed by external_format_text() function. 2858 'unread' => null, 2859 'isdeleted' => false, 2860 'isprivatereply' => false, 2861 'haswordcount' => false, 2862 'wordcount' => null, 2863 'author' => $exporteduser1, 2864 'attachments' => [], 2865 'messageinlinefiles' => [], 2866 'tags' => [], 2867 'html' => [ 2868 'rating' => null, 2869 'taglist' => null, 2870 'authorsubheading' => $forumgenerator->get_author_subheading_html( 2871 (object)$exporteduser1, $discussion2firstpostobject->created) 2872 ], 2873 'charcount' => null, 2874 'capabilities' => [ 2875 'view' => true, 2876 'edit' => true, 2877 'delete' => true, 2878 'split' => false, 2879 'reply' => true, 2880 'export' => false, 2881 'controlreadstatus' => false, 2882 'canreplyprivately' => true, 2883 'selfenrol' => false 2884 ], 2885 'urls' => [ 2886 'view' => $urlfactory->get_view_post_url_from_post_id( 2887 $discussion2firstpostobject->discussion, $discussion2firstpostobject->id)->out(false), 2888 'viewisolated' => $isolatedurlparent->out(false), 2889 'viewparent' => null, 2890 'edit' => (new \moodle_url('/mod/forum/post.php', [ 2891 'edit' => $discussion2firstpostobject->id 2892 ]))->out(false), 2893 'delete' => (new \moodle_url('/mod/forum/post.php', [ 2894 'delete' => $discussion2firstpostobject->id 2895 ]))->out(false), 2896 'split' => null, 2897 'reply' => (new \moodle_url('/mod/forum/post.php#mformforum', [ 2898 'reply' => $discussion2firstpostobject->id 2899 ]))->out(false), 2900 'export' => null, 2901 'markasread' => null, 2902 'markasunread' => null, 2903 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id( 2904 $discussion2firstpostobject->discussion)->out(false), 2905 2906 ] 2907 ], 2908 ] 2909 ], 2910 ]; 2911 2912 // Test discussions with one additional post each (total 2 posts). 2913 // Also testing that we get the parent posts too. 2914 $discussions = mod_forum_external::get_discussion_posts_by_userid($user2->id, $forum1->cmid, 'modified', 'DESC'); 2915 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_by_userid_returns(), $discussions); 2916 2917 $this->assertEquals(2, count($discussions['discussions'])); 2918 2919 $this->assertEquals($expectedposts, $discussions); 2920 2921 // When groupmode is SEPARATEGROUPS, even there is no groupid specified, the post not for the user shouldn't be seen. 2922 $group1 = self::getDataGenerator()->create_group(['courseid' => $course1->id]); 2923 $group2 = self::getDataGenerator()->create_group(['courseid' => $course1->id]); 2924 // Update discussion with group. 2925 $discussion = new \stdClass(); 2926 $discussion->id = $discussion1->id; 2927 $discussion->groupid = $group1->id; 2928 $DB->update_record('forum_discussions', $discussion); 2929 $discussion = new \stdClass(); 2930 $discussion->id = $discussion2->id; 2931 $discussion->groupid = $group2->id; 2932 $DB->update_record('forum_discussions', $discussion); 2933 $cm = get_coursemodule_from_id('forum', $forum1->cmid); 2934 $cm->groupmode = SEPARATEGROUPS; 2935 $DB->update_record('course_modules', $cm); 2936 $teacher = self::getDataGenerator()->create_user(); 2937 $role = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST); 2938 self::getDataGenerator()->enrol_user($teacher->id, $course1->id, $role->id); 2939 groups_add_member($group2->id, $teacher->id); 2940 self::setUser($teacher); 2941 $discussions = mod_forum_external::get_discussion_posts_by_userid($user2->id, $forum1->cmid, 'modified', 'DESC'); 2942 $discussions = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_by_userid_returns(), $discussions); 2943 // Discussion is only 1 record (group 2). 2944 $this->assertEquals(1, count($discussions['discussions'])); 2945 $this->assertEquals($expectedposts['discussions'][1], $discussions['discussions'][0]); 2946 } 2947 2948 /** 2949 * Test get_discussion_post a discussion. 2950 */ 2951 public function test_get_discussion_post_discussion() { 2952 global $DB; 2953 $this->resetAfterTest(true); 2954 // Setup test data. 2955 $course = $this->getDataGenerator()->create_course(); 2956 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 2957 $user = $this->getDataGenerator()->create_user(); 2958 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 2959 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 2960 // Add a discussion. 2961 $record = new \stdClass(); 2962 $record->course = $course->id; 2963 $record->userid = $user->id; 2964 $record->forum = $forum->id; 2965 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 2966 $this->setUser($user); 2967 $result = mod_forum_external::get_discussion_post($discussion->firstpost); 2968 $result = \external_api::clean_returnvalue(mod_forum_external::get_discussion_post_returns(), $result); 2969 $this->assertEquals($discussion->firstpost, $result['post']['id']); 2970 $this->assertFalse($result['post']['hasparent']); 2971 $this->assertEquals($discussion->message, $result['post']['message']); 2972 } 2973 2974 /** 2975 * Test get_discussion_post a post. 2976 */ 2977 public function test_get_discussion_post_post() { 2978 global $DB; 2979 $this->resetAfterTest(true); 2980 // Setup test data. 2981 $course = $this->getDataGenerator()->create_course(); 2982 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 2983 $user = $this->getDataGenerator()->create_user(); 2984 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 2985 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 2986 // Add a discussion. 2987 $record = new \stdClass(); 2988 $record->course = $course->id; 2989 $record->userid = $user->id; 2990 $record->forum = $forum->id; 2991 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 2992 $parentpost = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); 2993 // Add a post. 2994 $record = new \stdClass(); 2995 $record->course = $course->id; 2996 $record->userid = $user->id; 2997 $record->forum = $forum->id; 2998 $record->discussion = $discussion->id; 2999 $record->parent = $parentpost->id; 3000 $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 3001 $this->setUser($user); 3002 $result = mod_forum_external::get_discussion_post($post->id); 3003 $result = \external_api::clean_returnvalue(mod_forum_external::get_discussion_post_returns(), $result); 3004 $this->assertEquals($post->id, $result['post']['id']); 3005 $this->assertTrue($result['post']['hasparent']); 3006 $this->assertEquals($post->message, $result['post']['message']); 3007 } 3008 3009 /** 3010 * Test get_discussion_post a different user post. 3011 */ 3012 public function test_get_discussion_post_other_user_post() { 3013 global $DB; 3014 $this->resetAfterTest(true); 3015 // Setup test data. 3016 $course = $this->getDataGenerator()->create_course(); 3017 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 3018 $user = $this->getDataGenerator()->create_user(); 3019 $otheruser = $this->getDataGenerator()->create_user(); 3020 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 3021 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 3022 self::getDataGenerator()->enrol_user($otheruser->id, $course->id, $role->id); 3023 // Add a discussion. 3024 $record = array(); 3025 $record['course'] = $course->id; 3026 $record['forum'] = $forum->id; 3027 $record['userid'] = $user->id; 3028 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 3029 $parentpost = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); 3030 // Add a post. 3031 $record = new \stdClass(); 3032 $record->course = $course->id; 3033 $record->userid = $user->id; 3034 $record->forum = $forum->id; 3035 $record->discussion = $discussion->id; 3036 $record->parent = $parentpost->id; 3037 $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 3038 // Check other user post. 3039 $this->setUser($otheruser); 3040 $result = mod_forum_external::get_discussion_post($post->id); 3041 $result = \external_api::clean_returnvalue(mod_forum_external::get_discussion_post_returns(), $result); 3042 $this->assertEquals($post->id, $result['post']['id']); 3043 $this->assertTrue($result['post']['hasparent']); 3044 $this->assertEquals($post->message, $result['post']['message']); 3045 } 3046 3047 /** 3048 * Test prepare_draft_area_for_post a different user post. 3049 */ 3050 public function test_prepare_draft_area_for_post() { 3051 global $DB; 3052 $this->resetAfterTest(true); 3053 // Setup test data. 3054 $course = $this->getDataGenerator()->create_course(); 3055 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 3056 $user = $this->getDataGenerator()->create_user(); 3057 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 3058 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 3059 // Add a discussion. 3060 $record = array(); 3061 $record['course'] = $course->id; 3062 $record['forum'] = $forum->id; 3063 $record['userid'] = $user->id; 3064 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 3065 $parentpost = $DB->get_record('forum_posts', array('discussion' => $discussion->id)); 3066 // Add a post. 3067 $record = new \stdClass(); 3068 $record->course = $course->id; 3069 $record->userid = $user->id; 3070 $record->forum = $forum->id; 3071 $record->discussion = $discussion->id; 3072 $record->parent = $parentpost->id; 3073 $post = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 3074 3075 // Add some files only in the attachment area. 3076 $filename = 'faketxt.txt'; 3077 $filerecordinline = array( 3078 'contextid' => \context_module::instance($forum->cmid)->id, 3079 'component' => 'mod_forum', 3080 'filearea' => 'attachment', 3081 'itemid' => $post->id, 3082 'filepath' => '/', 3083 'filename' => $filename, 3084 ); 3085 $fs = get_file_storage(); 3086 $fs->create_file_from_string($filerecordinline, 'fake txt contents 1.'); 3087 $filerecordinline['filename'] = 'otherfaketxt.txt'; 3088 $fs->create_file_from_string($filerecordinline, 'fake txt contents 2.'); 3089 3090 $this->setUser($user); 3091 3092 // Check attachment area. 3093 $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'attachment'); 3094 $result = \external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); 3095 $this->assertCount(2, $result['files']); 3096 $this->assertEquals($filename, $result['files'][0]['filename']); 3097 $this->assertCount(5, $result['areaoptions']); 3098 $this->assertEmpty($result['messagetext']); 3099 3100 // Check again using existing draft item id. 3101 $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'attachment', $result['draftitemid']); 3102 $result = \external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); 3103 $this->assertCount(2, $result['files']); 3104 3105 // Keep only certain files in the area. 3106 $filestokeep = array(array('filename' => $filename, 'filepath' => '/')); 3107 $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'attachment', $result['draftitemid'], $filestokeep); 3108 $result = \external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); 3109 $this->assertCount(1, $result['files']); 3110 $this->assertEquals($filename, $result['files'][0]['filename']); 3111 3112 // Check editor (post) area. 3113 $filerecordinline['filearea'] = 'post'; 3114 $filerecordinline['filename'] = 'fakeimage.png'; 3115 $fs->create_file_from_string($filerecordinline, 'fake image.'); 3116 $result = mod_forum_external::prepare_draft_area_for_post($post->id, 'post'); 3117 $result = \external_api::clean_returnvalue(mod_forum_external::prepare_draft_area_for_post_returns(), $result); 3118 $this->assertCount(1, $result['files']); 3119 $this->assertEquals($filerecordinline['filename'], $result['files'][0]['filename']); 3120 $this->assertCount(5, $result['areaoptions']); 3121 $this->assertEquals($post->message, $result['messagetext']); 3122 } 3123 3124 /** 3125 * Test update_discussion_post with a discussion. 3126 */ 3127 public function test_update_discussion_post_discussion() { 3128 global $DB, $USER; 3129 $this->resetAfterTest(true); 3130 // Setup test data. 3131 $course = $this->getDataGenerator()->create_course(); 3132 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 3133 3134 $this->setAdminUser(); 3135 3136 // Add a discussion. 3137 $record = new \stdClass(); 3138 $record->course = $course->id; 3139 $record->userid = $USER->id; 3140 $record->forum = $forum->id; 3141 $record->pinned = FORUM_DISCUSSION_UNPINNED; 3142 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 3143 3144 $subject = 'Hey subject updated'; 3145 $message = 'Hey message updated'; 3146 $messageformat = FORMAT_HTML; 3147 $options = [ 3148 ['name' => 'pinned', 'value' => true], 3149 ]; 3150 3151 $result = mod_forum_external::update_discussion_post($discussion->firstpost, $subject, $message, $messageformat, 3152 $options); 3153 $result = \external_api::clean_returnvalue(mod_forum_external::update_discussion_post_returns(), $result); 3154 $this->assertTrue($result['status']); 3155 3156 // Get the post from WS. 3157 $result = mod_forum_external::get_discussion_post($discussion->firstpost); 3158 $result = \external_api::clean_returnvalue(mod_forum_external::get_discussion_post_returns(), $result); 3159 $this->assertEquals($subject, $result['post']['subject']); 3160 $this->assertEquals($message, $result['post']['message']); 3161 $this->assertEquals($messageformat, $result['post']['messageformat']); 3162 3163 // Get discussion object from DB. 3164 $discussion = $DB->get_record('forum_discussions', ['id' => $discussion->id]); 3165 $this->assertEquals($subject, $discussion->name); // Check discussion subject. 3166 $this->assertEquals(FORUM_DISCUSSION_PINNED, $discussion->pinned); // Check discussion pinned. 3167 } 3168 3169 /** 3170 * Test update_discussion_post with a post. 3171 */ 3172 public function test_update_discussion_post_post() { 3173 global $DB, $USER; 3174 $this->resetAfterTest(true); 3175 // Setup test data. 3176 $course = $this->getDataGenerator()->create_course(); 3177 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 3178 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST); 3179 $user = $this->getDataGenerator()->create_user(); 3180 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 3181 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 3182 3183 $this->setUser($user); 3184 // Enable auto subscribe discussion. 3185 $USER->autosubscribe = true; 3186 3187 // Add a discussion. 3188 $record = new \stdClass(); 3189 $record->course = $course->id; 3190 $record->userid = $user->id; 3191 $record->forum = $forum->id; 3192 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 3193 3194 // Add a post via WS (so discussion subscription works). 3195 $result = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...'); 3196 $newpost = $result['post']; 3197 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion->id, $cm)); 3198 3199 // Test inline and regular attachment in post 3200 // Create a file in a draft area for inline attachments. 3201 $draftidinlineattach = file_get_unused_draft_itemid(); 3202 $draftidattach = file_get_unused_draft_itemid(); 3203 self::setUser($user); 3204 $usercontext = \context_user::instance($user->id); 3205 $filepath = '/'; 3206 $filearea = 'draft'; 3207 $component = 'user'; 3208 $filenameimg = 'fakeimage.png'; 3209 $filerecordinline = array( 3210 'contextid' => $usercontext->id, 3211 'component' => $component, 3212 'filearea' => $filearea, 3213 'itemid' => $draftidinlineattach, 3214 'filepath' => $filepath, 3215 'filename' => $filenameimg, 3216 ); 3217 $fs = get_file_storage(); 3218 3219 // Create a file in a draft area for regular attachments. 3220 $filerecordattach = $filerecordinline; 3221 $attachfilename = 'faketxt.txt'; 3222 $filerecordattach['filename'] = $attachfilename; 3223 $filerecordattach['itemid'] = $draftidattach; 3224 $fs->create_file_from_string($filerecordinline, 'image contents (not really)'); 3225 $fs->create_file_from_string($filerecordattach, 'simple text attachment'); 3226 3227 // Do not update subject. 3228 $message = 'Hey message updated'; 3229 $messageformat = FORMAT_HTML; 3230 $options = [ 3231 ['name' => 'discussionsubscribe', 'value' => false], 3232 ['name' => 'inlineattachmentsid', 'value' => $draftidinlineattach], 3233 ['name' => 'attachmentsid', 'value' => $draftidattach], 3234 ]; 3235 3236 $result = mod_forum_external::update_discussion_post($newpost->id, '', $message, $messageformat, $options); 3237 $result = \external_api::clean_returnvalue(mod_forum_external::update_discussion_post_returns(), $result); 3238 $this->assertTrue($result['status']); 3239 // Check subscription status. 3240 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion->id, $cm)); 3241 3242 // Get the post from WS. 3243 $result = mod_forum_external::get_discussion_posts($discussion->id, 'modified', 'DESC', true); 3244 $result = \external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $result); 3245 $found = false; 3246 foreach ($result['posts'] as $post) { 3247 if ($post['id'] == $newpost->id) { 3248 $this->assertEquals($newpost->subject, $post['subject']); 3249 $this->assertEquals($message, $post['message']); 3250 $this->assertEquals($messageformat, $post['messageformat']); 3251 $this->assertCount(1, $post['messageinlinefiles']); 3252 $this->assertEquals('fakeimage.png', $post['messageinlinefiles'][0]['filename']); 3253 $this->assertCount(1, $post['attachments']); 3254 $this->assertEquals('faketxt.txt', $post['attachments'][0]['filename']); 3255 $found = true; 3256 } 3257 } 3258 $this->assertTrue($found); 3259 } 3260 3261 /** 3262 * Test update_discussion_post with other user post (no permissions). 3263 */ 3264 public function test_update_discussion_post_other_user_post() { 3265 global $DB, $USER; 3266 $this->resetAfterTest(true); 3267 // Setup test data. 3268 $course = $this->getDataGenerator()->create_course(); 3269 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 3270 $user = $this->getDataGenerator()->create_user(); 3271 $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 3272 self::getDataGenerator()->enrol_user($user->id, $course->id, $role->id); 3273 3274 $this->setAdminUser(); 3275 // Add a discussion. 3276 $record = new \stdClass(); 3277 $record->course = $course->id; 3278 $record->userid = $USER->id; 3279 $record->forum = $forum->id; 3280 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record); 3281 3282 // Add a post. 3283 $record = new \stdClass(); 3284 $record->course = $course->id; 3285 $record->userid = $USER->id; 3286 $record->forum = $forum->id; 3287 $record->discussion = $discussion->id; 3288 $record->parent = $discussion->firstpost; 3289 $newpost = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record); 3290 3291 $this->setUser($user); 3292 $subject = 'Hey subject updated'; 3293 $message = 'Hey message updated'; 3294 $messageformat = FORMAT_HTML; 3295 3296 $this->expectExceptionMessage(get_string('cannotupdatepost', 'forum')); 3297 mod_forum_external::update_discussion_post($newpost->id, $subject, $message, $messageformat); 3298 } 3299 3300 /** 3301 * Test that we can update the subject of a post to the string '0' 3302 */ 3303 public function test_update_discussion_post_set_subject_to_zero(): void { 3304 global $DB, $USER; 3305 3306 $this->resetAfterTest(true); 3307 $this->setAdminUser(); 3308 3309 // Setup test data. 3310 $course = $this->getDataGenerator()->create_course(); 3311 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 3312 3313 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion((object) [ 3314 'userid' => $USER->id, 3315 'course' => $course->id, 3316 'forum' => $forum->id, 3317 'name' => 'Test discussion subject', 3318 ]); 3319 3320 // Update discussion post subject. 3321 $result = \external_api::clean_returnvalue( 3322 mod_forum_external::update_discussion_post_returns(), 3323 mod_forum_external::update_discussion_post($discussion->firstpost, '0') 3324 ); 3325 $this->assertTrue($result['status']); 3326 3327 // Get updated discussion post subject from DB. 3328 $postsubject = $DB->get_field('forum_posts', 'subject', ['id' => $discussion->firstpost]); 3329 $this->assertEquals('0', $postsubject); 3330 } 3331 3332 /** 3333 * Test that we can update the message of a post to the string '0' 3334 */ 3335 public function test_update_discussion_post_set_message_to_zero(): void { 3336 global $DB, $USER; 3337 3338 $this->resetAfterTest(true); 3339 $this->setAdminUser(); 3340 3341 // Setup test data. 3342 $course = $this->getDataGenerator()->create_course(); 3343 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 3344 3345 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion((object) [ 3346 'userid' => $USER->id, 3347 'course' => $course->id, 3348 'forum' => $forum->id, 3349 'message' => 'Test discussion message', 3350 'messageformat' => FORMAT_HTML, 3351 ]); 3352 3353 // Update discussion post message. 3354 $result = \external_api::clean_returnvalue( 3355 mod_forum_external::update_discussion_post_returns(), 3356 mod_forum_external::update_discussion_post($discussion->firstpost, '', '0', FORMAT_HTML) 3357 ); 3358 $this->assertTrue($result['status']); 3359 3360 // Get updated discussion post subject from DB. 3361 $postmessage = $DB->get_field('forum_posts', 'message', ['id' => $discussion->firstpost]); 3362 $this->assertEquals('0', $postmessage); 3363 } 3364 3365 /** 3366 * Test that we can update the message format of a post to {@see FORMAT_MOODLE} 3367 */ 3368 public function test_update_discussion_post_set_message_format_moodle(): void { 3369 global $DB, $USER; 3370 3371 $this->resetAfterTest(true); 3372 $this->setAdminUser(); 3373 3374 // Setup test data. 3375 $course = $this->getDataGenerator()->create_course(); 3376 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 3377 3378 $discussion = $this->getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion((object) [ 3379 'userid' => $USER->id, 3380 'course' => $course->id, 3381 'forum' => $forum->id, 3382 'message' => 'Test discussion message', 3383 'messageformat' => FORMAT_HTML, 3384 ]); 3385 3386 // Update discussion post message & messageformat. 3387 $result = \external_api::clean_returnvalue( 3388 mod_forum_external::update_discussion_post_returns(), 3389 mod_forum_external::update_discussion_post($discussion->firstpost, '', 'Update discussion message', FORMAT_MOODLE) 3390 ); 3391 $this->assertTrue($result['status']); 3392 3393 // Get updated discussion post from DB. 3394 $updatedpost = $DB->get_record('forum_posts', ['id' => $discussion->firstpost], 'message,messageformat'); 3395 $this->assertEquals((object) [ 3396 'message' => 'Update discussion message', 3397 'messageformat' => FORMAT_MOODLE, 3398 ], $updatedpost); 3399 } 3400 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body