2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * The module forums external functions unit tests
22 * @copyright 2012 Mark Nelson <markn@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
30 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
31 require_once($CFG->dirroot . '/mod/forum/lib.php');
33 class mod_forum_external_testcase extends externallib_advanced_testcase {
38 protected function setUp() {
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();
45 require_once($CFG->dirroot . '/mod/forum/externallib.php');
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();
57 public function test_mod_forum_get_forums_by_courses() {
58 global $USER, $CFG, $DB;
60 $this->resetAfterTest(true);
63 $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
68 // Create courses to add the modules.
69 $course1 = self::getDataGenerator()->create_course();
70 $course2 = self::getDataGenerator()->create_course();
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);
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 = [];
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 = [];
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;
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)));
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;
128 $enrol->enrol_user($instance2, $user->id);
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);
136 // Create what we expect to be returned when querying the two courses.
137 unset($forum1->displaywordcount);
138 unset($forum2->displaywordcount);
140 $expectedforums = array();
141 $expectedforums[$forum1->id] = (array) $forum1;
142 $expectedforums[$forum2->id] = (array) $forum2;
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);
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);
160 // Unenrol user from second course and alter expected forums.
161 $enrol->unenrol_user($instance2, $user->id);
162 unset($expectedforums[$forum2->id]);
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']);
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']);
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);
184 * Test get forum posts
186 public function test_mod_forum_get_forum_discussion_posts() {
189 $this->resetAfterTest(true);
191 // Set the CFG variable to allow track forums.
192 $CFG->forum_trackreadposts = true;
194 // Create a user who can track forums.
195 $record = new stdClass();
196 $record->trackforums = true;
197 $user1 = self::getDataGenerator()->create_user($record);
198 // Create a bunch of other users to post.
199 $user2 = self::getDataGenerator()->create_user();
200 $user3 = self::getDataGenerator()->create_user();
202 // Set the first created user to the test user.
203 self::setUser($user1);
205 // Create course to add the module.
206 $course1 = self::getDataGenerator()->create_course();
208 // Forum with tracking off.
209 $record = new stdClass();
210 $record->course = $course1->id;
211 $record->trackingtype = FORUM_TRACKING_OFF;
212 $forum1 = self::getDataGenerator()->create_module('forum', $record);
213 $forum1context = context_module::instance($forum1->cmid);
215 // Forum with tracking enabled.
216 $record = new stdClass();
217 $record->course = $course1->id;
218 $forum2 = self::getDataGenerator()->create_module('forum', $record);
219 $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
220 $forum2context = context_module::instance($forum2->cmid);
222 // Add discussions to the forums.
223 $record = new stdClass();
224 $record->course = $course1->id;
225 $record->userid = $user1->id;
226 $record->forum = $forum1->id;
227 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
229 $record = new stdClass();
230 $record->course = $course1->id;
231 $record->userid = $user2->id;
232 $record->forum = $forum1->id;
233 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
235 $record = new stdClass();
236 $record->course = $course1->id;
237 $record->userid = $user2->id;
238 $record->forum = $forum2->id;
239 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
241 // Add 2 replies to the discussion 1 from different users.
242 $record = new stdClass();
243 $record->discussion = $discussion1->id;
244 $record->parent = $discussion1->firstpost;
245 $record->userid = $user2->id;
246 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
247 $filename = 'shouldbeanimage.jpg';
248 // Add a fake inline image to the post.
249 $filerecordinline = array(
250 'contextid' => $forum1context->id,
251 'component' => 'mod_forum',
252 'filearea' => 'post',
253 'itemid' => $discussion1reply1->id,
255 'filename' => $filename,
257 $fs = get_file_storage();
259 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
261 $record->parent = $discussion1reply1->id;
262 $record->userid = $user3->id;
263 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
265 // Enrol the user in the course.
266 $enrol = enrol_get_plugin('manual');
267 // Following line enrol and assign default role id to the user.
268 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
269 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
270 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
272 // Delete one user, to test that we still receive posts by this user.
275 // Create what we expect to be returned when querying the discussion.
276 $expectedposts = array(
278 'ratinginfo' => array(
279 'contextid' => $forum1context->id,
280 'component' => 'mod_forum',
281 'ratingarea' => 'post',
282 'canviewall' => null,
283 'canviewany' => null,
285 'ratings' => array(),
287 'warnings' => array(),
290 // User pictures are initially empty, we should get the links once the external function is called.
291 $expectedposts['posts'][] = array(
292 'id' => $discussion1reply2->id,
293 'discussion' => $discussion1reply2->discussion,
294 'parent' => $discussion1reply2->parent,
295 'userid' => (int) $discussion1reply2->userid,
296 'created' => $discussion1reply2->created,
297 'modified' => $discussion1reply2->modified,
298 'mailed' => $discussion1reply2->mailed,
299 'subject' => $discussion1reply2->subject,
300 'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
301 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
302 'messageformat' => 1, // This value is usually changed by external_format_text() function.
303 'messagetrust' => $discussion1reply2->messagetrust,
304 'attachment' => $discussion1reply2->attachment,
305 'totalscore' => $discussion1reply2->totalscore,
306 'mailnow' => $discussion1reply2->mailnow,
307 'children' => array(),
310 'userfullname' => fullname($user3),
311 'userpictureurl' => ''
314 $expectedposts['posts'][] = array(
315 'id' => $discussion1reply1->id,
316 'discussion' => $discussion1reply1->discussion,
317 'parent' => $discussion1reply1->parent,
318 'userid' => (int) $discussion1reply1->userid,
319 'created' => $discussion1reply1->created,
320 'modified' => $discussion1reply1->modified,
321 'mailed' => $discussion1reply1->mailed,
322 'subject' => $discussion1reply1->subject,
323 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
324 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
325 'messageformat' => 1, // This value is usually changed by external_format_text() function.
326 'messagetrust' => $discussion1reply1->messagetrust,
327 'attachment' => $discussion1reply1->attachment,
328 'messageinlinefiles' => array(
330 'filename' => $filename,
333 'fileurl' => moodle_url::make_webservice_pluginfile_url($forum1context->id, 'mod_forum', 'post',
334 $discussion1reply1->id, '/', $filename),
335 'timemodified' => $timepost,
336 'mimetype' => 'image/jpeg',
337 'isexternalfile' => false,
340 'totalscore' => $discussion1reply1->totalscore,
341 'mailnow' => $discussion1reply1->mailnow,
342 'children' => array($discussion1reply2->id),
345 'userfullname' => fullname($user2),
346 'userpictureurl' => ''
349 // Test a discussion with two additional posts (total 3 posts).
350 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
351 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
352 $this->assertEquals(3, count($posts['posts']));
354 // Generate here the pictures because we need to wait to the external function to init the theme.
355 $userpicture = new user_picture($user3);
356 $userpicture->size = 1; // Size f1.
357 $expectedposts['posts'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
359 $userpicture = new user_picture($user2);
360 $userpicture->size = 1; // Size f1.
361 $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
363 // Unset the initial discussion post.
364 array_pop($posts['posts']);
365 $this->assertEquals($expectedposts, $posts);
367 // Check we receive the unread count correctly on tracked forum.
368 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
369 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
370 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
371 foreach ($result as $f) {
372 if ($f['id'] == $forum2->id) {
373 $this->assertEquals(1, $f['unreadpostscount']);
377 // Test discussion without additional posts. There should be only one post (the one created by the discussion).
378 $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
379 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
380 $this->assertEquals(1, count($posts['posts']));
382 // Test discussion tracking on not tracked forum.
383 $result = mod_forum_external::view_forum_discussion($discussion1->id);
384 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
385 $this->assertTrue($result['status']);
386 $this->assertEmpty($result['warnings']);
388 // Test posts have not been marked as read.
389 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
390 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
391 foreach ($posts['posts'] as $post) {
392 $this->assertFalse($post['postread']);
395 // Test discussion tracking on tracked forum.
396 $result = mod_forum_external::view_forum_discussion($discussion3->id);
397 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
398 $this->assertTrue($result['status']);
399 $this->assertEmpty($result['warnings']);
401 // Test posts have been marked as read.
402 $posts = mod_forum_external::get_forum_discussion_posts($discussion3->id, 'modified', 'DESC');
403 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
404 foreach ($posts['posts'] as $post) {
405 $this->assertTrue($post['postread']);
408 // Check we receive 0 unread posts.
409 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
410 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
411 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
412 foreach ($result as $f) {
413 if ($f['id'] == $forum2->id) {
414 $this->assertEquals(0, $f['unreadpostscount']);
420 * Test get forum posts (qanda forum)
422 public function test_mod_forum_get_forum_discussion_posts_qanda() {
425 $this->resetAfterTest(true);
427 $record = new stdClass();
428 $user1 = self::getDataGenerator()->create_user($record);
429 $user2 = self::getDataGenerator()->create_user();
431 // Set the first created user to the test user.
432 self::setUser($user1);
434 // Create course to add the module.
435 $course1 = self::getDataGenerator()->create_course();
436 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
437 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
439 // Forum with tracking off.
440 $record = new stdClass();
441 $record->course = $course1->id;
442 $record->type = 'qanda';
443 $forum1 = self::getDataGenerator()->create_module('forum', $record);
444 $forum1context = context_module::instance($forum1->cmid);
446 // Add discussions to the forums.
447 $record = new stdClass();
448 $record->course = $course1->id;
449 $record->userid = $user2->id;
450 $record->forum = $forum1->id;
451 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
453 // Add 1 reply (not the actual user).
454 $record = new stdClass();
455 $record->discussion = $discussion1->id;
456 $record->parent = $discussion1->firstpost;
457 $record->userid = $user2->id;
458 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
460 // We still see only the original post.
461 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
462 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
463 $this->assertEquals(1, count($posts['posts']));
465 // Add a new reply, the user is going to be able to see only the original post and their new post.
466 $record = new stdClass();
467 $record->discussion = $discussion1->id;
468 $record->parent = $discussion1->firstpost;
469 $record->userid = $user1->id;
470 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
472 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
473 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
474 $this->assertEquals(2, count($posts['posts']));
476 // Now, we can fake the time of the user post, so he can se the rest of the discussion posts.
477 $discussion1reply2->created -= $CFG->maxeditingtime * 2;
478 $DB->update_record('forum_posts', $discussion1reply2);
480 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
481 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
482 $this->assertEquals(3, count($posts['posts']));
486 * Test get forum discussions paginated
488 public function test_mod_forum_get_forum_discussions_paginated() {
489 global $USER, $CFG, $DB, $PAGE;
491 $this->resetAfterTest(true);
493 // Set the CFG variable to allow track forums.
494 $CFG->forum_trackreadposts = true;
496 // Create a user who can track forums.
497 $record = new stdClass();
498 $record->trackforums = true;
499 $user1 = self::getDataGenerator()->create_user($record);
500 // Create a bunch of other users to post.
501 $user2 = self::getDataGenerator()->create_user();
502 $user3 = self::getDataGenerator()->create_user();
503 $user4 = self::getDataGenerator()->create_user();
505 // Set the first created user to the test user.
506 self::setUser($user1);
508 // Create courses to add the modules.
509 $course1 = self::getDataGenerator()->create_course();
511 // First forum with tracking off.
512 $record = new stdClass();
513 $record->course = $course1->id;
514 $record->trackingtype = FORUM_TRACKING_OFF;
515 $forum1 = self::getDataGenerator()->create_module('forum', $record);
517 // Add discussions to the forums.
518 $record = new stdClass();
519 $record->course = $course1->id;
520 $record->userid = $user1->id;
521 $record->forum = $forum1->id;
522 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
524 // Add three replies to the discussion 1 from different users.
525 $record = new stdClass();
526 $record->discussion = $discussion1->id;
527 $record->parent = $discussion1->firstpost;
528 $record->userid = $user2->id;
529 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
531 $record->parent = $discussion1reply1->id;
532 $record->userid = $user3->id;
533 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
535 $record->userid = $user4->id;
536 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
538 // Enrol the user in the first course.
539 $enrol = enrol_get_plugin('manual');
541 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
542 $enrolinstances = enrol_get_instances($course1->id, true);
543 foreach ($enrolinstances as $courseenrolinstance) {
544 if ($courseenrolinstance->enrol == "manual") {
545 $instance1 = $courseenrolinstance;
549 $enrol->enrol_user($instance1, $user1->id);
554 // Assign capabilities to view discussions for forum 1.
555 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
556 $context = context_module::instance($cm->id);
557 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
558 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
560 // Create what we expect to be returned when querying the forums.
562 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
564 // User pictures are initially empty, we should get the links once the external function is called.
565 $expecteddiscussions = array(
566 'id' => $discussion1->firstpost,
567 'name' => $discussion1->name,
568 'groupid' => $discussion1->groupid,
569 'timemodified' => $discussion1reply3->created,
570 'usermodified' => $discussion1reply3->userid,
571 'timestart' => $discussion1->timestart,
572 'timeend' => $discussion1->timeend,
573 'discussion' => $discussion1->id,
575 'userid' => $discussion1->userid,
576 'created' => $post1->created,
577 'modified' => $post1->modified,
578 'mailed' => $post1->mailed,
579 'subject' => $post1->subject,
580 'message' => $post1->message,
581 'messageformat' => $post1->messageformat,
582 'messagetrust' => $post1->messagetrust,
583 'attachment' => $post1->attachment,
584 'totalscore' => $post1->totalscore,
585 'mailnow' => $post1->mailnow,
586 'userfullname' => fullname($user1),
587 'usermodifiedfullname' => fullname($user4),
588 'userpictureurl' => '',
589 'usermodifiedpictureurl' => '',
592 'pinned' => FORUM_DISCUSSION_UNPINNED,
597 // Call the external function passing forum id.
598 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
599 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
600 $expectedreturn = array(
601 'discussions' => array($expecteddiscussions),
602 'warnings' => array()
605 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
606 $userpicture = new user_picture($user1);
607 $userpicture->size = 1; // Size f1.
608 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
610 $userpicture = new user_picture($user4);
611 $userpicture->size = 1; // Size f1.
612 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
614 $this->assertEquals($expectedreturn, $discussions);
616 // Call without required view discussion capability.
617 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
619 mod_forum_external::get_forum_discussions_paginated($forum1->id);
620 $this->fail('Exception expected due to missing capability.');
621 } catch (moodle_exception $e) {
622 $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
625 // Unenrol user from second course.
626 $enrol->unenrol_user($instance1, $user1->id);
628 // Call for the second course we unenrolled the user from, make sure exception thrown.
630 mod_forum_external::get_forum_discussions_paginated($forum1->id);
631 $this->fail('Exception expected due to being unenrolled from the course.');
632 } catch (moodle_exception $e) {
633 $this->assertEquals('requireloginerror', $e->errorcode);
638 * Test get forum discussions paginated (qanda forums)
640 public function test_mod_forum_get_forum_discussions_paginated_qanda() {
642 $this->resetAfterTest(true);
644 // Create courses to add the modules.
645 $course = self::getDataGenerator()->create_course();
647 $user1 = self::getDataGenerator()->create_user();
648 $user2 = self::getDataGenerator()->create_user();
650 // First forum with tracking off.
651 $record = new stdClass();
652 $record->course = $course->id;
653 $record->type = 'qanda';
654 $forum = self::getDataGenerator()->create_module('forum', $record);
656 // Add discussions to the forums.
657 $discussionrecord = new stdClass();
658 $discussionrecord->course = $course->id;
659 $discussionrecord->userid = $user2->id;
660 $discussionrecord->forum = $forum->id;
661 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
663 self::setAdminUser();
664 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
665 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
667 $this->assertCount(1, $discussions['discussions']);
668 $this->assertCount(0, $discussions['warnings']);
670 self::setUser($user1);
671 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
673 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
674 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
676 $this->assertCount(1, $discussions['discussions']);
677 $this->assertCount(0, $discussions['warnings']);
682 * Test add_discussion_post
684 public function test_add_discussion_post() {
687 $this->resetAfterTest(true);
689 $user = self::getDataGenerator()->create_user();
690 $otheruser = self::getDataGenerator()->create_user();
692 self::setAdminUser();
694 // Create course to add the module.
695 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
697 // Forum with tracking off.
698 $record = new stdClass();
699 $record->course = $course->id;
700 $forum = self::getDataGenerator()->create_module('forum', $record);
701 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
702 $forumcontext = context_module::instance($forum->cmid);
704 // Add discussions to the forums.
705 $record = new stdClass();
706 $record->course = $course->id;
707 $record->userid = $user->id;
708 $record->forum = $forum->id;
709 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
711 // Try to post (user not enrolled).
712 self::setUser($user);
714 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
715 $this->fail('Exception expected due to being unenrolled from the course.');
716 } catch (moodle_exception $e) {
717 $this->assertEquals('requireloginerror', $e->errorcode);
720 $this->getDataGenerator()->enrol_user($user->id, $course->id);
721 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
723 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
724 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
726 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
727 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
728 // We receive the discussion and the post.
729 $this->assertEquals(2, count($posts['posts']));
732 foreach ($posts['posts'] as $thispost) {
733 if ($createdpost['postid'] == $thispost['id']) {
734 $this->assertEquals('some subject', $thispost['subject']);
735 $this->assertEquals('some text here...', $thispost['message']);
739 $this->assertTrue($tested);
741 // Test inline and regular attachment in post
742 // Create a file in a draft area for inline attachments.
743 $draftidinlineattach = file_get_unused_draft_itemid();
744 $draftidattach = file_get_unused_draft_itemid();
745 self::setUser($user);
746 $usercontext = context_user::instance($user->id);
750 $filenameimg = 'shouldbeanimage.txt';
751 $filerecordinline = array(
752 'contextid' => $usercontext->id,
753 'component' => $component,
754 'filearea' => $filearea,
755 'itemid' => $draftidinlineattach,
756 'filepath' => $filepath,
757 'filename' => $filenameimg,
759 $fs = get_file_storage();
761 // Create a file in a draft area for regular attachments.
762 $filerecordattach = $filerecordinline;
763 $attachfilename = 'attachment.txt';
764 $filerecordattach['filename'] = $attachfilename;
765 $filerecordattach['itemid'] = $draftidattach;
766 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
767 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
769 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
770 array('name' => 'attachmentsid', 'value' => $draftidattach));
771 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
772 . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
773 . '" alt="inlineimage">.';
774 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
775 $dummytext, $options);
776 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
778 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
779 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
780 // We receive the discussion and the post.
781 // Can't guarantee order of posts during tests.
783 foreach ($posts['posts'] as $thispost) {
784 if ($createdpost['postid'] == $thispost['id']) {
785 $this->assertEquals($createdpost['postid'], $thispost['id']);
786 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
787 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
788 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
789 $this->assertContains('pluginfile.php', $thispost['message']);
795 $this->assertTrue($postfound);
797 // Check not posting in groups the user is not member of.
798 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
799 groups_add_member($group->id, $otheruser->id);
801 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
802 $record->forum = $forum->id;
803 $record->userid = $otheruser->id;
804 $record->groupid = $group->id;
805 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
808 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
809 $this->fail('Exception expected due to invalid permissions for posting.');
810 } catch (moodle_exception $e) {
811 $this->assertEquals('nopostforum', $e->errorcode);
817 * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
819 public function test_add_discussion() {
821 $this->resetAfterTest(true);
823 // Create courses to add the modules.
824 $course = self::getDataGenerator()->create_course();
826 $user1 = self::getDataGenerator()->create_user();
827 $user2 = self::getDataGenerator()->create_user();
829 // First forum with tracking off.
830 $record = new stdClass();
831 $record->course = $course->id;
832 $record->type = 'news';
833 $forum = self::getDataGenerator()->create_module('forum', $record);
835 self::setUser($user1);
836 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
839 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
840 $this->fail('Exception expected due to invalid permissions.');
841 } catch (moodle_exception $e) {
842 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
845 self::setAdminUser();
846 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
847 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
849 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
850 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
852 $this->assertCount(1, $discussions['discussions']);
853 $this->assertCount(0, $discussions['warnings']);
855 $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
856 $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
857 $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
858 $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
860 $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
861 array('options' => array('name' => 'discussionpinned',
863 $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
864 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
865 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
866 $this->assertCount(3, $discussions['discussions']);
867 $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
869 // Test inline and regular attachment in new discussion
870 // Create a file in a draft area for inline attachments.
872 $fs = get_file_storage();
874 $draftidinlineattach = file_get_unused_draft_itemid();
875 $draftidattach = file_get_unused_draft_itemid();
877 $usercontext = context_user::instance($USER->id);
881 $filenameimg = 'shouldbeanimage.txt';
883 'contextid' => $usercontext->id,
884 'component' => $component,
885 'filearea' => $filearea,
886 'itemid' => $draftidinlineattach,
887 'filepath' => $filepath,
888 'filename' => $filenameimg,
891 // Create a file in a draft area for regular attachments.
892 $filerecordattach = $filerecord;
893 $attachfilename = 'attachment.txt';
894 $filerecordattach['filename'] = $attachfilename;
895 $filerecordattach['itemid'] = $draftidattach;
896 $fs->create_file_from_string($filerecord, 'image contents (not really)');
897 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
899 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
900 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
901 '" alt="inlineimage">.';
903 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
904 array('name' => 'attachmentsid', 'value' => $draftidattach));
905 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
906 $dummytext, -1, $options);
907 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
909 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
910 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
912 $this->assertCount(4, $discussions['discussions']);
913 $this->assertCount(0, $createddiscussion['warnings']);
914 // Can't guarantee order of posts during tests.
916 foreach ($discussions['discussions'] as $thisdiscussion) {
917 if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
918 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
919 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
920 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
921 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
922 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
928 $this->assertTrue($postfound);
932 * Test adding discussions in a course with gorups
934 public function test_add_discussion_in_course_with_groups() {
937 $this->resetAfterTest(true);
939 // Create course to add the module.
940 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
941 $user = self::getDataGenerator()->create_user();
942 $this->getDataGenerator()->enrol_user($user->id, $course->id);
944 // Forum forcing separate gropus.
945 $record = new stdClass();
946 $record->course = $course->id;
947 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
949 // Try to post (user not enrolled).
950 self::setUser($user);
952 // The user is not enroled in any group, try to post in a forum with separate groups.
954 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
955 $this->fail('Exception expected due to invalid group permissions.');
956 } catch (moodle_exception $e) {
957 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
961 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
962 $this->fail('Exception expected due to invalid group permissions.');
963 } catch (moodle_exception $e) {
964 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
968 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
970 // Try to post in a group the user is not enrolled.
972 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
973 $this->fail('Exception expected due to invalid group permissions.');
974 } catch (moodle_exception $e) {
975 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
978 // Add the user to a group.
979 groups_add_member($group->id, $user->id);
981 // Try to post in a group the user is not enrolled.
983 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
984 $this->fail('Exception expected due to invalid group.');
985 } catch (moodle_exception $e) {
986 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
989 // Nost add the discussion using a valid group.
990 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
991 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
993 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
994 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
996 $this->assertCount(1, $discussions['discussions']);
997 $this->assertCount(0, $discussions['warnings']);
998 $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
999 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1001 // Now add a discussions without indicating a group. The function should guess the correct group.
1002 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1003 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1005 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1006 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1008 $this->assertCount(2, $discussions['discussions']);
1009 $this->assertCount(0, $discussions['warnings']);
1010 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1011 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1013 // Enrol the same user in other group.
1014 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1015 groups_add_member($group2->id, $user->id);
1017 // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
1018 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1019 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1021 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1022 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1024 $this->assertCount(3, $discussions['discussions']);
1025 $this->assertCount(0, $discussions['warnings']);
1026 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1027 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1028 $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
1033 * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
1035 public function test_can_add_discussion() {
1037 $this->resetAfterTest(true);
1039 // Create courses to add the modules.
1040 $course = self::getDataGenerator()->create_course();
1042 $user = self::getDataGenerator()->create_user();
1044 // First forum with tracking off.
1045 $record = new stdClass();
1046 $record->course = $course->id;
1047 $record->type = 'news';
1048 $forum = self::getDataGenerator()->create_module('forum', $record);
1050 // User with no permissions to add in a news forum.
1051 self::setUser($user);
1052 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1054 $result = mod_forum_external::can_add_discussion($forum->id);
1055 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1056 $this->assertFalse($result['status']);
1057 $this->assertFalse($result['canpindiscussions']);
1058 $this->assertTrue($result['cancreateattachment']);
1060 // Disable attachments.
1061 $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id));
1062 $result = mod_forum_external::can_add_discussion($forum->id);
1063 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1064 $this->assertFalse($result['status']);
1065 $this->assertFalse($result['canpindiscussions']);
1066 $this->assertFalse($result['cancreateattachment']);
1067 $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id)); // Enable attachments again.
1069 self::setAdminUser();
1070 $result = mod_forum_external::can_add_discussion($forum->id);
1071 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1072 $this->assertTrue($result['status']);
1073 $this->assertTrue($result['canpindiscussions']);
1074 $this->assertTrue($result['cancreateattachment']);
1079 * Test get forum posts discussions including rating information.
1081 public function test_mod_forum_get_forum_discussion_rating_information() {
1083 require_once($CFG->dirroot . '/rating/lib.php');
1085 $this->resetAfterTest(true);
1087 $user1 = self::getDataGenerator()->create_user();
1088 $user2 = self::getDataGenerator()->create_user();
1089 $user3 = self::getDataGenerator()->create_user();
1090 $teacher = self::getDataGenerator()->create_user();
1092 // Create course to add the module.
1093 $course = self::getDataGenerator()->create_course();
1095 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1096 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
1097 $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id, 'manual');
1098 $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id, 'manual');
1099 $this->getDataGenerator()->enrol_user($user3->id, $course->id, $studentrole->id, 'manual');
1100 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
1102 // Create the forum.
1103 $record = new stdClass();
1104 $record->course = $course->id;
1105 // Set Aggregate type = Average of ratings.
1106 $record->assessed = RATING_AGGREGATE_AVERAGE;
1107 $record->scale = 100;
1108 $forum = self::getDataGenerator()->create_module('forum', $record);
1109 $context = context_module::instance($forum->cmid);
1111 // Add discussion to the forum.
1112 $record = new stdClass();
1113 $record->course = $course->id;
1114 $record->userid = $user1->id;
1115 $record->forum = $forum->id;
1116 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1118 // Retrieve the first post.
1119 $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
1121 // Rate the discussion as user2.
1122 $rating1 = new stdClass();
1123 $rating1->contextid = $context->id;
1124 $rating1->component = 'mod_forum';
1125 $rating1->ratingarea = 'post';
1126 $rating1->itemid = $post->id;
1127 $rating1->rating = 50;
1128 $rating1->scaleid = 100;
1129 $rating1->userid = $user2->id;
1130 $rating1->timecreated = time();
1131 $rating1->timemodified = time();
1132 $rating1->id = $DB->insert_record('rating', $rating1);
1134 // Rate the discussion as user3.
1135 $rating2 = new stdClass();
1136 $rating2->contextid = $context->id;
1137 $rating2->component = 'mod_forum';
1138 $rating2->ratingarea = 'post';
1139 $rating2->itemid = $post->id;
1140 $rating2->rating = 100;
1141 $rating2->scaleid = 100;
1142 $rating2->userid = $user3->id;
1143 $rating2->timecreated = time() + 1;
1144 $rating2->timemodified = time() + 1;
1145 $rating2->id = $DB->insert_record('rating', $rating2);
1147 // Retrieve the rating for the post as student.
1148 $this->setUser($user1);
1149 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1150 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1151 $this->assertCount(1, $posts['ratinginfo']['ratings']);
1152 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
1153 $this->assertFalse($posts['ratinginfo']['canviewall']);
1154 $this->assertFalse($posts['ratinginfo']['ratings'][0]['canrate']);
1155 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
1156 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
1158 // Retrieve the rating for the post as teacher.
1159 $this->setUser($teacher);
1160 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1161 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1162 $this->assertCount(1, $posts['ratinginfo']['ratings']);
1163 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
1164 $this->assertTrue($posts['ratinginfo']['canviewall']);
1165 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canrate']);
1166 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
1167 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);