f9f08ca3e8a40b0a8a5655a82c59a4fa5a10a5b2
[moodle.git] / mod / forum / tests / externallib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * The module forums external functions unit tests
19  *
20  * @package    mod_forum
21  * @category   external
22  * @copyright  2012 Mark Nelson <markn@moodle.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
30 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
32 class mod_forum_external_testcase extends externallib_advanced_testcase {
34     /**
35      * Tests set up
36      */
37     protected function setUp() {
38         global $CFG;
40         // We must clear the subscription caches. This has to be done both before each test, and after in case of other
41         // tests using these functions.
42         \mod_forum\subscriptions::reset_forum_cache();
44         require_once($CFG->dirroot . '/mod/forum/externallib.php');
45     }
47     public function tearDown() {
48         // We must clear the subscription caches. This has to be done both before each test, and after in case of other
49         // tests using these functions.
50         \mod_forum\subscriptions::reset_forum_cache();
51     }
53     /**
54      * Test get forums
55      */
56     public function test_mod_forum_get_forums_by_courses() {
57         global $USER, $CFG, $DB;
59         $this->resetAfterTest(true);
61         // Create a user.
62         $user = self::getDataGenerator()->create_user();
64         // Set to the user.
65         self::setUser($user);
67         // Create courses to add the modules.
68         $course1 = self::getDataGenerator()->create_course();
69         $course2 = self::getDataGenerator()->create_course();
71         // First forum.
72         $record = new stdClass();
73         $record->introformat = FORMAT_HTML;
74         $record->course = $course1->id;
75         $forum1 = self::getDataGenerator()->create_module('forum', $record);
77         // Second forum.
78         $record = new stdClass();
79         $record->introformat = FORMAT_HTML;
80         $record->course = $course2->id;
81         $forum2 = self::getDataGenerator()->create_module('forum', $record);
82         $forum2->introfiles = [];
84         // Add discussions to the forums.
85         $record = new stdClass();
86         $record->course = $course1->id;
87         $record->userid = $user->id;
88         $record->forum = $forum1->id;
89         $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
90         // Expect one discussion.
91         $forum1->numdiscussions = 1;
92         $forum1->cancreatediscussions = true;
93         $forum1->introfiles = [];
95         $record = new stdClass();
96         $record->course = $course2->id;
97         $record->userid = $user->id;
98         $record->forum = $forum2->id;
99         $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
100         $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
101         // Expect two discussions.
102         $forum2->numdiscussions = 2;
103         // Default limited role, no create discussion capability enabled.
104         $forum2->cancreatediscussions = false;
106         // Check the forum was correctly created.
107         $this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2',
108                 array('forum1' => $forum1->id, 'forum2' => $forum2->id)));
110         // Enrol the user in two courses.
111         // DataGenerator->enrol_user automatically sets a role for the user with the permission mod/form:viewdiscussion.
112         $this->getDataGenerator()->enrol_user($user->id, $course1->id, null, 'manual');
113         // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
114         $enrol = enrol_get_plugin('manual');
115         $enrolinstances = enrol_get_instances($course2->id, true);
116         foreach ($enrolinstances as $courseenrolinstance) {
117             if ($courseenrolinstance->enrol == "manual") {
118                 $instance2 = $courseenrolinstance;
119                 break;
120             }
121         }
122         $enrol->enrol_user($instance2, $user->id);
124         // Assign capabilities to view forums for forum 2.
125         $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
126         $context2 = context_module::instance($cm2->id);
127         $newrole = create_role('Role 2', 'role2', 'Role 2 description');
128         $roleid2 = $this->assignUserCapability('mod/forum:viewdiscussion', $context2->id, $newrole);
130         // Create what we expect to be returned when querying the two courses.
131         unset($forum1->displaywordcount);
132         unset($forum2->displaywordcount);
134         $expectedforums = array();
135         $expectedforums[$forum1->id] = (array) $forum1;
136         $expectedforums[$forum2->id] = (array) $forum2;
138         // Call the external function passing course ids.
139         $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id));
140         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
141         $this->assertCount(2, $forums);
142         foreach ($forums as $forum) {
143             $this->assertEquals($expectedforums[$forum['id']], $forum);
144         }
146         // Call the external function without passing course id.
147         $forums = mod_forum_external::get_forums_by_courses();
148         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
149         $this->assertCount(2, $forums);
150         foreach ($forums as $forum) {
151             $this->assertEquals($expectedforums[$forum['id']], $forum);
152         }
154         // Unenrol user from second course and alter expected forums.
155         $enrol->unenrol_user($instance2, $user->id);
156         unset($expectedforums[$forum2->id]);
158         // Call the external function without passing course id.
159         $forums = mod_forum_external::get_forums_by_courses();
160         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
161         $this->assertCount(1, $forums);
162         $this->assertEquals($expectedforums[$forum1->id], $forums[0]);
163         $this->assertTrue($forums[0]['cancreatediscussions']);
165         // Change the type of the forum, the user shouldn't be able to add discussions.
166         $DB->set_field('forum', 'type', 'news', array('id' => $forum1->id));
167         $forums = mod_forum_external::get_forums_by_courses();
168         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
169         $this->assertFalse($forums[0]['cancreatediscussions']);
171         // Call for the second course we unenrolled the user from.
172         $forums = mod_forum_external::get_forums_by_courses(array($course2->id));
173         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
174         $this->assertCount(0, $forums);
175     }
177     /**
178      * Test get forum posts
179      */
180     public function test_mod_forum_get_forum_discussion_posts() {
181         global $CFG, $PAGE;
183         $this->resetAfterTest(true);
185         // Set the CFG variable to allow track forums.
186         $CFG->forum_trackreadposts = true;
188         // Create a user who can track forums.
189         $record = new stdClass();
190         $record->trackforums = true;
191         $user1 = self::getDataGenerator()->create_user($record);
192         // Create a bunch of other users to post.
193         $user2 = self::getDataGenerator()->create_user();
194         $user3 = self::getDataGenerator()->create_user();
196         // Set the first created user to the test user.
197         self::setUser($user1);
199         // Create course to add the module.
200         $course1 = self::getDataGenerator()->create_course();
202         // Forum with tracking off.
203         $record = new stdClass();
204         $record->course = $course1->id;
205         $record->trackingtype = FORUM_TRACKING_OFF;
206         $forum1 = self::getDataGenerator()->create_module('forum', $record);
207         $forum1context = context_module::instance($forum1->cmid);
209         // Add discussions to the forums.
210         $record = new stdClass();
211         $record->course = $course1->id;
212         $record->userid = $user1->id;
213         $record->forum = $forum1->id;
214         $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
216         $record = new stdClass();
217         $record->course = $course1->id;
218         $record->userid = $user2->id;
219         $record->forum = $forum1->id;
220         $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
222         // Add 2 replies to the discussion 1 from different users.
223         $record = new stdClass();
224         $record->discussion = $discussion1->id;
225         $record->parent = $discussion1->firstpost;
226         $record->userid = $user2->id;
227         $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
229         $record->parent = $discussion1reply1->id;
230         $record->userid = $user3->id;
231         $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
233         // Enrol the user in the  course.
234         $enrol = enrol_get_plugin('manual');
235         // Following line enrol and assign default role id to the user.
236         // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
237         $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
238         $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
240         // Delete one user, to test that we still receive posts by this user.
241         delete_user($user3);
243         // Create what we expect to be returned when querying the discussion.
244         $expectedposts = array(
245             'posts' => array(),
246             'warnings' => array(),
247         );
249         // User pictures are initially empty, we should get the links once the external function is called.
250         $expectedposts['posts'][] = array(
251             'id' => $discussion1reply2->id,
252             'discussion' => $discussion1reply2->discussion,
253             'parent' => $discussion1reply2->parent,
254             'userid' => (int) $discussion1reply2->userid,
255             'created' => $discussion1reply2->created,
256             'modified' => $discussion1reply2->modified,
257             'mailed' => $discussion1reply2->mailed,
258             'subject' => $discussion1reply2->subject,
259             'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
260                     $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
261             'messageformat' => 1,   // This value is usually changed by external_format_text() function.
262             'messagetrust' => $discussion1reply2->messagetrust,
263             'attachment' => $discussion1reply2->attachment,
264             'totalscore' => $discussion1reply2->totalscore,
265             'mailnow' => $discussion1reply2->mailnow,
266             'children' => array(),
267             'canreply' => true,
268             'postread' => false,
269             'userfullname' => fullname($user3),
270             'userpictureurl' => ''
271         );
273         $expectedposts['posts'][] = array(
274             'id' => $discussion1reply1->id,
275             'discussion' => $discussion1reply1->discussion,
276             'parent' => $discussion1reply1->parent,
277             'userid' => (int) $discussion1reply1->userid,
278             'created' => $discussion1reply1->created,
279             'modified' => $discussion1reply1->modified,
280             'mailed' => $discussion1reply1->mailed,
281             'subject' => $discussion1reply1->subject,
282             'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
283                     $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
284             'messageformat' => 1,   // This value is usually changed by external_format_text() function.
285             'messagetrust' => $discussion1reply1->messagetrust,
286             'attachment' => $discussion1reply1->attachment,
287             'totalscore' => $discussion1reply1->totalscore,
288             'mailnow' => $discussion1reply1->mailnow,
289             'children' => array($discussion1reply2->id),
290             'canreply' => true,
291             'postread' => false,
292             'userfullname' => fullname($user2),
293             'userpictureurl' => ''
294         );
296         // Test a discussion with two additional posts (total 3 posts).
297         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
298         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
299         $this->assertEquals(3, count($posts['posts']));
301         // Generate here the pictures because we need to wait to the external function to init the theme.
302         $userpicture = new user_picture($user3);
303         $userpicture->size = 1; // Size f1.
304         $expectedposts['posts'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
306         $userpicture = new user_picture($user2);
307         $userpicture->size = 1; // Size f1.
308         $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
310         // Unset the initial discussion post.
311         array_pop($posts['posts']);
312         $this->assertEquals($expectedposts, $posts);
314         // Test discussion without additional posts. There should be only one post (the one created by the discussion).
315         $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
316         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
317         $this->assertEquals(1, count($posts['posts']));
319     }
321     /**
322      * Test get forum posts (qanda forum)
323      */
324     public function test_mod_forum_get_forum_discussion_posts_qanda() {
325         global $CFG, $DB;
327         $this->resetAfterTest(true);
329         $record = new stdClass();
330         $user1 = self::getDataGenerator()->create_user($record);
331         $user2 = self::getDataGenerator()->create_user();
333         // Set the first created user to the test user.
334         self::setUser($user1);
336         // Create course to add the module.
337         $course1 = self::getDataGenerator()->create_course();
338         $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
339         $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
341         // Forum with tracking off.
342         $record = new stdClass();
343         $record->course = $course1->id;
344         $record->type = 'qanda';
345         $forum1 = self::getDataGenerator()->create_module('forum', $record);
346         $forum1context = context_module::instance($forum1->cmid);
348         // Add discussions to the forums.
349         $record = new stdClass();
350         $record->course = $course1->id;
351         $record->userid = $user2->id;
352         $record->forum = $forum1->id;
353         $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
355         // Add 1 reply (not the actual user).
356         $record = new stdClass();
357         $record->discussion = $discussion1->id;
358         $record->parent = $discussion1->firstpost;
359         $record->userid = $user2->id;
360         $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
362         // We still see only the original post.
363         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
364         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
365         $this->assertEquals(1, count($posts['posts']));
367         // Add a new reply, the user is going to be able to see only the original post and their new post.
368         $record = new stdClass();
369         $record->discussion = $discussion1->id;
370         $record->parent = $discussion1->firstpost;
371         $record->userid = $user1->id;
372         $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
374         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
375         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
376         $this->assertEquals(2, count($posts['posts']));
378         // Now, we can fake the time of the user post, so he can se the rest of the discussion posts.
379         $discussion1reply2->created -= $CFG->maxeditingtime * 2;
380         $DB->update_record('forum_posts', $discussion1reply2);
382         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
383         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
384         $this->assertEquals(3, count($posts['posts']));
385     }
387     /**
388      * Test get forum discussions paginated
389      */
390     public function test_mod_forum_get_forum_discussions_paginated() {
391         global $USER, $CFG, $DB, $PAGE;
393         $this->resetAfterTest(true);
395         // Set the CFG variable to allow track forums.
396         $CFG->forum_trackreadposts = true;
398         // Create a user who can track forums.
399         $record = new stdClass();
400         $record->trackforums = true;
401         $user1 = self::getDataGenerator()->create_user($record);
402         // Create a bunch of other users to post.
403         $user2 = self::getDataGenerator()->create_user();
404         $user3 = self::getDataGenerator()->create_user();
405         $user4 = self::getDataGenerator()->create_user();
407         // Set the first created user to the test user.
408         self::setUser($user1);
410         // Create courses to add the modules.
411         $course1 = self::getDataGenerator()->create_course();
413         // First forum with tracking off.
414         $record = new stdClass();
415         $record->course = $course1->id;
416         $record->trackingtype = FORUM_TRACKING_OFF;
417         $forum1 = self::getDataGenerator()->create_module('forum', $record);
419         // Add discussions to the forums.
420         $record = new stdClass();
421         $record->course = $course1->id;
422         $record->userid = $user1->id;
423         $record->forum = $forum1->id;
424         $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
426         // Add three replies to the discussion 1 from different users.
427         $record = new stdClass();
428         $record->discussion = $discussion1->id;
429         $record->parent = $discussion1->firstpost;
430         $record->userid = $user2->id;
431         $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
433         $record->parent = $discussion1reply1->id;
434         $record->userid = $user3->id;
435         $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
437         $record->userid = $user4->id;
438         $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
440         // Enrol the user in the first course.
441         $enrol = enrol_get_plugin('manual');
443         // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
444         $enrolinstances = enrol_get_instances($course1->id, true);
445         foreach ($enrolinstances as $courseenrolinstance) {
446             if ($courseenrolinstance->enrol == "manual") {
447                 $instance1 = $courseenrolinstance;
448                 break;
449             }
450         }
451         $enrol->enrol_user($instance1, $user1->id);
453         // Delete one user.
454         delete_user($user4);
456         // Assign capabilities to view discussions for forum 1.
457         $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
458         $context = context_module::instance($cm->id);
459         $newrole = create_role('Role 2', 'role2', 'Role 2 description');
460         $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
462         // Create what we expect to be returned when querying the forums.
464         $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
466         // User pictures are initially empty, we should get the links once the external function is called.
467         $expecteddiscussions = array(
468                 'id' => $discussion1->firstpost,
469                 'name' => $discussion1->name,
470                 'groupid' => $discussion1->groupid,
471                 'timemodified' => $discussion1reply3->created,
472                 'usermodified' => $discussion1reply3->userid,
473                 'timestart' => $discussion1->timestart,
474                 'timeend' => $discussion1->timeend,
475                 'discussion' => $discussion1->id,
476                 'parent' => 0,
477                 'userid' => $discussion1->userid,
478                 'created' => $post1->created,
479                 'modified' => $post1->modified,
480                 'mailed' => $post1->mailed,
481                 'subject' => $post1->subject,
482                 'message' => $post1->message,
483                 'messageformat' => $post1->messageformat,
484                 'messagetrust' => $post1->messagetrust,
485                 'attachment' => $post1->attachment,
486                 'totalscore' => $post1->totalscore,
487                 'mailnow' => $post1->mailnow,
488                 'userfullname' => fullname($user1),
489                 'usermodifiedfullname' => fullname($user4),
490                 'userpictureurl' => '',
491                 'usermodifiedpictureurl' => '',
492                 'numreplies' => 3,
493                 'numunread' => 0,
494                 'pinned' => FORUM_DISCUSSION_UNPINNED
495             );
497         // Call the external function passing forum id.
498         $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
499         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
500         $expectedreturn = array(
501             'discussions' => array($expecteddiscussions),
502             'warnings' => array()
503         );
505         // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
506         $userpicture = new user_picture($user1);
507         $userpicture->size = 1; // Size f1.
508         $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
510         $userpicture = new user_picture($user4);
511         $userpicture->size = 1; // Size f1.
512         $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
514         $this->assertEquals($expectedreturn, $discussions);
516         // Call without required view discussion capability.
517         $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
518         try {
519             mod_forum_external::get_forum_discussions_paginated($forum1->id);
520             $this->fail('Exception expected due to missing capability.');
521         } catch (moodle_exception $e) {
522             $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
523         }
525         // Unenrol user from second course.
526         $enrol->unenrol_user($instance1, $user1->id);
528         // Call for the second course we unenrolled the user from, make sure exception thrown.
529         try {
530             mod_forum_external::get_forum_discussions_paginated($forum1->id);
531             $this->fail('Exception expected due to being unenrolled from the course.');
532         } catch (moodle_exception $e) {
533             $this->assertEquals('requireloginerror', $e->errorcode);
534         }
535     }
537     /**
538      * Test get forum discussions paginated (qanda forums)
539      */
540     public function test_mod_forum_get_forum_discussions_paginated_qanda() {
542         $this->resetAfterTest(true);
544         // Create courses to add the modules.
545         $course = self::getDataGenerator()->create_course();
547         $user1 = self::getDataGenerator()->create_user();
548         $user2 = self::getDataGenerator()->create_user();
550         // First forum with tracking off.
551         $record = new stdClass();
552         $record->course = $course->id;
553         $record->type = 'qanda';
554         $forum = self::getDataGenerator()->create_module('forum', $record);
556         // Add discussions to the forums.
557         $discussionrecord = new stdClass();
558         $discussionrecord->course = $course->id;
559         $discussionrecord->userid = $user2->id;
560         $discussionrecord->forum = $forum->id;
561         $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
563         self::setAdminUser();
564         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
565         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
567         $this->assertCount(1, $discussions['discussions']);
568         $this->assertCount(0, $discussions['warnings']);
570         self::setUser($user1);
571         $this->getDataGenerator()->enrol_user($user1->id, $course->id);
573         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
574         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
576         $this->assertCount(1, $discussions['discussions']);
577         $this->assertCount(0, $discussions['warnings']);
579     }
581     /**
582      * Test add_discussion_post
583      */
584     public function test_add_discussion_post() {
585         global $CFG;
587         $this->resetAfterTest(true);
589         $user = self::getDataGenerator()->create_user();
590         $otheruser = self::getDataGenerator()->create_user();
592         self::setAdminUser();
594         // Create course to add the module.
595         $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
597         // Forum with tracking off.
598         $record = new stdClass();
599         $record->course = $course->id;
600         $forum = self::getDataGenerator()->create_module('forum', $record);
601         $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
602         $forumcontext = context_module::instance($forum->cmid);
604         // Add discussions to the forums.
605         $record = new stdClass();
606         $record->course = $course->id;
607         $record->userid = $user->id;
608         $record->forum = $forum->id;
609         $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
611         // Try to post (user not enrolled).
612         self::setUser($user);
613         try {
614             mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
615             $this->fail('Exception expected due to being unenrolled from the course.');
616         } catch (moodle_exception $e) {
617             $this->assertEquals('requireloginerror', $e->errorcode);
618         }
620         $this->getDataGenerator()->enrol_user($user->id, $course->id);
621         $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
623         $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
624         $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
626         $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
627         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
628         // We receive the discussion and the post.
629         $this->assertEquals(2, count($posts['posts']));
631         $tested = false;
632         foreach ($posts['posts'] as $thispost) {
633             if ($createdpost['postid'] == $thispost['id']) {
634                 $this->assertEquals('some subject', $thispost['subject']);
635                 $this->assertEquals('some text here...', $thispost['message']);
636                 $tested = true;
637             }
638         }
639         $this->assertTrue($tested);
641         // Test inline and regular attachment in post
642         // Create a file in a draft area for inline attachments.
643         $draftidinlineattach = file_get_unused_draft_itemid();
644         $draftidattach = file_get_unused_draft_itemid();
645         self::setUser($user);
646         $usercontext = context_user::instance($user->id);
647         $filepath = '/';
648         $filearea = 'draft';
649         $component = 'user';
650         $filenameimg = 'shouldbeanimage.txt';
651         $filerecordinline = array(
652             'contextid' => $usercontext->id,
653             'component' => $component,
654             'filearea'  => $filearea,
655             'itemid'    => $draftidinlineattach,
656             'filepath'  => $filepath,
657             'filename'  => $filenameimg,
658         );
659         $fs = get_file_storage();
661         // Create a file in a draft area for regular attachments.
662         $filerecordattach = $filerecordinline;
663         $attachfilename = 'attachment.txt';
664         $filerecordattach['filename'] = $attachfilename;
665         $filerecordattach['itemid'] = $draftidattach;
666         $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
667         $fs->create_file_from_string($filerecordattach, 'simple text attachment');
669         $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
670                          array('name' => 'attachmentsid', 'value' => $draftidattach));
671         $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
672                      . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
673                      . '" alt="inlineimage">.';
674         $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
675                                                                $dummytext, $options);
676         $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
678         $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
679         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
680         // We receive the discussion and the post.
681         // Can't guarantee order of posts during tests.
682         $postfound = false;
683         foreach ($posts['posts'] as $thispost) {
684             if ($createdpost['postid'] == $thispost['id']) {
685                 $this->assertEquals($createdpost['postid'], $thispost['id']);
686                 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
687                 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
688                 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
689                 $this->assertContains('pluginfile.php', $thispost['message']);
690                 $postfound = true;
691                 break;
692             }
693         }
695         $this->assertTrue($postfound);
697         // Check not posting in groups the user is not member of.
698         $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
699         groups_add_member($group->id, $otheruser->id);
701         $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
702         $record->forum = $forum->id;
703         $record->userid = $otheruser->id;
704         $record->groupid = $group->id;
705         $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
707         try {
708             mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
709             $this->fail('Exception expected due to invalid permissions for posting.');
710         } catch (moodle_exception $e) {
711             $this->assertEquals('nopostforum', $e->errorcode);
712         }
714     }
716     /*
717      * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
718      */
719     public function test_add_discussion() {
720         global $CFG, $USER;
721         $this->resetAfterTest(true);
723         // Create courses to add the modules.
724         $course = self::getDataGenerator()->create_course();
726         $user1 = self::getDataGenerator()->create_user();
727         $user2 = self::getDataGenerator()->create_user();
729         // First forum with tracking off.
730         $record = new stdClass();
731         $record->course = $course->id;
732         $record->type = 'news';
733         $forum = self::getDataGenerator()->create_module('forum', $record);
735         self::setUser($user1);
736         $this->getDataGenerator()->enrol_user($user1->id, $course->id);
738         try {
739             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
740             $this->fail('Exception expected due to invalid permissions.');
741         } catch (moodle_exception $e) {
742             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
743         }
745         self::setAdminUser();
746         $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
747         $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
749         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
750         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
752         $this->assertCount(1, $discussions['discussions']);
753         $this->assertCount(0, $discussions['warnings']);
755         $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
756         $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
757         $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
758         $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
760         $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
761                                                                 array('options' => array('name' => 'discussionpinned',
762                                                                                          'value' => true)));
763         $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
764         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
765         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
766         $this->assertCount(3, $discussions['discussions']);
767         $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
769         // Test inline and regular attachment in new discussion
770         // Create a file in a draft area for inline attachments.
772         $fs = get_file_storage();
774         $draftidinlineattach = file_get_unused_draft_itemid();
775         $draftidattach = file_get_unused_draft_itemid();
777         $usercontext = context_user::instance($USER->id);
778         $filepath = '/';
779         $filearea = 'draft';
780         $component = 'user';
781         $filenameimg = 'shouldbeanimage.txt';
782         $filerecord = array(
783             'contextid' => $usercontext->id,
784             'component' => $component,
785             'filearea'  => $filearea,
786             'itemid'    => $draftidinlineattach,
787             'filepath'  => $filepath,
788             'filename'  => $filenameimg,
789         );
791         // Create a file in a draft area for regular attachments.
792         $filerecordattach = $filerecord;
793         $attachfilename = 'attachment.txt';
794         $filerecordattach['filename'] = $attachfilename;
795         $filerecordattach['itemid'] = $draftidattach;
796         $fs->create_file_from_string($filerecord, 'image contents (not really)');
797         $fs->create_file_from_string($filerecordattach, 'simple text attachment');
799         $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
800                     "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
801                     '" alt="inlineimage">.';
803         $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
804                          array('name' => 'attachmentsid', 'value' => $draftidattach));
805         $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
806                                                                 $dummytext, -1, $options);
807         $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
809         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
810         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
812         $this->assertCount(4, $discussions['discussions']);
813         $this->assertCount(0, $createddiscussion['warnings']);
814         // Can't guarantee order of posts during tests.
815         $postfound = false;
816         foreach ($discussions['discussions'] as $thisdiscussion) {
817             if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
818                 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
819                 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
820                 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
821                 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
822                 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
823                 $postfound = true;
824                 break;
825             }
826         }
828         $this->assertTrue($postfound);
829     }
831     /**
832      * Test adding discussions in a course with gorups
833      */
834     public function test_add_discussion_in_course_with_groups() {
835         global $CFG;
837         $this->resetAfterTest(true);
839         // Create course to add the module.
840         $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
841         $user = self::getDataGenerator()->create_user();
842         $this->getDataGenerator()->enrol_user($user->id, $course->id);
844         // Forum forcing separate gropus.
845         $record = new stdClass();
846         $record->course = $course->id;
847         $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
849         // Try to post (user not enrolled).
850         self::setUser($user);
852         // The user is not enroled in any group, try to post in a forum with separate groups.
853         try {
854             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
855             $this->fail('Exception expected due to invalid group permissions.');
856         } catch (moodle_exception $e) {
857             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
858         }
860         try {
861             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
862             $this->fail('Exception expected due to invalid group permissions.');
863         } catch (moodle_exception $e) {
864             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
865         }
867         // Create a group.
868         $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
870         // Try to post in a group the user is not enrolled.
871         try {
872             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
873             $this->fail('Exception expected due to invalid group permissions.');
874         } catch (moodle_exception $e) {
875             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
876         }
878         // Add the user to a group.
879         groups_add_member($group->id, $user->id);
881         // Try to post in a group the user is not enrolled.
882         try {
883             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
884             $this->fail('Exception expected due to invalid group.');
885         } catch (moodle_exception $e) {
886             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
887         }
889         // Nost add the discussion using a valid group.
890         $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
891         $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
893         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
894         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
896         $this->assertCount(1, $discussions['discussions']);
897         $this->assertCount(0, $discussions['warnings']);
898         $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
899         $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
901         // Now add a discussions without indicating a group. The function should guess the correct group.
902         $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
903         $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
905         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
906         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
908         $this->assertCount(2, $discussions['discussions']);
909         $this->assertCount(0, $discussions['warnings']);
910         $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
911         $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
913         // Enrol the same user in other group.
914         $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
915         groups_add_member($group2->id, $user->id);
917         // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
918         $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
919         $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
921         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
922         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
924         $this->assertCount(3, $discussions['discussions']);
925         $this->assertCount(0, $discussions['warnings']);
926         $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
927         $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
928         $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
930     }
932     /*
933      * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
934      */
935     public function test_can_add_discussion() {
937         $this->resetAfterTest(true);
939         // Create courses to add the modules.
940         $course = self::getDataGenerator()->create_course();
942         $user = self::getDataGenerator()->create_user();
944         // First forum with tracking off.
945         $record = new stdClass();
946         $record->course = $course->id;
947         $record->type = 'news';
948         $forum = self::getDataGenerator()->create_module('forum', $record);
950         // User with no permissions to add in a news forum.
951         self::setUser($user);
952         $this->getDataGenerator()->enrol_user($user->id, $course->id);
954         $result = mod_forum_external::can_add_discussion($forum->id);
955         $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
956         $this->assertFalse($result['status']);
958         self::setAdminUser();
959         $result = mod_forum_external::can_add_discussion($forum->id);
960         $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
961         $this->assertTrue($result['status']);
963     }