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 'warnings' => array(),
281 // User pictures are initially empty, we should get the links once the external function is called.
282 $expectedposts['posts'][] = array(
283 'id' => $discussion1reply2->id,
284 'discussion' => $discussion1reply2->discussion,
285 'parent' => $discussion1reply2->parent,
286 'userid' => (int) $discussion1reply2->userid,
287 'created' => $discussion1reply2->created,
288 'modified' => $discussion1reply2->modified,
289 'mailed' => $discussion1reply2->mailed,
290 'subject' => $discussion1reply2->subject,
291 'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
292 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
293 'messageformat' => 1, // This value is usually changed by external_format_text() function.
294 'messagetrust' => $discussion1reply2->messagetrust,
295 'attachment' => $discussion1reply2->attachment,
296 'totalscore' => $discussion1reply2->totalscore,
297 'mailnow' => $discussion1reply2->mailnow,
298 'children' => array(),
301 'userfullname' => fullname($user3),
302 'userpictureurl' => ''
305 $expectedposts['posts'][] = array(
306 'id' => $discussion1reply1->id,
307 'discussion' => $discussion1reply1->discussion,
308 'parent' => $discussion1reply1->parent,
309 'userid' => (int) $discussion1reply1->userid,
310 'created' => $discussion1reply1->created,
311 'modified' => $discussion1reply1->modified,
312 'mailed' => $discussion1reply1->mailed,
313 'subject' => $discussion1reply1->subject,
314 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
315 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
316 'messageformat' => 1, // This value is usually changed by external_format_text() function.
317 'messagetrust' => $discussion1reply1->messagetrust,
318 'attachment' => $discussion1reply1->attachment,
319 'messageinlinefiles' => array(
321 'filename' => $filename,
324 'fileurl' => moodle_url::make_webservice_pluginfile_url($forum1context->id, 'mod_forum', 'post',
325 $discussion1reply1->id, '/', $filename),
326 'timemodified' => $timepost,
327 'mimetype' => 'image/jpeg',
328 'isexternalfile' => false,
331 'totalscore' => $discussion1reply1->totalscore,
332 'mailnow' => $discussion1reply1->mailnow,
333 'children' => array($discussion1reply2->id),
336 'userfullname' => fullname($user2),
337 'userpictureurl' => ''
340 // Test a discussion with two additional posts (total 3 posts).
341 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
342 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
343 $this->assertEquals(3, count($posts['posts']));
345 // Generate here the pictures because we need to wait to the external function to init the theme.
346 $userpicture = new user_picture($user3);
347 $userpicture->size = 1; // Size f1.
348 $expectedposts['posts'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
350 $userpicture = new user_picture($user2);
351 $userpicture->size = 1; // Size f1.
352 $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
354 // Unset the initial discussion post.
355 array_pop($posts['posts']);
356 $this->assertEquals($expectedposts, $posts);
358 // Check we receive the unread count correctly on tracked forum.
359 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
360 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
361 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
362 foreach ($result as $f) {
363 if ($f['id'] == $forum2->id) {
364 $this->assertEquals(1, $f['unreadpostscount']);
368 // Test discussion without additional posts. There should be only one post (the one created by the discussion).
369 $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
370 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
371 $this->assertEquals(1, count($posts['posts']));
373 // Test discussion tracking on not tracked forum.
374 $result = mod_forum_external::view_forum_discussion($discussion1->id);
375 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
376 $this->assertTrue($result['status']);
377 $this->assertEmpty($result['warnings']);
379 // Test posts have not been marked as read.
380 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
381 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
382 foreach ($posts['posts'] as $post) {
383 $this->assertFalse($post['postread']);
386 // Test discussion tracking on tracked forum.
387 $result = mod_forum_external::view_forum_discussion($discussion3->id);
388 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
389 $this->assertTrue($result['status']);
390 $this->assertEmpty($result['warnings']);
392 // Test posts have been marked as read.
393 $posts = mod_forum_external::get_forum_discussion_posts($discussion3->id, 'modified', 'DESC');
394 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
395 foreach ($posts['posts'] as $post) {
396 $this->assertTrue($post['postread']);
399 // Check we receive 0 unread posts.
400 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
401 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
402 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
403 foreach ($result as $f) {
404 if ($f['id'] == $forum2->id) {
405 $this->assertEquals(0, $f['unreadpostscount']);
411 * Test get forum posts (qanda forum)
413 public function test_mod_forum_get_forum_discussion_posts_qanda() {
416 $this->resetAfterTest(true);
418 $record = new stdClass();
419 $user1 = self::getDataGenerator()->create_user($record);
420 $user2 = self::getDataGenerator()->create_user();
422 // Set the first created user to the test user.
423 self::setUser($user1);
425 // Create course to add the module.
426 $course1 = self::getDataGenerator()->create_course();
427 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
428 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
430 // Forum with tracking off.
431 $record = new stdClass();
432 $record->course = $course1->id;
433 $record->type = 'qanda';
434 $forum1 = self::getDataGenerator()->create_module('forum', $record);
435 $forum1context = context_module::instance($forum1->cmid);
437 // Add discussions to the forums.
438 $record = new stdClass();
439 $record->course = $course1->id;
440 $record->userid = $user2->id;
441 $record->forum = $forum1->id;
442 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
444 // Add 1 reply (not the actual user).
445 $record = new stdClass();
446 $record->discussion = $discussion1->id;
447 $record->parent = $discussion1->firstpost;
448 $record->userid = $user2->id;
449 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
451 // We still see only the original post.
452 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
453 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
454 $this->assertEquals(1, count($posts['posts']));
456 // Add a new reply, the user is going to be able to see only the original post and their new post.
457 $record = new stdClass();
458 $record->discussion = $discussion1->id;
459 $record->parent = $discussion1->firstpost;
460 $record->userid = $user1->id;
461 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
463 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
464 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
465 $this->assertEquals(2, count($posts['posts']));
467 // Now, we can fake the time of the user post, so he can se the rest of the discussion posts.
468 $discussion1reply2->created -= $CFG->maxeditingtime * 2;
469 $DB->update_record('forum_posts', $discussion1reply2);
471 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
472 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
473 $this->assertEquals(3, count($posts['posts']));
477 * Test get forum discussions paginated
479 public function test_mod_forum_get_forum_discussions_paginated() {
480 global $USER, $CFG, $DB, $PAGE;
482 $this->resetAfterTest(true);
484 // Set the CFG variable to allow track forums.
485 $CFG->forum_trackreadposts = true;
487 // Create a user who can track forums.
488 $record = new stdClass();
489 $record->trackforums = true;
490 $user1 = self::getDataGenerator()->create_user($record);
491 // Create a bunch of other users to post.
492 $user2 = self::getDataGenerator()->create_user();
493 $user3 = self::getDataGenerator()->create_user();
494 $user4 = self::getDataGenerator()->create_user();
496 // Set the first created user to the test user.
497 self::setUser($user1);
499 // Create courses to add the modules.
500 $course1 = self::getDataGenerator()->create_course();
502 // First forum with tracking off.
503 $record = new stdClass();
504 $record->course = $course1->id;
505 $record->trackingtype = FORUM_TRACKING_OFF;
506 $forum1 = self::getDataGenerator()->create_module('forum', $record);
508 // Add discussions to the forums.
509 $record = new stdClass();
510 $record->course = $course1->id;
511 $record->userid = $user1->id;
512 $record->forum = $forum1->id;
513 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
515 // Add three replies to the discussion 1 from different users.
516 $record = new stdClass();
517 $record->discussion = $discussion1->id;
518 $record->parent = $discussion1->firstpost;
519 $record->userid = $user2->id;
520 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
522 $record->parent = $discussion1reply1->id;
523 $record->userid = $user3->id;
524 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
526 $record->userid = $user4->id;
527 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
529 // Enrol the user in the first course.
530 $enrol = enrol_get_plugin('manual');
532 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
533 $enrolinstances = enrol_get_instances($course1->id, true);
534 foreach ($enrolinstances as $courseenrolinstance) {
535 if ($courseenrolinstance->enrol == "manual") {
536 $instance1 = $courseenrolinstance;
540 $enrol->enrol_user($instance1, $user1->id);
545 // Assign capabilities to view discussions for forum 1.
546 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
547 $context = context_module::instance($cm->id);
548 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
549 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
551 // Create what we expect to be returned when querying the forums.
553 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
555 // User pictures are initially empty, we should get the links once the external function is called.
556 $expecteddiscussions = array(
557 'id' => $discussion1->firstpost,
558 'name' => $discussion1->name,
559 'groupid' => $discussion1->groupid,
560 'timemodified' => $discussion1reply3->created,
561 'usermodified' => $discussion1reply3->userid,
562 'timestart' => $discussion1->timestart,
563 'timeend' => $discussion1->timeend,
564 'discussion' => $discussion1->id,
566 'userid' => $discussion1->userid,
567 'created' => $post1->created,
568 'modified' => $post1->modified,
569 'mailed' => $post1->mailed,
570 'subject' => $post1->subject,
571 'message' => $post1->message,
572 'messageformat' => $post1->messageformat,
573 'messagetrust' => $post1->messagetrust,
574 'attachment' => $post1->attachment,
575 'totalscore' => $post1->totalscore,
576 'mailnow' => $post1->mailnow,
577 'userfullname' => fullname($user1),
578 'usermodifiedfullname' => fullname($user4),
579 'userpictureurl' => '',
580 'usermodifiedpictureurl' => '',
583 'pinned' => FORUM_DISCUSSION_UNPINNED,
588 // Call the external function passing forum id.
589 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
590 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
591 $expectedreturn = array(
592 'discussions' => array($expecteddiscussions),
593 'warnings' => array()
596 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
597 $userpicture = new user_picture($user1);
598 $userpicture->size = 1; // Size f1.
599 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
601 $userpicture = new user_picture($user4);
602 $userpicture->size = 1; // Size f1.
603 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
605 $this->assertEquals($expectedreturn, $discussions);
607 // Call without required view discussion capability.
608 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
610 mod_forum_external::get_forum_discussions_paginated($forum1->id);
611 $this->fail('Exception expected due to missing capability.');
612 } catch (moodle_exception $e) {
613 $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
616 // Unenrol user from second course.
617 $enrol->unenrol_user($instance1, $user1->id);
619 // Call for the second course we unenrolled the user from, make sure exception thrown.
621 mod_forum_external::get_forum_discussions_paginated($forum1->id);
622 $this->fail('Exception expected due to being unenrolled from the course.');
623 } catch (moodle_exception $e) {
624 $this->assertEquals('requireloginerror', $e->errorcode);
629 * Test get forum discussions paginated (qanda forums)
631 public function test_mod_forum_get_forum_discussions_paginated_qanda() {
633 $this->resetAfterTest(true);
635 // Create courses to add the modules.
636 $course = self::getDataGenerator()->create_course();
638 $user1 = self::getDataGenerator()->create_user();
639 $user2 = self::getDataGenerator()->create_user();
641 // First forum with tracking off.
642 $record = new stdClass();
643 $record->course = $course->id;
644 $record->type = 'qanda';
645 $forum = self::getDataGenerator()->create_module('forum', $record);
647 // Add discussions to the forums.
648 $discussionrecord = new stdClass();
649 $discussionrecord->course = $course->id;
650 $discussionrecord->userid = $user2->id;
651 $discussionrecord->forum = $forum->id;
652 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
654 self::setAdminUser();
655 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
656 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
658 $this->assertCount(1, $discussions['discussions']);
659 $this->assertCount(0, $discussions['warnings']);
661 self::setUser($user1);
662 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
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']);
673 * Test add_discussion_post
675 public function test_add_discussion_post() {
678 $this->resetAfterTest(true);
680 $user = self::getDataGenerator()->create_user();
681 $otheruser = self::getDataGenerator()->create_user();
683 self::setAdminUser();
685 // Create course to add the module.
686 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
688 // Forum with tracking off.
689 $record = new stdClass();
690 $record->course = $course->id;
691 $forum = self::getDataGenerator()->create_module('forum', $record);
692 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
693 $forumcontext = context_module::instance($forum->cmid);
695 // Add discussions to the forums.
696 $record = new stdClass();
697 $record->course = $course->id;
698 $record->userid = $user->id;
699 $record->forum = $forum->id;
700 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
702 // Try to post (user not enrolled).
703 self::setUser($user);
705 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
706 $this->fail('Exception expected due to being unenrolled from the course.');
707 } catch (moodle_exception $e) {
708 $this->assertEquals('requireloginerror', $e->errorcode);
711 $this->getDataGenerator()->enrol_user($user->id, $course->id);
712 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
714 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
715 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
717 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
718 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
719 // We receive the discussion and the post.
720 $this->assertEquals(2, count($posts['posts']));
723 foreach ($posts['posts'] as $thispost) {
724 if ($createdpost['postid'] == $thispost['id']) {
725 $this->assertEquals('some subject', $thispost['subject']);
726 $this->assertEquals('some text here...', $thispost['message']);
730 $this->assertTrue($tested);
732 // Test inline and regular attachment in post
733 // Create a file in a draft area for inline attachments.
734 $draftidinlineattach = file_get_unused_draft_itemid();
735 $draftidattach = file_get_unused_draft_itemid();
736 self::setUser($user);
737 $usercontext = context_user::instance($user->id);
741 $filenameimg = 'shouldbeanimage.txt';
742 $filerecordinline = array(
743 'contextid' => $usercontext->id,
744 'component' => $component,
745 'filearea' => $filearea,
746 'itemid' => $draftidinlineattach,
747 'filepath' => $filepath,
748 'filename' => $filenameimg,
750 $fs = get_file_storage();
752 // Create a file in a draft area for regular attachments.
753 $filerecordattach = $filerecordinline;
754 $attachfilename = 'attachment.txt';
755 $filerecordattach['filename'] = $attachfilename;
756 $filerecordattach['itemid'] = $draftidattach;
757 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
758 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
760 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
761 array('name' => 'attachmentsid', 'value' => $draftidattach));
762 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
763 . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
764 . '" alt="inlineimage">.';
765 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
766 $dummytext, $options);
767 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
769 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
770 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
771 // We receive the discussion and the post.
772 // Can't guarantee order of posts during tests.
774 foreach ($posts['posts'] as $thispost) {
775 if ($createdpost['postid'] == $thispost['id']) {
776 $this->assertEquals($createdpost['postid'], $thispost['id']);
777 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
778 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
779 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
780 $this->assertContains('pluginfile.php', $thispost['message']);
786 $this->assertTrue($postfound);
788 // Check not posting in groups the user is not member of.
789 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
790 groups_add_member($group->id, $otheruser->id);
792 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
793 $record->forum = $forum->id;
794 $record->userid = $otheruser->id;
795 $record->groupid = $group->id;
796 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
799 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
800 $this->fail('Exception expected due to invalid permissions for posting.');
801 } catch (moodle_exception $e) {
802 $this->assertEquals('nopostforum', $e->errorcode);
808 * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
810 public function test_add_discussion() {
812 $this->resetAfterTest(true);
814 // Create courses to add the modules.
815 $course = self::getDataGenerator()->create_course();
817 $user1 = self::getDataGenerator()->create_user();
818 $user2 = self::getDataGenerator()->create_user();
820 // First forum with tracking off.
821 $record = new stdClass();
822 $record->course = $course->id;
823 $record->type = 'news';
824 $forum = self::getDataGenerator()->create_module('forum', $record);
826 self::setUser($user1);
827 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
830 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
831 $this->fail('Exception expected due to invalid permissions.');
832 } catch (moodle_exception $e) {
833 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
836 self::setAdminUser();
837 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
838 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
840 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
841 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
843 $this->assertCount(1, $discussions['discussions']);
844 $this->assertCount(0, $discussions['warnings']);
846 $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
847 $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
848 $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
849 $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
851 $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
852 array('options' => array('name' => 'discussionpinned',
854 $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
855 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
856 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
857 $this->assertCount(3, $discussions['discussions']);
858 $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
860 // Test inline and regular attachment in new discussion
861 // Create a file in a draft area for inline attachments.
863 $fs = get_file_storage();
865 $draftidinlineattach = file_get_unused_draft_itemid();
866 $draftidattach = file_get_unused_draft_itemid();
868 $usercontext = context_user::instance($USER->id);
872 $filenameimg = 'shouldbeanimage.txt';
874 'contextid' => $usercontext->id,
875 'component' => $component,
876 'filearea' => $filearea,
877 'itemid' => $draftidinlineattach,
878 'filepath' => $filepath,
879 'filename' => $filenameimg,
882 // Create a file in a draft area for regular attachments.
883 $filerecordattach = $filerecord;
884 $attachfilename = 'attachment.txt';
885 $filerecordattach['filename'] = $attachfilename;
886 $filerecordattach['itemid'] = $draftidattach;
887 $fs->create_file_from_string($filerecord, 'image contents (not really)');
888 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
890 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
891 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
892 '" alt="inlineimage">.';
894 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
895 array('name' => 'attachmentsid', 'value' => $draftidattach));
896 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
897 $dummytext, -1, $options);
898 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
900 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
901 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
903 $this->assertCount(4, $discussions['discussions']);
904 $this->assertCount(0, $createddiscussion['warnings']);
905 // Can't guarantee order of posts during tests.
907 foreach ($discussions['discussions'] as $thisdiscussion) {
908 if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
909 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
910 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
911 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
912 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
913 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
919 $this->assertTrue($postfound);
923 * Test adding discussions in a course with gorups
925 public function test_add_discussion_in_course_with_groups() {
928 $this->resetAfterTest(true);
930 // Create course to add the module.
931 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
932 $user = self::getDataGenerator()->create_user();
933 $this->getDataGenerator()->enrol_user($user->id, $course->id);
935 // Forum forcing separate gropus.
936 $record = new stdClass();
937 $record->course = $course->id;
938 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
940 // Try to post (user not enrolled).
941 self::setUser($user);
943 // The user is not enroled in any group, try to post in a forum with separate groups.
945 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
946 $this->fail('Exception expected due to invalid group permissions.');
947 } catch (moodle_exception $e) {
948 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
952 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
953 $this->fail('Exception expected due to invalid group permissions.');
954 } catch (moodle_exception $e) {
955 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
959 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
961 // Try to post in a group the user is not enrolled.
963 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
964 $this->fail('Exception expected due to invalid group permissions.');
965 } catch (moodle_exception $e) {
966 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
969 // Add the user to a group.
970 groups_add_member($group->id, $user->id);
972 // Try to post in a group the user is not enrolled.
974 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
975 $this->fail('Exception expected due to invalid group.');
976 } catch (moodle_exception $e) {
977 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
980 // Nost add the discussion using a valid group.
981 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
982 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
984 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
985 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
987 $this->assertCount(1, $discussions['discussions']);
988 $this->assertCount(0, $discussions['warnings']);
989 $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
990 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
992 // Now add a discussions without indicating a group. The function should guess the correct group.
993 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
994 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
996 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
997 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
999 $this->assertCount(2, $discussions['discussions']);
1000 $this->assertCount(0, $discussions['warnings']);
1001 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1002 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1004 // Enrol the same user in other group.
1005 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1006 groups_add_member($group2->id, $user->id);
1008 // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
1009 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1010 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1012 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1013 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1015 $this->assertCount(3, $discussions['discussions']);
1016 $this->assertCount(0, $discussions['warnings']);
1017 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1018 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1019 $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
1024 * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
1026 public function test_can_add_discussion() {
1028 $this->resetAfterTest(true);
1030 // Create courses to add the modules.
1031 $course = self::getDataGenerator()->create_course();
1033 $user = self::getDataGenerator()->create_user();
1035 // First forum with tracking off.
1036 $record = new stdClass();
1037 $record->course = $course->id;
1038 $record->type = 'news';
1039 $forum = self::getDataGenerator()->create_module('forum', $record);
1041 // User with no permissions to add in a news forum.
1042 self::setUser($user);
1043 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1045 $result = mod_forum_external::can_add_discussion($forum->id);
1046 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1047 $this->assertFalse($result['status']);
1048 $this->assertFalse($result['canpindiscussions']);
1049 $this->assertTrue($result['cancreateattachment']);
1051 // Disable attachments.
1052 $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id));
1053 $result = mod_forum_external::can_add_discussion($forum->id);
1054 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1055 $this->assertFalse($result['status']);
1056 $this->assertFalse($result['canpindiscussions']);
1057 $this->assertFalse($result['cancreateattachment']);
1058 $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id)); // Enable attachments again.
1060 self::setAdminUser();
1061 $result = mod_forum_external::can_add_discussion($forum->id);
1062 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1063 $this->assertTrue($result['status']);
1064 $this->assertTrue($result['canpindiscussions']);
1065 $this->assertTrue($result['cancreateattachment']);