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