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