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