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