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