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