MDL-55982 mod_forum: Add time-based discussion locking
[moodle.git] / mod / forum / tests / externallib_test.php
CommitLineData
2b9fe87d 1<?php
2b9fe87d
MN
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * The module forums external functions unit tests
19 *
20 * @package mod_forum
21 * @category external
22 * @copyright 2012 Mark Nelson <markn@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28global $CFG;
29
30require_once($CFG->dirroot . '/webservice/tests/helpers.php');
31
32class mod_forum_external_testcase extends externallib_advanced_testcase {
33
34 /**
35 * Tests set up
36 */
37 protected function setUp() {
38 global $CFG;
39
59075a43
AN
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();
43
2b9fe87d
MN
44 require_once($CFG->dirroot . '/mod/forum/externallib.php');
45 }
46
59075a43
AN
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 }
52
2b9fe87d
MN
53 /**
54 * Test get forums
55 */
56 public function test_mod_forum_get_forums_by_courses() {
57 global $USER, $CFG, $DB;
58
59 $this->resetAfterTest(true);
60
61 // Create a user.
62 $user = self::getDataGenerator()->create_user();
63
64 // Set to the user.
65 self::setUser($user);
66
67 // Create courses to add the modules.
68 $course1 = self::getDataGenerator()->create_course();
69 $course2 = self::getDataGenerator()->create_course();
70
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);
76
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);
7ef49bd3 82 $forum2->introfiles = [];
2b9fe87d 83
7ea6ada3
JL
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;
ea5b910b 92 $forum1->cancreatediscussions = true;
7ef49bd3 93 $forum1->introfiles = [];
7ea6ada3
JL
94
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;
ea5b910b
JL
103 // Default limited role, no create discussion capability enabled.
104 $forum2->cancreatediscussions = false;
7ea6ada3 105
2b9fe87d
MN
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)));
109
110 // Enrol the user in two courses.
909f27ac
JM
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.
2b9fe87d 114 $enrol = enrol_get_plugin('manual');
2b9fe87d
MN
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);
123
2b9fe87d 124 // Assign capabilities to view forums for forum 2.
74b63eae 125 $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
2b9fe87d
MN
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);
129
130 // Create what we expect to be returned when querying the two courses.
c8f1d8a0
JL
131 unset($forum1->displaywordcount);
132 unset($forum2->displaywordcount);
133
2b9fe87d
MN
134 $expectedforums = array();
135 $expectedforums[$forum1->id] = (array) $forum1;
136 $expectedforums[$forum2->id] = (array) $forum2;
137
138 // Call the external function passing course ids.
139 $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id));
c8f1d8a0
JL
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 }
2b9fe87d
MN
145
146 // Call the external function without passing course id.
147 $forums = mod_forum_external::get_forums_by_courses();
c8f1d8a0
JL
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 }
2b9fe87d
MN
153
154 // Unenrol user from second course and alter expected forums.
155 $enrol->unenrol_user($instance2, $user->id);
156 unset($expectedforums[$forum2->id]);
157
158 // Call the external function without passing course id.
159 $forums = mod_forum_external::get_forums_by_courses();
c8f1d8a0
JL
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]);
ea5b910b
JL
163 $this->assertTrue($forums[0]['cancreatediscussions']);
164
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']);
c8f1d8a0
JL
170
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);
a9a0cb69
MN
175 }
176
e2ede426
JL
177 /**
178 * Test get forum posts
179 */
180 public function test_mod_forum_get_forum_discussion_posts() {
d85bedf7 181 global $CFG, $PAGE;
e2ede426
JL
182
183 $this->resetAfterTest(true);
184
185 // Set the CFG variable to allow track forums.
186 $CFG->forum_trackreadposts = true;
187
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();
195
196 // Set the first created user to the test user.
197 self::setUser($user1);
198
199 // Create course to add the module.
200 $course1 = self::getDataGenerator()->create_course();
201
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);
208
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);
215
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);
221
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);
228
229 $record->parent = $discussion1reply1->id;
230 $record->userid = $user3->id;
231 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
232
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);
81f810dc
JL
239
240 // Delete one user, to test that we still receive posts by this user.
241 delete_user($user3);
e2ede426
JL
242
243 // Create what we expect to be returned when querying the discussion.
244 $expectedposts = array(
245 'posts' => array(),
246 'warnings' => array(),
247 );
694bf0c7 248
d85bedf7 249 // User pictures are initially empty, we should get the links once the external function is called.
e2ede426
JL
250 $expectedposts['posts'][] = array(
251 'id' => $discussion1reply2->id,
252 'discussion' => $discussion1reply2->discussion,
253 'parent' => $discussion1reply2->parent,
48fb0250 254 'userid' => (int) $discussion1reply2->userid,
e2ede426
JL
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),
48fb0250 261 'messageformat' => 1, // This value is usually changed by external_format_text() function.
e2ede426
JL
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,
694bf0c7 269 'userfullname' => fullname($user3),
d85bedf7 270 'userpictureurl' => ''
e2ede426 271 );
694bf0c7 272
e2ede426
JL
273 $expectedposts['posts'][] = array(
274 'id' => $discussion1reply1->id,
275 'discussion' => $discussion1reply1->discussion,
276 'parent' => $discussion1reply1->parent,
48fb0250 277 'userid' => (int) $discussion1reply1->userid,
e2ede426
JL
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),
48fb0250 284 'messageformat' => 1, // This value is usually changed by external_format_text() function.
e2ede426
JL
285 'messagetrust' => $discussion1reply1->messagetrust,
286 'attachment' => $discussion1reply1->attachment,
287 'totalscore' => $discussion1reply1->totalscore,
288 'mailnow' => $discussion1reply1->mailnow,
d2c58b95 289 'children' => array($discussion1reply2->id),
e2ede426
JL
290 'canreply' => true,
291 'postread' => false,
694bf0c7 292 'userfullname' => fullname($user2),
d85bedf7 293 'userpictureurl' => ''
e2ede426
JL
294 );
295
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']));
300
d85bedf7
JL
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);
305
306 $userpicture = new user_picture($user2);
307 $userpicture->size = 1; // Size f1.
308 $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
309
e2ede426
JL
310 // Unset the initial discussion post.
311 array_pop($posts['posts']);
312 $this->assertEquals($expectedposts, $posts);
313
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']));
318
319 }
c2586672 320
b1aa7dfa
JL
321 /**
322 * Test get forum posts (qanda forum)
323 */
324 public function test_mod_forum_get_forum_discussion_posts_qanda() {
325 global $CFG, $DB;
326
327 $this->resetAfterTest(true);
328
329 $record = new stdClass();
330 $user1 = self::getDataGenerator()->create_user($record);
331 $user2 = self::getDataGenerator()->create_user();
332
333 // Set the first created user to the test user.
334 self::setUser($user1);
335
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);
340
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);
347
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);
354
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);
361
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']));
366
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);
373
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']));
377
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);
381
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']));
b1aa7dfa
JL
385 }
386
c2586672
JL
387 /**
388 * Test get forum discussions paginated
389 */
390 public function test_mod_forum_get_forum_discussions_paginated() {
d85bedf7 391 global $USER, $CFG, $DB, $PAGE;
c2586672
JL
392
393 $this->resetAfterTest(true);
394
395 // Set the CFG variable to allow track forums.
396 $CFG->forum_trackreadposts = true;
397
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();
406
407 // Set the first created user to the test user.
408 self::setUser($user1);
409
410 // Create courses to add the modules.
411 $course1 = self::getDataGenerator()->create_course();
412
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);
418
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);
425
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);
432
433 $record->parent = $discussion1reply1->id;
434 $record->userid = $user3->id;
435 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
436
437 $record->userid = $user4->id;
438 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
439
440 // Enrol the user in the first course.
441 $enrol = enrol_get_plugin('manual');
442
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);
452
81f810dc
JL
453 // Delete one user.
454 delete_user($user4);
455
c2586672
JL
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);
461
462 // Create what we expect to be returned when querying the forums.
463
464 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
c2586672 465
d85bedf7 466 // User pictures are initially empty, we should get the links once the external function is called.
c2586672
JL
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),
d85bedf7
JL
490 'userpictureurl' => '',
491 'usermodifiedpictureurl' => '',
c2586672 492 'numreplies' => 3,
5f219cf1 493 'numunread' => 0,
0f3bbfd4
AN
494 'pinned' => FORUM_DISCUSSION_UNPINNED,
495 'locked' => false,
496 'canreply' => false,
c2586672
JL
497 );
498
499 // Call the external function passing forum id.
500 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
501 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
502 $expectedreturn = array(
503 'discussions' => array($expecteddiscussions),
504 'warnings' => array()
505 );
d85bedf7
JL
506
507 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
508 $userpicture = new user_picture($user1);
509 $userpicture->size = 1; // Size f1.
510 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
511
512 $userpicture = new user_picture($user4);
513 $userpicture->size = 1; // Size f1.
514 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
515
c2586672
JL
516 $this->assertEquals($expectedreturn, $discussions);
517
518 // Call without required view discussion capability.
519 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
520 try {
521 mod_forum_external::get_forum_discussions_paginated($forum1->id);
522 $this->fail('Exception expected due to missing capability.');
523 } catch (moodle_exception $e) {
524 $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
525 }
526
527 // Unenrol user from second course.
528 $enrol->unenrol_user($instance1, $user1->id);
529
530 // Call for the second course we unenrolled the user from, make sure exception thrown.
531 try {
532 mod_forum_external::get_forum_discussions_paginated($forum1->id);
533 $this->fail('Exception expected due to being unenrolled from the course.');
534 } catch (moodle_exception $e) {
535 $this->assertEquals('requireloginerror', $e->errorcode);
536 }
537 }
039c81f0
JL
538
539 /**
540 * Test get forum discussions paginated (qanda forums)
541 */
542 public function test_mod_forum_get_forum_discussions_paginated_qanda() {
543
544 $this->resetAfterTest(true);
545
546 // Create courses to add the modules.
547 $course = self::getDataGenerator()->create_course();
548
549 $user1 = self::getDataGenerator()->create_user();
550 $user2 = self::getDataGenerator()->create_user();
551
552 // First forum with tracking off.
553 $record = new stdClass();
554 $record->course = $course->id;
555 $record->type = 'qanda';
556 $forum = self::getDataGenerator()->create_module('forum', $record);
557
558 // Add discussions to the forums.
559 $discussionrecord = new stdClass();
560 $discussionrecord->course = $course->id;
561 $discussionrecord->userid = $user2->id;
562 $discussionrecord->forum = $forum->id;
563 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
564
565 self::setAdminUser();
566 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
567 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
568
569 $this->assertCount(1, $discussions['discussions']);
570 $this->assertCount(0, $discussions['warnings']);
571
572 self::setUser($user1);
573 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
574
575 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
576 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
577
578 $this->assertCount(1, $discussions['discussions']);
579 $this->assertCount(0, $discussions['warnings']);
580
581 }
50a20317
JL
582
583 /**
584 * Test add_discussion_post
585 */
586 public function test_add_discussion_post() {
e881c4f5 587 global $CFG;
50a20317
JL
588
589 $this->resetAfterTest(true);
590
591 $user = self::getDataGenerator()->create_user();
592 $otheruser = self::getDataGenerator()->create_user();
593
594 self::setAdminUser();
595
596 // Create course to add the module.
597 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
598
599 // Forum with tracking off.
600 $record = new stdClass();
601 $record->course = $course->id;
602 $forum = self::getDataGenerator()->create_module('forum', $record);
603 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
604 $forumcontext = context_module::instance($forum->cmid);
605
606 // Add discussions to the forums.
607 $record = new stdClass();
608 $record->course = $course->id;
609 $record->userid = $user->id;
610 $record->forum = $forum->id;
611 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
612
613 // Try to post (user not enrolled).
614 self::setUser($user);
615 try {
616 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
617 $this->fail('Exception expected due to being unenrolled from the course.');
618 } catch (moodle_exception $e) {
619 $this->assertEquals('requireloginerror', $e->errorcode);
620 }
621
622 $this->getDataGenerator()->enrol_user($user->id, $course->id);
623 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
624
41182118
BK
625 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
626 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
50a20317
JL
627
628 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
629 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
630 // We receive the discussion and the post.
631 $this->assertEquals(2, count($posts['posts']));
43ff833d
JL
632
633 $tested = false;
41182118
BK
634 foreach ($posts['posts'] as $thispost) {
635 if ($createdpost['postid'] == $thispost['id']) {
636 $this->assertEquals('some subject', $thispost['subject']);
637 $this->assertEquals('some text here...', $thispost['message']);
43ff833d
JL
638 $tested = true;
639 }
640 }
641 $this->assertTrue($tested);
50a20317 642
e881c4f5 643 // Test inline and regular attachment in post
41182118
BK
644 // Create a file in a draft area for inline attachments.
645 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5 646 $draftidattach = file_get_unused_draft_itemid();
41182118
BK
647 self::setUser($user);
648 $usercontext = context_user::instance($user->id);
649 $filepath = '/';
650 $filearea = 'draft';
651 $component = 'user';
652 $filenameimg = 'shouldbeanimage.txt';
e881c4f5 653 $filerecordinline = array(
41182118
BK
654 'contextid' => $usercontext->id,
655 'component' => $component,
656 'filearea' => $filearea,
657 'itemid' => $draftidinlineattach,
658 'filepath' => $filepath,
659 'filename' => $filenameimg,
660 );
661 $fs = get_file_storage();
41182118 662
e881c4f5
BK
663 // Create a file in a draft area for regular attachments.
664 $filerecordattach = $filerecordinline;
665 $attachfilename = 'attachment.txt';
666 $filerecordattach['filename'] = $attachfilename;
667 $filerecordattach['itemid'] = $draftidattach;
668 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
669 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
670
48143990 671 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 672 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
673 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
674 . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
675 . '" alt="inlineimage">.';
676 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
677 $dummytext, $options);
678 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
679
680 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
681 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
682 // We receive the discussion and the post.
683 // Can't guarantee order of posts during tests.
684 $postfound = false;
685 foreach ($posts['posts'] as $thispost) {
686 if ($createdpost['postid'] == $thispost['id']) {
687 $this->assertEquals($createdpost['postid'], $thispost['id']);
e881c4f5
BK
688 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
689 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
690 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
691 $this->assertContains('pluginfile.php', $thispost['message']);
692 $postfound = true;
e881c4f5 693 break;
41182118
BK
694 }
695 }
696
697 $this->assertTrue($postfound);
698
50a20317
JL
699 // Check not posting in groups the user is not member of.
700 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
701 groups_add_member($group->id, $otheruser->id);
702
703 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
704 $record->forum = $forum->id;
705 $record->userid = $otheruser->id;
706 $record->groupid = $group->id;
707 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
708
709 try {
710 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
711 $this->fail('Exception expected due to invalid permissions for posting.');
712 } catch (moodle_exception $e) {
50a20317
JL
713 $this->assertEquals('nopostforum', $e->errorcode);
714 }
715
716 }
7ab43ac8
JL
717
718 /*
719 * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
720 */
721 public function test_add_discussion() {
41182118 722 global $CFG, $USER;
7ab43ac8
JL
723 $this->resetAfterTest(true);
724
725 // Create courses to add the modules.
726 $course = self::getDataGenerator()->create_course();
727
728 $user1 = self::getDataGenerator()->create_user();
729 $user2 = self::getDataGenerator()->create_user();
730
731 // First forum with tracking off.
732 $record = new stdClass();
733 $record->course = $course->id;
734 $record->type = 'news';
735 $forum = self::getDataGenerator()->create_module('forum', $record);
736
737 self::setUser($user1);
738 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
739
740 try {
741 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
742 $this->fail('Exception expected due to invalid permissions.');
743 } catch (moodle_exception $e) {
744 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
745 }
746
747 self::setAdminUser();
41182118
BK
748 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
749 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
7ab43ac8
JL
750
751 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
752 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
753
754 $this->assertCount(1, $discussions['discussions']);
755 $this->assertCount(0, $discussions['warnings']);
756
41182118 757 $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
7ab43ac8
JL
758 $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
759 $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
760 $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
761
5f219cf1
BK
762 $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
763 array('options' => array('name' => 'discussionpinned',
764 'value' => true)));
765 $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
766 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
767 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
768 $this->assertCount(3, $discussions['discussions']);
769 $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
41182118 770
e881c4f5 771 // Test inline and regular attachment in new discussion
41182118 772 // Create a file in a draft area for inline attachments.
e881c4f5
BK
773
774 $fs = get_file_storage();
775
41182118 776 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5
BK
777 $draftidattach = file_get_unused_draft_itemid();
778
41182118
BK
779 $usercontext = context_user::instance($USER->id);
780 $filepath = '/';
781 $filearea = 'draft';
782 $component = 'user';
783 $filenameimg = 'shouldbeanimage.txt';
784 $filerecord = array(
785 'contextid' => $usercontext->id,
786 'component' => $component,
787 'filearea' => $filearea,
788 'itemid' => $draftidinlineattach,
789 'filepath' => $filepath,
790 'filename' => $filenameimg,
791 );
e881c4f5 792
e881c4f5
BK
793 // Create a file in a draft area for regular attachments.
794 $filerecordattach = $filerecord;
795 $attachfilename = 'attachment.txt';
796 $filerecordattach['filename'] = $attachfilename;
797 $filerecordattach['itemid'] = $draftidattach;
41182118 798 $fs->create_file_from_string($filerecord, 'image contents (not really)');
e881c4f5
BK
799 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
800
41182118
BK
801 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
802 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
803 '" alt="inlineimage">.';
804
48143990 805 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 806 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
807 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
808 $dummytext, -1, $options);
809 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
810
811 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
812 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
813
814 $this->assertCount(4, $discussions['discussions']);
815 $this->assertCount(0, $createddiscussion['warnings']);
816 // Can't guarantee order of posts during tests.
817 $postfound = false;
818 foreach ($discussions['discussions'] as $thisdiscussion) {
819 if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
e881c4f5
BK
820 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
821 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
822 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
823 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
824 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
825 $postfound = true;
e881c4f5 826 break;
41182118
BK
827 }
828 }
829
830 $this->assertTrue($postfound);
7ab43ac8
JL
831 }
832
833 /**
834 * Test adding discussions in a course with gorups
835 */
836 public function test_add_discussion_in_course_with_groups() {
837 global $CFG;
838
839 $this->resetAfterTest(true);
840
841 // Create course to add the module.
842 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
843 $user = self::getDataGenerator()->create_user();
844 $this->getDataGenerator()->enrol_user($user->id, $course->id);
845
846 // Forum forcing separate gropus.
847 $record = new stdClass();
848 $record->course = $course->id;
849 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
850
851 // Try to post (user not enrolled).
852 self::setUser($user);
853
854 // The user is not enroled in any group, try to post in a forum with separate groups.
855 try {
856 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
857 $this->fail('Exception expected due to invalid group permissions.');
858 } catch (moodle_exception $e) {
859 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
860 }
861
862 try {
863 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
864 $this->fail('Exception expected due to invalid group permissions.');
865 } catch (moodle_exception $e) {
866 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
867 }
868
869 // Create a group.
870 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
871
872 // Try to post in a group the user is not enrolled.
873 try {
874 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
875 $this->fail('Exception expected due to invalid group permissions.');
876 } catch (moodle_exception $e) {
877 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
878 }
879
880 // Add the user to a group.
881 groups_add_member($group->id, $user->id);
882
883 // Try to post in a group the user is not enrolled.
884 try {
885 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
886 $this->fail('Exception expected due to invalid group.');
887 } catch (moodle_exception $e) {
888 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
889 }
890
891 // Nost add the discussion using a valid group.
892 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
893 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
894
895 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
896 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
897
898 $this->assertCount(1, $discussions['discussions']);
899 $this->assertCount(0, $discussions['warnings']);
900 $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
901 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
902
903 // Now add a discussions without indicating a group. The function should guess the correct group.
904 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
905 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
906
907 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
908 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
909
910 $this->assertCount(2, $discussions['discussions']);
911 $this->assertCount(0, $discussions['warnings']);
912 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
913 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
914
915 // Enrol the same user in other group.
916 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
917 groups_add_member($group2->id, $user->id);
918
919 // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
920 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
921 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
922
923 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
924 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
925
926 $this->assertCount(3, $discussions['discussions']);
927 $this->assertCount(0, $discussions['warnings']);
928 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
929 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
930 $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
931
932 }
933
04cd8ae3
JL
934 /*
935 * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
936 */
937 public function test_can_add_discussion() {
938
939 $this->resetAfterTest(true);
940
941 // Create courses to add the modules.
942 $course = self::getDataGenerator()->create_course();
943
944 $user = self::getDataGenerator()->create_user();
945
946 // First forum with tracking off.
947 $record = new stdClass();
948 $record->course = $course->id;
949 $record->type = 'news';
950 $forum = self::getDataGenerator()->create_module('forum', $record);
951
952 // User with no permissions to add in a news forum.
953 self::setUser($user);
954 $this->getDataGenerator()->enrol_user($user->id, $course->id);
955
956 $result = mod_forum_external::can_add_discussion($forum->id);
957 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
958 $this->assertFalse($result['status']);
959
960 self::setAdminUser();
961 $result = mod_forum_external::can_add_discussion($forum->id);
962 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
963 $this->assertTrue($result['status']);
964
965 }
966
2b9fe87d 967}