MDL-58220 oauth2: missing param confirmtokenexpires
[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');
31 require_once($CFG->dirroot . '/mod/forum/lib.php');
33 class mod_forum_external_testcase extends externallib_advanced_testcase {
35     /**
36      * Tests set up
37      */
38     protected function setUp() {
39         global $CFG;
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');
46     }
48     public function tearDown() {
49         // We must clear the subscription caches. This has to be done both before each test, and after in case of other
50         // tests using these functions.
51         \mod_forum\subscriptions::reset_forum_cache();
52     }
54     /**
55      * Test get forums
56      */
57     public function test_mod_forum_get_forums_by_courses() {
58         global $USER, $CFG, $DB;
60         $this->resetAfterTest(true);
62         // Create a user.
63         $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
65         // Set to the user.
66         self::setUser($user);
68         // Create courses to add the modules.
69         $course1 = self::getDataGenerator()->create_course();
70         $course2 = self::getDataGenerator()->create_course();
72         // First forum.
73         $record = new stdClass();
74         $record->introformat = FORMAT_HTML;
75         $record->course = $course1->id;
76         $record->trackingtype = FORUM_TRACKING_FORCED;
77         $forum1 = self::getDataGenerator()->create_module('forum', $record);
79         // Second forum.
80         $record = new stdClass();
81         $record->introformat = FORMAT_HTML;
82         $record->course = $course2->id;
83         $record->trackingtype = FORUM_TRACKING_OFF;
84         $forum2 = self::getDataGenerator()->create_module('forum', $record);
85         $forum2->introfiles = [];
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->introfiles = [];
99         $record = new stdClass();
100         $record->course = $course2->id;
101         $record->userid = $user->id;
102         $record->forum = $forum2->id;
103         $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
104         $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
105         // Expect two discussions.
106         $forum2->numdiscussions = 2;
107         // Default limited role, no create discussion capability enabled.
108         $forum2->cancreatediscussions = false;
109         $forum2->istracked = false;
111         // Check the forum was correctly created.
112         $this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2',
113                 array('forum1' => $forum1->id, 'forum2' => $forum2->id)));
115         // Enrol the user in two courses.
116         // DataGenerator->enrol_user automatically sets a role for the user with the permission mod/form:viewdiscussion.
117         $this->getDataGenerator()->enrol_user($user->id, $course1->id, null, 'manual');
118         // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
119         $enrol = enrol_get_plugin('manual');
120         $enrolinstances = enrol_get_instances($course2->id, true);
121         foreach ($enrolinstances as $courseenrolinstance) {
122             if ($courseenrolinstance->enrol == "manual") {
123                 $instance2 = $courseenrolinstance;
124                 break;
125             }
126         }
127         $enrol->enrol_user($instance2, $user->id);
129         // Assign capabilities to view forums for forum 2.
130         $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
131         $context2 = context_module::instance($cm2->id);
132         $newrole = create_role('Role 2', 'role2', 'Role 2 description');
133         $roleid2 = $this->assignUserCapability('mod/forum:viewdiscussion', $context2->id, $newrole);
135         // Create what we expect to be returned when querying the two courses.
136         unset($forum1->displaywordcount);
137         unset($forum2->displaywordcount);
139         $expectedforums = array();
140         $expectedforums[$forum1->id] = (array) $forum1;
141         $expectedforums[$forum2->id] = (array) $forum2;
143         // Call the external function passing course ids.
144         $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id));
145         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
146         $this->assertCount(2, $forums);
147         foreach ($forums as $forum) {
148             $this->assertEquals($expectedforums[$forum['id']], $forum);
149         }
151         // Call the external function without passing course id.
152         $forums = mod_forum_external::get_forums_by_courses();
153         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
154         $this->assertCount(2, $forums);
155         foreach ($forums as $forum) {
156             $this->assertEquals($expectedforums[$forum['id']], $forum);
157         }
159         // Unenrol user from second course and alter expected forums.
160         $enrol->unenrol_user($instance2, $user->id);
161         unset($expectedforums[$forum2->id]);
163         // Call the external function without passing course id.
164         $forums = mod_forum_external::get_forums_by_courses();
165         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
166         $this->assertCount(1, $forums);
167         $this->assertEquals($expectedforums[$forum1->id], $forums[0]);
168         $this->assertTrue($forums[0]['cancreatediscussions']);
170         // Change the type of the forum, the user shouldn't be able to add discussions.
171         $DB->set_field('forum', 'type', 'news', array('id' => $forum1->id));
172         $forums = mod_forum_external::get_forums_by_courses();
173         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
174         $this->assertFalse($forums[0]['cancreatediscussions']);
176         // Call for the second course we unenrolled the user from.
177         $forums = mod_forum_external::get_forums_by_courses(array($course2->id));
178         $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
179         $this->assertCount(0, $forums);
180     }
182     /**
183      * Test get forum posts
184      */
185     public function test_mod_forum_get_forum_discussion_posts() {
186         global $CFG, $PAGE;
188         $this->resetAfterTest(true);
190         // Set the CFG variable to allow track forums.
191         $CFG->forum_trackreadposts = true;
193         // Create a user who can track forums.
194         $record = new stdClass();
195         $record->trackforums = true;
196         $user1 = self::getDataGenerator()->create_user($record);
197         // Create a bunch of other users to post.
198         $user2 = self::getDataGenerator()->create_user();
199         $user3 = self::getDataGenerator()->create_user();
201         // Set the first created user to the test user.
202         self::setUser($user1);
204         // Create course to add the module.
205         $course1 = self::getDataGenerator()->create_course();
207         // Forum with tracking off.
208         $record = new stdClass();
209         $record->course = $course1->id;
210         $record->trackingtype = FORUM_TRACKING_OFF;
211         $forum1 = self::getDataGenerator()->create_module('forum', $record);
212         $forum1context = context_module::instance($forum1->cmid);
214         // Forum with tracking enabled.
215         $record = new stdClass();
216         $record->course = $course1->id;
217         $forum2 = self::getDataGenerator()->create_module('forum', $record);
218         $forum2context = context_module::instance($forum2->cmid);
220         // Add discussions to the forums.
221         $record = new stdClass();
222         $record->course = $course1->id;
223         $record->userid = $user1->id;
224         $record->forum = $forum1->id;
225         $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
227         $record = new stdClass();
228         $record->course = $course1->id;
229         $record->userid = $user2->id;
230         $record->forum = $forum1->id;
231         $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
233         $record = new stdClass();
234         $record->course = $course1->id;
235         $record->userid = $user2->id;
236         $record->forum = $forum2->id;
237         $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
239         // Add 2 replies to the discussion 1 from different users.
240         $record = new stdClass();
241         $record->discussion = $discussion1->id;
242         $record->parent = $discussion1->firstpost;
243         $record->userid = $user2->id;
244         $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
245         $filename = 'shouldbeanimage.jpg';
246         // Add a fake inline image to the post.
247         $filerecordinline = array(
248             'contextid' => $forum1context->id,
249             'component' => 'mod_forum',
250             'filearea'  => 'post',
251             'itemid'    => $discussion1reply1->id,
252             'filepath'  => '/',
253             'filename'  => $filename,
254         );
255         $fs = get_file_storage();
256         $timepost = time();
257         $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
259         $record->parent = $discussion1reply1->id;
260         $record->userid = $user3->id;
261         $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
263         // Enrol the user in the  course.
264         $enrol = enrol_get_plugin('manual');
265         // Following line enrol and assign default role id to the user.
266         // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
267         $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
268         $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
270         // Delete one user, to test that we still receive posts by this user.
271         delete_user($user3);
273         // Create what we expect to be returned when querying the discussion.
274         $expectedposts = array(
275             'posts' => array(),
276             'warnings' => array(),
277         );
279         // User pictures are initially empty, we should get the links once the external function is called.
280         $expectedposts['posts'][] = array(
281             'id' => $discussion1reply2->id,
282             'discussion' => $discussion1reply2->discussion,
283             'parent' => $discussion1reply2->parent,
284             'userid' => (int) $discussion1reply2->userid,
285             'created' => $discussion1reply2->created,
286             'modified' => $discussion1reply2->modified,
287             'mailed' => $discussion1reply2->mailed,
288             'subject' => $discussion1reply2->subject,
289             'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
290                     $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
291             'messageformat' => 1,   // This value is usually changed by external_format_text() function.
292             'messagetrust' => $discussion1reply2->messagetrust,
293             'attachment' => $discussion1reply2->attachment,
294             'totalscore' => $discussion1reply2->totalscore,
295             'mailnow' => $discussion1reply2->mailnow,
296             'children' => array(),
297             'canreply' => true,
298             'postread' => false,
299             'userfullname' => fullname($user3),
300             'userpictureurl' => ''
301         );
303         $expectedposts['posts'][] = array(
304             'id' => $discussion1reply1->id,
305             'discussion' => $discussion1reply1->discussion,
306             'parent' => $discussion1reply1->parent,
307             'userid' => (int) $discussion1reply1->userid,
308             'created' => $discussion1reply1->created,
309             'modified' => $discussion1reply1->modified,
310             'mailed' => $discussion1reply1->mailed,
311             'subject' => $discussion1reply1->subject,
312             'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
313                     $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
314             'messageformat' => 1,   // This value is usually changed by external_format_text() function.
315             'messagetrust' => $discussion1reply1->messagetrust,
316             'attachment' => $discussion1reply1->attachment,
317             'messageinlinefiles' => array(
318                 array(
319                     'filename' => $filename,
320                     'filepath' => '/',
321                     'filesize' => '27',
322                     'fileurl' => moodle_url::make_webservice_pluginfile_url($forum1context->id, 'mod_forum', 'post',
323                                     $discussion1reply1->id, '/', $filename),
324                     'timemodified' => $timepost,
325                     'mimetype' => 'image/jpeg',
326                 )
327             ),
328             'totalscore' => $discussion1reply1->totalscore,
329             'mailnow' => $discussion1reply1->mailnow,
330             'children' => array($discussion1reply2->id),
331             'canreply' => true,
332             'postread' => false,
333             'userfullname' => fullname($user2),
334             'userpictureurl' => ''
335         );
337         // Test a discussion with two additional posts (total 3 posts).
338         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
339         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
340         $this->assertEquals(3, count($posts['posts']));
342         // Generate here the pictures because we need to wait to the external function to init the theme.
343         $userpicture = new user_picture($user3);
344         $userpicture->size = 1; // Size f1.
345         $expectedposts['posts'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
347         $userpicture = new user_picture($user2);
348         $userpicture->size = 1; // Size f1.
349         $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
351         // Unset the initial discussion post.
352         array_pop($posts['posts']);
353         $this->assertEquals($expectedposts, $posts);
355         // Test discussion without additional posts. There should be only one post (the one created by the discussion).
356         $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
357         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
358         $this->assertEquals(1, count($posts['posts']));
360         // Test discussion tracking on not tracked forum.
361         $result = mod_forum_external::view_forum_discussion($discussion1->id);
362         $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
363         $this->assertTrue($result['status']);
364         $this->assertEmpty($result['warnings']);
366         // Test posts have not been marked as read.
367         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
368         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
369         foreach ($posts['posts'] as $post) {
370             $this->assertFalse($post['postread']);
371         }
373         // Test discussion tracking on tracked forum.
374         $result = mod_forum_external::view_forum_discussion($discussion3->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 been marked as read.
380         $posts = mod_forum_external::get_forum_discussion_posts($discussion3->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->assertTrue($post['postread']);
384         }
385     }
387     /**
388      * Test get forum posts (qanda forum)
389      */
390     public function test_mod_forum_get_forum_discussion_posts_qanda() {
391         global $CFG, $DB;
393         $this->resetAfterTest(true);
395         $record = new stdClass();
396         $user1 = self::getDataGenerator()->create_user($record);
397         $user2 = self::getDataGenerator()->create_user();
399         // Set the first created user to the test user.
400         self::setUser($user1);
402         // Create course to add the module.
403         $course1 = self::getDataGenerator()->create_course();
404         $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
405         $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
407         // Forum with tracking off.
408         $record = new stdClass();
409         $record->course = $course1->id;
410         $record->type = 'qanda';
411         $forum1 = self::getDataGenerator()->create_module('forum', $record);
412         $forum1context = context_module::instance($forum1->cmid);
414         // Add discussions to the forums.
415         $record = new stdClass();
416         $record->course = $course1->id;
417         $record->userid = $user2->id;
418         $record->forum = $forum1->id;
419         $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
421         // Add 1 reply (not the actual user).
422         $record = new stdClass();
423         $record->discussion = $discussion1->id;
424         $record->parent = $discussion1->firstpost;
425         $record->userid = $user2->id;
426         $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
428         // We still see only the original post.
429         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
430         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
431         $this->assertEquals(1, count($posts['posts']));
433         // Add a new reply, the user is going to be able to see only the original post and their new post.
434         $record = new stdClass();
435         $record->discussion = $discussion1->id;
436         $record->parent = $discussion1->firstpost;
437         $record->userid = $user1->id;
438         $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
440         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
441         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
442         $this->assertEquals(2, count($posts['posts']));
444         // Now, we can fake the time of the user post, so he can se the rest of the discussion posts.
445         $discussion1reply2->created -= $CFG->maxeditingtime * 2;
446         $DB->update_record('forum_posts', $discussion1reply2);
448         $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
449         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
450         $this->assertEquals(3, count($posts['posts']));
451     }
453     /**
454      * Test get forum discussions paginated
455      */
456     public function test_mod_forum_get_forum_discussions_paginated() {
457         global $USER, $CFG, $DB, $PAGE;
459         $this->resetAfterTest(true);
461         // Set the CFG variable to allow track forums.
462         $CFG->forum_trackreadposts = true;
464         // Create a user who can track forums.
465         $record = new stdClass();
466         $record->trackforums = true;
467         $user1 = self::getDataGenerator()->create_user($record);
468         // Create a bunch of other users to post.
469         $user2 = self::getDataGenerator()->create_user();
470         $user3 = self::getDataGenerator()->create_user();
471         $user4 = self::getDataGenerator()->create_user();
473         // Set the first created user to the test user.
474         self::setUser($user1);
476         // Create courses to add the modules.
477         $course1 = self::getDataGenerator()->create_course();
479         // First forum with tracking off.
480         $record = new stdClass();
481         $record->course = $course1->id;
482         $record->trackingtype = FORUM_TRACKING_OFF;
483         $forum1 = self::getDataGenerator()->create_module('forum', $record);
485         // Add discussions to the forums.
486         $record = new stdClass();
487         $record->course = $course1->id;
488         $record->userid = $user1->id;
489         $record->forum = $forum1->id;
490         $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
492         // Add three replies to the discussion 1 from different users.
493         $record = new stdClass();
494         $record->discussion = $discussion1->id;
495         $record->parent = $discussion1->firstpost;
496         $record->userid = $user2->id;
497         $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
499         $record->parent = $discussion1reply1->id;
500         $record->userid = $user3->id;
501         $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
503         $record->userid = $user4->id;
504         $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
506         // Enrol the user in the first course.
507         $enrol = enrol_get_plugin('manual');
509         // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
510         $enrolinstances = enrol_get_instances($course1->id, true);
511         foreach ($enrolinstances as $courseenrolinstance) {
512             if ($courseenrolinstance->enrol == "manual") {
513                 $instance1 = $courseenrolinstance;
514                 break;
515             }
516         }
517         $enrol->enrol_user($instance1, $user1->id);
519         // Delete one user.
520         delete_user($user4);
522         // Assign capabilities to view discussions for forum 1.
523         $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
524         $context = context_module::instance($cm->id);
525         $newrole = create_role('Role 2', 'role2', 'Role 2 description');
526         $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
528         // Create what we expect to be returned when querying the forums.
530         $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
532         // User pictures are initially empty, we should get the links once the external function is called.
533         $expecteddiscussions = array(
534                 'id' => $discussion1->firstpost,
535                 'name' => $discussion1->name,
536                 'groupid' => $discussion1->groupid,
537                 'timemodified' => $discussion1reply3->created,
538                 'usermodified' => $discussion1reply3->userid,
539                 'timestart' => $discussion1->timestart,
540                 'timeend' => $discussion1->timeend,
541                 'discussion' => $discussion1->id,
542                 'parent' => 0,
543                 'userid' => $discussion1->userid,
544                 'created' => $post1->created,
545                 'modified' => $post1->modified,
546                 'mailed' => $post1->mailed,
547                 'subject' => $post1->subject,
548                 'message' => $post1->message,
549                 'messageformat' => $post1->messageformat,
550                 'messagetrust' => $post1->messagetrust,
551                 'attachment' => $post1->attachment,
552                 'totalscore' => $post1->totalscore,
553                 'mailnow' => $post1->mailnow,
554                 'userfullname' => fullname($user1),
555                 'usermodifiedfullname' => fullname($user4),
556                 'userpictureurl' => '',
557                 'usermodifiedpictureurl' => '',
558                 'numreplies' => 3,
559                 'numunread' => 0,
560                 'pinned' => FORUM_DISCUSSION_UNPINNED,
561                 'locked' => false,
562                 'canreply' => false,
563             );
565         // Call the external function passing forum id.
566         $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
567         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
568         $expectedreturn = array(
569             'discussions' => array($expecteddiscussions),
570             'warnings' => array()
571         );
573         // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
574         $userpicture = new user_picture($user1);
575         $userpicture->size = 1; // Size f1.
576         $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
578         $userpicture = new user_picture($user4);
579         $userpicture->size = 1; // Size f1.
580         $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
582         $this->assertEquals($expectedreturn, $discussions);
584         // Call without required view discussion capability.
585         $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
586         try {
587             mod_forum_external::get_forum_discussions_paginated($forum1->id);
588             $this->fail('Exception expected due to missing capability.');
589         } catch (moodle_exception $e) {
590             $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
591         }
593         // Unenrol user from second course.
594         $enrol->unenrol_user($instance1, $user1->id);
596         // Call for the second course we unenrolled the user from, make sure exception thrown.
597         try {
598             mod_forum_external::get_forum_discussions_paginated($forum1->id);
599             $this->fail('Exception expected due to being unenrolled from the course.');
600         } catch (moodle_exception $e) {
601             $this->assertEquals('requireloginerror', $e->errorcode);
602         }
603     }
605     /**
606      * Test get forum discussions paginated (qanda forums)
607      */
608     public function test_mod_forum_get_forum_discussions_paginated_qanda() {
610         $this->resetAfterTest(true);
612         // Create courses to add the modules.
613         $course = self::getDataGenerator()->create_course();
615         $user1 = self::getDataGenerator()->create_user();
616         $user2 = self::getDataGenerator()->create_user();
618         // First forum with tracking off.
619         $record = new stdClass();
620         $record->course = $course->id;
621         $record->type = 'qanda';
622         $forum = self::getDataGenerator()->create_module('forum', $record);
624         // Add discussions to the forums.
625         $discussionrecord = new stdClass();
626         $discussionrecord->course = $course->id;
627         $discussionrecord->userid = $user2->id;
628         $discussionrecord->forum = $forum->id;
629         $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
631         self::setAdminUser();
632         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
633         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
635         $this->assertCount(1, $discussions['discussions']);
636         $this->assertCount(0, $discussions['warnings']);
638         self::setUser($user1);
639         $this->getDataGenerator()->enrol_user($user1->id, $course->id);
641         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
642         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
644         $this->assertCount(1, $discussions['discussions']);
645         $this->assertCount(0, $discussions['warnings']);
647     }
649     /**
650      * Test add_discussion_post
651      */
652     public function test_add_discussion_post() {
653         global $CFG;
655         $this->resetAfterTest(true);
657         $user = self::getDataGenerator()->create_user();
658         $otheruser = self::getDataGenerator()->create_user();
660         self::setAdminUser();
662         // Create course to add the module.
663         $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
665         // Forum with tracking off.
666         $record = new stdClass();
667         $record->course = $course->id;
668         $forum = self::getDataGenerator()->create_module('forum', $record);
669         $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
670         $forumcontext = context_module::instance($forum->cmid);
672         // Add discussions to the forums.
673         $record = new stdClass();
674         $record->course = $course->id;
675         $record->userid = $user->id;
676         $record->forum = $forum->id;
677         $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
679         // Try to post (user not enrolled).
680         self::setUser($user);
681         try {
682             mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
683             $this->fail('Exception expected due to being unenrolled from the course.');
684         } catch (moodle_exception $e) {
685             $this->assertEquals('requireloginerror', $e->errorcode);
686         }
688         $this->getDataGenerator()->enrol_user($user->id, $course->id);
689         $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
691         $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
692         $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
694         $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
695         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
696         // We receive the discussion and the post.
697         $this->assertEquals(2, count($posts['posts']));
699         $tested = false;
700         foreach ($posts['posts'] as $thispost) {
701             if ($createdpost['postid'] == $thispost['id']) {
702                 $this->assertEquals('some subject', $thispost['subject']);
703                 $this->assertEquals('some text here...', $thispost['message']);
704                 $tested = true;
705             }
706         }
707         $this->assertTrue($tested);
709         // Test inline and regular attachment in post
710         // Create a file in a draft area for inline attachments.
711         $draftidinlineattach = file_get_unused_draft_itemid();
712         $draftidattach = file_get_unused_draft_itemid();
713         self::setUser($user);
714         $usercontext = context_user::instance($user->id);
715         $filepath = '/';
716         $filearea = 'draft';
717         $component = 'user';
718         $filenameimg = 'shouldbeanimage.txt';
719         $filerecordinline = array(
720             'contextid' => $usercontext->id,
721             'component' => $component,
722             'filearea'  => $filearea,
723             'itemid'    => $draftidinlineattach,
724             'filepath'  => $filepath,
725             'filename'  => $filenameimg,
726         );
727         $fs = get_file_storage();
729         // Create a file in a draft area for regular attachments.
730         $filerecordattach = $filerecordinline;
731         $attachfilename = 'attachment.txt';
732         $filerecordattach['filename'] = $attachfilename;
733         $filerecordattach['itemid'] = $draftidattach;
734         $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
735         $fs->create_file_from_string($filerecordattach, 'simple text attachment');
737         $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
738                          array('name' => 'attachmentsid', 'value' => $draftidattach));
739         $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
740                      . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
741                      . '" alt="inlineimage">.';
742         $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
743                                                                $dummytext, $options);
744         $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
746         $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
747         $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
748         // We receive the discussion and the post.
749         // Can't guarantee order of posts during tests.
750         $postfound = false;
751         foreach ($posts['posts'] as $thispost) {
752             if ($createdpost['postid'] == $thispost['id']) {
753                 $this->assertEquals($createdpost['postid'], $thispost['id']);
754                 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
755                 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
756                 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
757                 $this->assertContains('pluginfile.php', $thispost['message']);
758                 $postfound = true;
759                 break;
760             }
761         }
763         $this->assertTrue($postfound);
765         // Check not posting in groups the user is not member of.
766         $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
767         groups_add_member($group->id, $otheruser->id);
769         $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
770         $record->forum = $forum->id;
771         $record->userid = $otheruser->id;
772         $record->groupid = $group->id;
773         $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
775         try {
776             mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
777             $this->fail('Exception expected due to invalid permissions for posting.');
778         } catch (moodle_exception $e) {
779             $this->assertEquals('nopostforum', $e->errorcode);
780         }
782     }
784     /*
785      * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
786      */
787     public function test_add_discussion() {
788         global $CFG, $USER;
789         $this->resetAfterTest(true);
791         // Create courses to add the modules.
792         $course = self::getDataGenerator()->create_course();
794         $user1 = self::getDataGenerator()->create_user();
795         $user2 = self::getDataGenerator()->create_user();
797         // First forum with tracking off.
798         $record = new stdClass();
799         $record->course = $course->id;
800         $record->type = 'news';
801         $forum = self::getDataGenerator()->create_module('forum', $record);
803         self::setUser($user1);
804         $this->getDataGenerator()->enrol_user($user1->id, $course->id);
806         try {
807             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
808             $this->fail('Exception expected due to invalid permissions.');
809         } catch (moodle_exception $e) {
810             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
811         }
813         self::setAdminUser();
814         $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
815         $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
817         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
818         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
820         $this->assertCount(1, $discussions['discussions']);
821         $this->assertCount(0, $discussions['warnings']);
823         $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
824         $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
825         $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
826         $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
828         $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
829                                                                 array('options' => array('name' => 'discussionpinned',
830                                                                                          'value' => true)));
831         $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
832         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
833         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
834         $this->assertCount(3, $discussions['discussions']);
835         $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
837         // Test inline and regular attachment in new discussion
838         // Create a file in a draft area for inline attachments.
840         $fs = get_file_storage();
842         $draftidinlineattach = file_get_unused_draft_itemid();
843         $draftidattach = file_get_unused_draft_itemid();
845         $usercontext = context_user::instance($USER->id);
846         $filepath = '/';
847         $filearea = 'draft';
848         $component = 'user';
849         $filenameimg = 'shouldbeanimage.txt';
850         $filerecord = array(
851             'contextid' => $usercontext->id,
852             'component' => $component,
853             'filearea'  => $filearea,
854             'itemid'    => $draftidinlineattach,
855             'filepath'  => $filepath,
856             'filename'  => $filenameimg,
857         );
859         // Create a file in a draft area for regular attachments.
860         $filerecordattach = $filerecord;
861         $attachfilename = 'attachment.txt';
862         $filerecordattach['filename'] = $attachfilename;
863         $filerecordattach['itemid'] = $draftidattach;
864         $fs->create_file_from_string($filerecord, 'image contents (not really)');
865         $fs->create_file_from_string($filerecordattach, 'simple text attachment');
867         $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
868                     "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
869                     '" alt="inlineimage">.';
871         $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
872                          array('name' => 'attachmentsid', 'value' => $draftidattach));
873         $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
874                                                                 $dummytext, -1, $options);
875         $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
877         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
878         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
880         $this->assertCount(4, $discussions['discussions']);
881         $this->assertCount(0, $createddiscussion['warnings']);
882         // Can't guarantee order of posts during tests.
883         $postfound = false;
884         foreach ($discussions['discussions'] as $thisdiscussion) {
885             if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
886                 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
887                 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
888                 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
889                 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
890                 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
891                 $postfound = true;
892                 break;
893             }
894         }
896         $this->assertTrue($postfound);
897     }
899     /**
900      * Test adding discussions in a course with gorups
901      */
902     public function test_add_discussion_in_course_with_groups() {
903         global $CFG;
905         $this->resetAfterTest(true);
907         // Create course to add the module.
908         $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
909         $user = self::getDataGenerator()->create_user();
910         $this->getDataGenerator()->enrol_user($user->id, $course->id);
912         // Forum forcing separate gropus.
913         $record = new stdClass();
914         $record->course = $course->id;
915         $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
917         // Try to post (user not enrolled).
918         self::setUser($user);
920         // The user is not enroled in any group, try to post in a forum with separate groups.
921         try {
922             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
923             $this->fail('Exception expected due to invalid group permissions.');
924         } catch (moodle_exception $e) {
925             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
926         }
928         try {
929             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
930             $this->fail('Exception expected due to invalid group permissions.');
931         } catch (moodle_exception $e) {
932             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
933         }
935         // Create a group.
936         $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
938         // Try to post in a group the user is not enrolled.
939         try {
940             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
941             $this->fail('Exception expected due to invalid group permissions.');
942         } catch (moodle_exception $e) {
943             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
944         }
946         // Add the user to a group.
947         groups_add_member($group->id, $user->id);
949         // Try to post in a group the user is not enrolled.
950         try {
951             mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
952             $this->fail('Exception expected due to invalid group.');
953         } catch (moodle_exception $e) {
954             $this->assertEquals('cannotcreatediscussion', $e->errorcode);
955         }
957         // Nost add the discussion using a valid group.
958         $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
959         $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
961         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
962         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
964         $this->assertCount(1, $discussions['discussions']);
965         $this->assertCount(0, $discussions['warnings']);
966         $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
967         $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
969         // Now add a discussions without indicating a group. The function should guess the correct group.
970         $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
971         $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
973         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
974         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
976         $this->assertCount(2, $discussions['discussions']);
977         $this->assertCount(0, $discussions['warnings']);
978         $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
979         $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
981         // Enrol the same user in other group.
982         $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
983         groups_add_member($group2->id, $user->id);
985         // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
986         $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
987         $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
989         $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
990         $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
992         $this->assertCount(3, $discussions['discussions']);
993         $this->assertCount(0, $discussions['warnings']);
994         $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
995         $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
996         $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
998     }
1000     /*
1001      * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
1002      */
1003     public function test_can_add_discussion() {
1004         global $DB;
1005         $this->resetAfterTest(true);
1007         // Create courses to add the modules.
1008         $course = self::getDataGenerator()->create_course();
1010         $user = self::getDataGenerator()->create_user();
1012         // First forum with tracking off.
1013         $record = new stdClass();
1014         $record->course = $course->id;
1015         $record->type = 'news';
1016         $forum = self::getDataGenerator()->create_module('forum', $record);
1018         // User with no permissions to add in a news forum.
1019         self::setUser($user);
1020         $this->getDataGenerator()->enrol_user($user->id, $course->id);
1022         $result = mod_forum_external::can_add_discussion($forum->id);
1023         $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1024         $this->assertFalse($result['status']);
1025         $this->assertFalse($result['canpindiscussions']);
1026         $this->assertTrue($result['cancreateattachment']);
1028         // Disable attachments.
1029         $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id));
1030         $result = mod_forum_external::can_add_discussion($forum->id);
1031         $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1032         $this->assertFalse($result['status']);
1033         $this->assertFalse($result['canpindiscussions']);
1034         $this->assertFalse($result['cancreateattachment']);
1035         $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id));    // Enable attachments again.
1037         self::setAdminUser();
1038         $result = mod_forum_external::can_add_discussion($forum->id);
1039         $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1040         $this->assertTrue($result['status']);
1041         $this->assertTrue($result['canpindiscussions']);
1042         $this->assertTrue($result['cancreateattachment']);
1044     }