on-demand release 3.7dev+
[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');
4669389d 31require_once($CFG->dirroot . '/mod/forum/lib.php');
2b9fe87d
MN
32
33class mod_forum_external_testcase extends externallib_advanced_testcase {
34
35 /**
36 * Tests set up
37 */
38 protected function setUp() {
39 global $CFG;
40
59075a43
AN
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();
44
2b9fe87d
MN
45 require_once($CFG->dirroot . '/mod/forum/externallib.php');
46 }
47
59075a43
AN
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 }
53
2b9fe87d
MN
54 /**
55 * Test get forums
56 */
57 public function test_mod_forum_get_forums_by_courses() {
58 global $USER, $CFG, $DB;
59
60 $this->resetAfterTest(true);
61
62 // Create a user.
4669389d 63 $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
2b9fe87d
MN
64
65 // Set to the user.
66 self::setUser($user);
67
68 // Create courses to add the modules.
69 $course1 = self::getDataGenerator()->create_course();
70 $course2 = self::getDataGenerator()->create_course();
71
72 // First forum.
73 $record = new stdClass();
74 $record->introformat = FORMAT_HTML;
75 $record->course = $course1->id;
4669389d 76 $record->trackingtype = FORUM_TRACKING_FORCED;
2b9fe87d
MN
77 $forum1 = self::getDataGenerator()->create_module('forum', $record);
78
79 // Second forum.
80 $record = new stdClass();
81 $record->introformat = FORMAT_HTML;
82 $record->course = $course2->id;
4669389d 83 $record->trackingtype = FORUM_TRACKING_OFF;
2b9fe87d 84 $forum2 = self::getDataGenerator()->create_module('forum', $record);
7ef49bd3 85 $forum2->introfiles = [];
2b9fe87d 86
7ea6ada3
JL
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;
ea5b910b 95 $forum1->cancreatediscussions = true;
4669389d 96 $forum1->istracked = true;
2256bb74 97 $forum1->unreadpostscount = 0;
7ef49bd3 98 $forum1->introfiles = [];
7ea6ada3
JL
99
100 $record = new stdClass();
101 $record->course = $course2->id;
102 $record->userid = $user->id;
103 $record->forum = $forum2->id;
104 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
105 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
106 // Expect two discussions.
107 $forum2->numdiscussions = 2;
ea5b910b
JL
108 // Default limited role, no create discussion capability enabled.
109 $forum2->cancreatediscussions = false;
4669389d 110 $forum2->istracked = false;
7ea6ada3 111
2b9fe87d
MN
112 // Check the forum was correctly created.
113 $this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2',
114 array('forum1' => $forum1->id, 'forum2' => $forum2->id)));
115
116 // Enrol the user in two courses.
909f27ac
JM
117 // DataGenerator->enrol_user automatically sets a role for the user with the permission mod/form:viewdiscussion.
118 $this->getDataGenerator()->enrol_user($user->id, $course1->id, null, 'manual');
119 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
2b9fe87d 120 $enrol = enrol_get_plugin('manual');
2b9fe87d
MN
121 $enrolinstances = enrol_get_instances($course2->id, true);
122 foreach ($enrolinstances as $courseenrolinstance) {
123 if ($courseenrolinstance->enrol == "manual") {
124 $instance2 = $courseenrolinstance;
125 break;
126 }
127 }
128 $enrol->enrol_user($instance2, $user->id);
129
2b9fe87d 130 // Assign capabilities to view forums for forum 2.
74b63eae 131 $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
2b9fe87d
MN
132 $context2 = context_module::instance($cm2->id);
133 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
134 $roleid2 = $this->assignUserCapability('mod/forum:viewdiscussion', $context2->id, $newrole);
135
136 // Create what we expect to be returned when querying the two courses.
c8f1d8a0
JL
137 unset($forum1->displaywordcount);
138 unset($forum2->displaywordcount);
139
2b9fe87d
MN
140 $expectedforums = array();
141 $expectedforums[$forum1->id] = (array) $forum1;
142 $expectedforums[$forum2->id] = (array) $forum2;
143
144 // Call the external function passing course ids.
145 $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id));
c8f1d8a0
JL
146 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
147 $this->assertCount(2, $forums);
148 foreach ($forums as $forum) {
149 $this->assertEquals($expectedforums[$forum['id']], $forum);
150 }
2b9fe87d
MN
151
152 // Call the external function without passing course id.
153 $forums = mod_forum_external::get_forums_by_courses();
c8f1d8a0
JL
154 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
155 $this->assertCount(2, $forums);
156 foreach ($forums as $forum) {
157 $this->assertEquals($expectedforums[$forum['id']], $forum);
158 }
2b9fe87d
MN
159
160 // Unenrol user from second course and alter expected forums.
161 $enrol->unenrol_user($instance2, $user->id);
162 unset($expectedforums[$forum2->id]);
163
164 // Call the external function without passing course id.
165 $forums = mod_forum_external::get_forums_by_courses();
c8f1d8a0
JL
166 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
167 $this->assertCount(1, $forums);
168 $this->assertEquals($expectedforums[$forum1->id], $forums[0]);
ea5b910b
JL
169 $this->assertTrue($forums[0]['cancreatediscussions']);
170
171 // Change the type of the forum, the user shouldn't be able to add discussions.
172 $DB->set_field('forum', 'type', 'news', array('id' => $forum1->id));
173 $forums = mod_forum_external::get_forums_by_courses();
174 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
175 $this->assertFalse($forums[0]['cancreatediscussions']);
c8f1d8a0
JL
176
177 // Call for the second course we unenrolled the user from.
178 $forums = mod_forum_external::get_forums_by_courses(array($course2->id));
179 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
180 $this->assertCount(0, $forums);
a9a0cb69
MN
181 }
182
897ac0de
P
183 /**
184 * Test the toggle favourite state
185 */
186 public function test_mod_forum_toggle_favourite_state() {
187 global $USER, $CFG, $DB;
188
189 $this->resetAfterTest(true);
190
191 // Create a user.
192 $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
193
194 // Set to the user.
195 self::setUser($user);
196
197 // Create courses to add the modules.
198 $course1 = self::getDataGenerator()->create_course();
199 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
200
201 $record = new stdClass();
202 $record->introformat = FORMAT_HTML;
203 $record->course = $course1->id;
204 $record->trackingtype = FORUM_TRACKING_OFF;
205 $forum1 = self::getDataGenerator()->create_module('forum', $record);
206 $forum1->introfiles = [];
207
208 // Add discussions to the forums.
209 $record = new stdClass();
210 $record->course = $course1->id;
211 $record->userid = $user->id;
212 $record->forum = $forum1->id;
213 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
214
13cd05ac 215 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 1);
897ac0de
P
216 $response = external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
217 $this->assertTrue($response['userstate']['favourited']);
218
13cd05ac 219 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0);
897ac0de
P
220 $response = external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
221 $this->assertFalse($response['userstate']['favourited']);
d3cac88d
P
222
223 $this->setUser(0);
224 try {
225 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0);
226 } catch (moodle_exception $e) {
227 $this->assertEquals('requireloginerror', $e->errorcode);
228 }
897ac0de
P
229 }
230
309e8698
P
231 /**
232 * Test the toggle pin state
233 */
234 public function test_mod_forum_set_pin_state() {
235 $this->resetAfterTest(true);
236
237 // Create a user.
238 $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
239
240 // Set to the user.
241 self::setUser($user);
242
243 // Create courses to add the modules.
244 $course1 = self::getDataGenerator()->create_course();
245 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
246
247 $record = new stdClass();
248 $record->introformat = FORMAT_HTML;
249 $record->course = $course1->id;
250 $record->trackingtype = FORUM_TRACKING_OFF;
251 $forum1 = self::getDataGenerator()->create_module('forum', $record);
252 $forum1->introfiles = [];
253
254 // Add discussions to the forums.
255 $record = new stdClass();
256 $record->course = $course1->id;
257 $record->userid = $user->id;
258 $record->forum = $forum1->id;
259 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
260
261 try {
13cd05ac 262 $response = mod_forum_external::set_pin_state($discussion1->id, 1);
309e8698
P
263 } catch (Exception $e) {
264 $this->assertEquals('cannotpindiscussions', $e->errorcode);
265 }
266
267 self::setAdminUser();
13cd05ac 268 $response = mod_forum_external::set_pin_state($discussion1->id, 1);
309e8698
P
269 $response = external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
270 $this->assertTrue($response['pinned']);
271
13cd05ac 272 $response = mod_forum_external::set_pin_state($discussion1->id, 0);
309e8698
P
273 $response = external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
274 $this->assertFalse($response['pinned']);
275 }
276
e2ede426
JL
277 /**
278 * Test get forum posts
279 */
280 public function test_mod_forum_get_forum_discussion_posts() {
d85bedf7 281 global $CFG, $PAGE;
e2ede426
JL
282
283 $this->resetAfterTest(true);
284
285 // Set the CFG variable to allow track forums.
286 $CFG->forum_trackreadposts = true;
287
288 // Create a user who can track forums.
289 $record = new stdClass();
290 $record->trackforums = true;
291 $user1 = self::getDataGenerator()->create_user($record);
292 // Create a bunch of other users to post.
293 $user2 = self::getDataGenerator()->create_user();
294 $user3 = self::getDataGenerator()->create_user();
295
296 // Set the first created user to the test user.
297 self::setUser($user1);
298
299 // Create course to add the module.
300 $course1 = self::getDataGenerator()->create_course();
301
302 // Forum with tracking off.
303 $record = new stdClass();
304 $record->course = $course1->id;
305 $record->trackingtype = FORUM_TRACKING_OFF;
306 $forum1 = self::getDataGenerator()->create_module('forum', $record);
307 $forum1context = context_module::instance($forum1->cmid);
308
db3c9ff8
PFO
309 // Forum with tracking enabled.
310 $record = new stdClass();
311 $record->course = $course1->id;
312 $forum2 = self::getDataGenerator()->create_module('forum', $record);
2256bb74 313 $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
db3c9ff8
PFO
314 $forum2context = context_module::instance($forum2->cmid);
315
e2ede426
JL
316 // Add discussions to the forums.
317 $record = new stdClass();
318 $record->course = $course1->id;
319 $record->userid = $user1->id;
320 $record->forum = $forum1->id;
321 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
322
323 $record = new stdClass();
324 $record->course = $course1->id;
325 $record->userid = $user2->id;
326 $record->forum = $forum1->id;
327 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
328
db3c9ff8
PFO
329 $record = new stdClass();
330 $record->course = $course1->id;
331 $record->userid = $user2->id;
332 $record->forum = $forum2->id;
333 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
334
e2ede426
JL
335 // Add 2 replies to the discussion 1 from different users.
336 $record = new stdClass();
337 $record->discussion = $discussion1->id;
338 $record->parent = $discussion1->firstpost;
339 $record->userid = $user2->id;
340 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
c8743f62
JL
341 $filename = 'shouldbeanimage.jpg';
342 // Add a fake inline image to the post.
343 $filerecordinline = array(
344 'contextid' => $forum1context->id,
345 'component' => 'mod_forum',
346 'filearea' => 'post',
347 'itemid' => $discussion1reply1->id,
348 'filepath' => '/',
349 'filename' => $filename,
350 );
351 $fs = get_file_storage();
352 $timepost = time();
353 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
e2ede426
JL
354
355 $record->parent = $discussion1reply1->id;
356 $record->userid = $user3->id;
6c344ff2 357 $record->tags = array('Cats', 'Dogs');
e2ede426
JL
358 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
359
360 // Enrol the user in the course.
361 $enrol = enrol_get_plugin('manual');
362 // Following line enrol and assign default role id to the user.
363 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
364 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
365 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
81f810dc
JL
366
367 // Delete one user, to test that we still receive posts by this user.
368 delete_user($user3);
e2ede426
JL
369
370 // Create what we expect to be returned when querying the discussion.
371 $expectedposts = array(
372 'posts' => array(),
b7ce46df
JL
373 'ratinginfo' => array(
374 'contextid' => $forum1context->id,
375 'component' => 'mod_forum',
376 'ratingarea' => 'post',
377 'canviewall' => null,
378 'canviewany' => null,
379 'scales' => array(),
380 'ratings' => array(),
381 ),
e2ede426
JL
382 'warnings' => array(),
383 );
694bf0c7 384
d85bedf7 385 // User pictures are initially empty, we should get the links once the external function is called.
e2ede426
JL
386 $expectedposts['posts'][] = array(
387 'id' => $discussion1reply2->id,
388 'discussion' => $discussion1reply2->discussion,
389 'parent' => $discussion1reply2->parent,
48fb0250 390 'userid' => (int) $discussion1reply2->userid,
e2ede426
JL
391 'created' => $discussion1reply2->created,
392 'modified' => $discussion1reply2->modified,
393 'mailed' => $discussion1reply2->mailed,
394 'subject' => $discussion1reply2->subject,
395 'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
396 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
48fb0250 397 'messageformat' => 1, // This value is usually changed by external_format_text() function.
e2ede426
JL
398 'messagetrust' => $discussion1reply2->messagetrust,
399 'attachment' => $discussion1reply2->attachment,
400 'totalscore' => $discussion1reply2->totalscore,
401 'mailnow' => $discussion1reply2->mailnow,
402 'children' => array(),
403 'canreply' => true,
404 'postread' => false,
694bf0c7 405 'userfullname' => fullname($user3),
3e95e09b
AN
406 'userpictureurl' => '',
407 'deleted' => false,
bc4c7337 408 'isprivatereply' => false,
6c344ff2 409 'tags' => \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $discussion1reply2->id),
e2ede426 410 );
6c344ff2
JL
411 // Cast to expected.
412 $this->assertCount(2, $expectedposts['posts'][0]['tags']);
413 $expectedposts['posts'][0]['tags'][0]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][0]['isstandard'];
414 $expectedposts['posts'][0]['tags'][1]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][1]['isstandard'];
694bf0c7 415
e2ede426
JL
416 $expectedposts['posts'][] = array(
417 'id' => $discussion1reply1->id,
418 'discussion' => $discussion1reply1->discussion,
419 'parent' => $discussion1reply1->parent,
48fb0250 420 'userid' => (int) $discussion1reply1->userid,
e2ede426
JL
421 'created' => $discussion1reply1->created,
422 'modified' => $discussion1reply1->modified,
423 'mailed' => $discussion1reply1->mailed,
424 'subject' => $discussion1reply1->subject,
425 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
426 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
48fb0250 427 'messageformat' => 1, // This value is usually changed by external_format_text() function.
e2ede426
JL
428 'messagetrust' => $discussion1reply1->messagetrust,
429 'attachment' => $discussion1reply1->attachment,
c8743f62
JL
430 'messageinlinefiles' => array(
431 array(
432 'filename' => $filename,
433 'filepath' => '/',
434 'filesize' => '27',
435 'fileurl' => moodle_url::make_webservice_pluginfile_url($forum1context->id, 'mod_forum', 'post',
436 $discussion1reply1->id, '/', $filename),
437 'timemodified' => $timepost,
438 'mimetype' => 'image/jpeg',
1104a9fa 439 'isexternalfile' => false,
c8743f62
JL
440 )
441 ),
e2ede426
JL
442 'totalscore' => $discussion1reply1->totalscore,
443 'mailnow' => $discussion1reply1->mailnow,
d2c58b95 444 'children' => array($discussion1reply2->id),
e2ede426
JL
445 'canreply' => true,
446 'postread' => false,
694bf0c7 447 'userfullname' => fullname($user2),
3e95e09b
AN
448 'userpictureurl' => '',
449 'deleted' => false,
bc4c7337 450 'isprivatereply' => false,
6c344ff2 451 'tags' => array(),
e2ede426
JL
452 );
453
454 // Test a discussion with two additional posts (total 3 posts).
455 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
456 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
457 $this->assertEquals(3, count($posts['posts']));
458
d85bedf7
JL
459 // Generate here the pictures because we need to wait to the external function to init the theme.
460 $userpicture = new user_picture($user3);
461 $userpicture->size = 1; // Size f1.
462 $expectedposts['posts'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
463
464 $userpicture = new user_picture($user2);
465 $userpicture->size = 1; // Size f1.
466 $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
467
e2ede426
JL
468 // Unset the initial discussion post.
469 array_pop($posts['posts']);
470 $this->assertEquals($expectedposts, $posts);
471
2256bb74
JL
472 // Check we receive the unread count correctly on tracked forum.
473 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
474 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
475 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
476 foreach ($result as $f) {
477 if ($f['id'] == $forum2->id) {
478 $this->assertEquals(1, $f['unreadpostscount']);
479 }
480 }
481
e2ede426
JL
482 // Test discussion without additional posts. There should be only one post (the one created by the discussion).
483 $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
484 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
485 $this->assertEquals(1, count($posts['posts']));
486
db3c9ff8
PFO
487 // Test discussion tracking on not tracked forum.
488 $result = mod_forum_external::view_forum_discussion($discussion1->id);
489 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
490 $this->assertTrue($result['status']);
491 $this->assertEmpty($result['warnings']);
492
493 // Test posts have not been marked as read.
494 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
495 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
496 foreach ($posts['posts'] as $post) {
497 $this->assertFalse($post['postread']);
498 }
499
500 // Test discussion tracking on tracked forum.
501 $result = mod_forum_external::view_forum_discussion($discussion3->id);
502 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
503 $this->assertTrue($result['status']);
504 $this->assertEmpty($result['warnings']);
505
506 // Test posts have been marked as read.
507 $posts = mod_forum_external::get_forum_discussion_posts($discussion3->id, 'modified', 'DESC');
508 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
509 foreach ($posts['posts'] as $post) {
510 $this->assertTrue($post['postread']);
511 }
2256bb74
JL
512
513 // Check we receive 0 unread posts.
514 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
515 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
516 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
517 foreach ($result as $f) {
518 if ($f['id'] == $forum2->id) {
519 $this->assertEquals(0, $f['unreadpostscount']);
520 }
521 }
e2ede426 522 }
c2586672 523
8245daba
P
524 /**
525 * Test get forum posts
526 *
527 * Tests is similar to the get_forum_discussion_posts only utilizing the new return structure and entities
528 */
529 public function test_mod_forum_get_discussion_posts() {
530 global $CFG, $PAGE;
531
532 $this->resetAfterTest(true);
533
534 // Set the CFG variable to allow track forums.
535 $CFG->forum_trackreadposts = true;
536
537 $urlfactory = mod_forum\local\container::get_url_factory();
538 $legacyfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
539 $entityfactory = mod_forum\local\container::get_entity_factory();
540
541 // Create a user who can track forums.
542 $record = new stdClass();
543 $record->trackforums = true;
544 $user1 = self::getDataGenerator()->create_user($record);
545 // Create a bunch of other users to post.
546 $user2 = self::getDataGenerator()->create_user();
547 $user2entity = $entityfactory->get_author_from_stdclass($user2);
548 $exporteduser2 = [
549 'id' => (int) $user2->id,
550 'fullname' => fullname($user2),
551 'groups' => [],
552 'urls' => [
553 'profile' => $urlfactory->get_author_profile_url($user2entity),
554 'profileimage' => $urlfactory->get_author_profile_image_url($user2entity),
555 ]
556 ];
557 $user2->fullname = $exporteduser2['fullname'];
558
559 $user3 = self::getDataGenerator()->create_user(['fullname' => "Mr Pants 1"]);
560 $user3entity = $entityfactory->get_author_from_stdclass($user3);
561 $exporteduser3 = [
562 'id' => (int) $user3->id,
563 'fullname' => fullname($user3),
564 'groups' => [],
565 'urls' => [
566 'profile' => $urlfactory->get_author_profile_url($user3entity),
567 'profileimage' => $urlfactory->get_author_profile_image_url($user3entity),
568 ]
569 ];
570 $user3->fullname = $exporteduser3['fullname'];
571 $forumgenerator = self::getDataGenerator()->get_plugin_generator('mod_forum');
572
573 // Set the first created user to the test user.
574 self::setUser($user1);
575
576 // Create course to add the module.
577 $course1 = self::getDataGenerator()->create_course();
578
579 // Forum with tracking off.
580 $record = new stdClass();
581 $record->course = $course1->id;
582 $record->trackingtype = FORUM_TRACKING_OFF;
583 $forum1 = self::getDataGenerator()->create_module('forum', $record);
584 $forum1context = context_module::instance($forum1->cmid);
585
586 // Forum with tracking enabled.
587 $record = new stdClass();
588 $record->course = $course1->id;
589 $forum2 = self::getDataGenerator()->create_module('forum', $record);
590 $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
591 $forum2context = context_module::instance($forum2->cmid);
592
593 // Add discussions to the forums.
594 $record = new stdClass();
595 $record->course = $course1->id;
596 $record->userid = $user1->id;
597 $record->forum = $forum1->id;
598 $discussion1 = $forumgenerator->create_discussion($record);
599
600 $record = new stdClass();
601 $record->course = $course1->id;
602 $record->userid = $user2->id;
603 $record->forum = $forum1->id;
604 $discussion2 = $forumgenerator->create_discussion($record);
605
606 $record = new stdClass();
607 $record->course = $course1->id;
608 $record->userid = $user2->id;
609 $record->forum = $forum2->id;
610 $discussion3 = $forumgenerator->create_discussion($record);
611
612 // Add 2 replies to the discussion 1 from different users.
613 $record = new stdClass();
614 $record->discussion = $discussion1->id;
615 $record->parent = $discussion1->firstpost;
616 $record->userid = $user2->id;
617 $discussion1reply1 = $forumgenerator->create_post($record);
618 $filename = 'shouldbeanimage.jpg';
619 // Add a fake inline image to the post.
620 $filerecordinline = array(
621 'contextid' => $forum1context->id,
622 'component' => 'mod_forum',
623 'filearea' => 'post',
624 'itemid' => $discussion1reply1->id,
625 'filepath' => '/',
626 'filename' => $filename,
627 );
628 $fs = get_file_storage();
629 $timepost = time();
630 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
631
632 $record->parent = $discussion1reply1->id;
633 $record->userid = $user3->id;
634 $discussion1reply2 = $forumgenerator->create_post($record);
635
636 // Enrol the user in the course.
637 $enrol = enrol_get_plugin('manual');
638 // Following line enrol and assign default role id to the user.
639 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
640 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
641 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
642
643 // Delete one user, to test that we still receive posts by this user.
644 delete_user($user3);
645
646 // Create what we expect to be returned when querying the discussion.
647 $expectedposts = array(
648 'posts' => array(),
649 'ratinginfo' => array(
650 'contextid' => $forum1context->id,
651 'component' => 'mod_forum',
652 'ratingarea' => 'post',
653 'canviewall' => null,
654 'canviewany' => null,
655 'scales' => array(),
656 'ratings' => array(),
657 ),
658 'warnings' => array(),
659 );
660
661 // User pictures are initially empty, we should get the links once the external function is called.
662 $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion);
663 $isolatedurl->params(['parent' => $discussion1reply2->id]);
664 $expectedposts['posts'][] = array(
665 'id' => $discussion1reply2->id,
666 'discussionid' => $discussion1reply2->discussion,
667 'parentid' => $discussion1reply2->parent,
668 'hasparent' => true,
669 'timecreated' => $discussion1reply2->created,
670 'subject' => $discussion1reply2->subject,
671 'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
672 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
673 'messageformat' => 1, // This value is usually changed by external_format_text() function.
674 'unread' => null,
675 'isdeleted' => false,
676 'isprivatereply' => false,
677 'haswordcount' => false,
678 'wordcount' => null,
679 'author'=> $exporteduser3,
680 'attachments' => [],
681 'tags' => [],
682 'html' => [
683 'rating' => null,
684 'taglist' => null,
685 'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser3, $discussion1reply2->created)
686 ],
687 'capabilities' => [
688 'view' => 1,
689 'edit' => 0,
690 'delete' => 0,
691 'split' => 0,
692 'reply' => 1,
693 'export' => 0,
694 'controlreadstatus' => 0
695 ],
696 'urls' => [
697 'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->id),
698 'viewisolated' => $isolatedurl->out(false),
699 'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->parent),
700 'edit' => null,
701 'delete' =>null,
702 'split' => null,
703 'reply' => (new moodle_url('/mod/forum/post.php#mformforum', [
704 'reply' => $discussion1reply2->id
705 ]))->out(false),
706 'export' => null,
707 'markasread' => null,
708 'markasunread' => null,
709 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion),
710 ],
711 );
712
713
714 $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion);
715 $isolatedurl->params(['parent' => $discussion1reply1->id]);
716 $expectedposts['posts'][] = array(
717 'id' => $discussion1reply1->id,
718 'discussionid' => $discussion1reply1->discussion,
719 'parentid' => $discussion1reply1->parent,
720 'hasparent' => true,
721 'timecreated' => $discussion1reply1->created,
722 'subject' => $discussion1reply1->subject,
723 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
724 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
725 'messageformat' => 1, // This value is usually changed by external_format_text() function.
726 'unread' => null,
727 'isdeleted' => false,
728 'isprivatereply' => false,
729 'haswordcount' => false,
730 'wordcount' => null,
731 'author'=> $exporteduser2,
732 'attachments' => [],
733 'tags' => [],
734 'html' => [
735 'rating' => null,
736 'taglist' => null,
737 'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser2, $discussion1reply1->created)
738 ],
739 'capabilities' => [
740 'view' => 1,
741 'edit' => 0,
742 'delete' => 0,
743 'split' => 0,
744 'reply' => 1,
745 'export' => 0,
746 'controlreadstatus' => 0
747 ],
748 'urls' => [
749 'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->id),
750 'viewisolated' => $isolatedurl->out(false),
751 'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->parent),
752 'edit' => null,
753 'delete' =>null,
754 'split' => null,
755 'reply' => (new moodle_url('/mod/forum/post.php#mformforum', [
756 'reply' => $discussion1reply1->id
757 ]))->out(false),
758 'export' => null,
759 'markasread' => null,
760 'markasunread' => null,
761 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion),
762 ],
763 );
764
765 // Test a discussion with two additional posts (total 3 posts).
766 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
767 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
768 $this->assertEquals(3, count($posts['posts']));
769
770 // Unset the initial discussion post.
771 array_pop($posts['posts']);
772 $this->assertEquals($expectedposts, $posts);
773
774 // Check we receive the unread count correctly on tracked forum.
775 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
776 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
777 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
778 foreach ($result as $f) {
779 if ($f['id'] == $forum2->id) {
780 $this->assertEquals(1, $f['unreadpostscount']);
781 }
782 }
783
784 // Test discussion without additional posts. There should be only one post (the one created by the discussion).
785 $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'DESC');
786 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
787 $this->assertEquals(1, count($posts['posts']));
788
789 // Test discussion tracking on not tracked forum.
790 $result = mod_forum_external::view_forum_discussion($discussion1->id);
791 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
792 $this->assertTrue($result['status']);
793 $this->assertEmpty($result['warnings']);
794
795 // Test posts have not been marked as read.
796 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
797 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
798 foreach ($posts['posts'] as $post) {
799 $this->assertNull($post['unread']);
800 }
801
802 // Test discussion tracking on tracked forum.
803 $result = mod_forum_external::view_forum_discussion($discussion3->id);
804 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
805 $this->assertTrue($result['status']);
806 $this->assertEmpty($result['warnings']);
807
808 // Test posts have been marked as read.
809 $posts = mod_forum_external::get_discussion_posts($discussion3->id, 'modified', 'DESC');
810 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
811 foreach ($posts['posts'] as $post) {
812 $this->assertFalse($post['unread']);
813 }
814
815 // Check we receive 0 unread posts.
816 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
817 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
818 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
819 foreach ($result as $f) {
820 if ($f['id'] == $forum2->id) {
821 $this->assertEquals(0, $f['unreadpostscount']);
822 }
823 }
824 }
825
3e95e09b
AN
826 /**
827 * Test get forum posts
828 */
829 public function test_mod_forum_get_forum_discussion_posts_deleted() {
830 global $CFG, $PAGE;
831
832 $this->resetAfterTest(true);
833 $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
834
835 // Create a course and enrol some users in it.
836 $course1 = self::getDataGenerator()->create_course();
837
838 // Create users.
839 $user1 = self::getDataGenerator()->create_user();
840 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
841 $user2 = self::getDataGenerator()->create_user();
842 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
843
844 // Set the first created user to the test user.
845 self::setUser($user1);
846
847 // Create test data.
848 $forum1 = self::getDataGenerator()->create_module('forum', (object) [
849 'course' => $course1->id,
850 ]);
851 $forum1context = context_module::instance($forum1->cmid);
852
853 // Add discussions to the forum.
854 $discussion = $generator->create_discussion((object) [
855 'course' => $course1->id,
856 'userid' => $user1->id,
857 'forum' => $forum1->id,
858 ]);
859
860 $discussion2 = $generator->create_discussion((object) [
861 'course' => $course1->id,
862 'userid' => $user2->id,
863 'forum' => $forum1->id,
864 ]);
865
866 // Add replies to the discussion.
867 $discussionreply1 = $generator->create_post((object) [
868 'discussion' => $discussion->id,
869 'parent' => $discussion->firstpost,
870 'userid' => $user2->id,
871 ]);
872 $discussionreply2 = $generator->create_post((object) [
873 'discussion' => $discussion->id,
874 'parent' => $discussionreply1->id,
875 'userid' => $user2->id,
876 'subject' => '',
877 'message' => '',
878 'messageformat' => FORMAT_PLAIN,
879 'deleted' => 1,
880 ]);
881 $discussionreply3 = $generator->create_post((object) [
882 'discussion' => $discussion->id,
883 'parent' => $discussion->firstpost,
884 'userid' => $user2->id,
885 ]);
886
887 // Test where some posts have been marked as deleted.
888 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id, 'modified', 'DESC');
889 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
890 $deletedsubject = get_string('privacy:request:delete:post:subject', 'mod_forum');
891 $deletedmessage = get_string('privacy:request:delete:post:message', 'mod_forum');
892
893 foreach ($posts['posts'] as $post) {
894 if ($post['id'] == $discussionreply2->id) {
895 $this->assertTrue($post['deleted']);
896 $this->assertEquals($deletedsubject, $post['subject']);
897 $this->assertEquals($deletedmessage, $post['message']);
898 } else {
899 $this->assertFalse($post['deleted']);
900 $this->assertNotEquals($deletedsubject, $post['subject']);
901 $this->assertNotEquals($deletedmessage, $post['message']);
902 }
903 }
904 }
905
b1aa7dfa
JL
906 /**
907 * Test get forum posts (qanda forum)
908 */
909 public function test_mod_forum_get_forum_discussion_posts_qanda() {
910 global $CFG, $DB;
911
912 $this->resetAfterTest(true);
913
914 $record = new stdClass();
915 $user1 = self::getDataGenerator()->create_user($record);
916 $user2 = self::getDataGenerator()->create_user();
917
918 // Set the first created user to the test user.
919 self::setUser($user1);
920
921 // Create course to add the module.
922 $course1 = self::getDataGenerator()->create_course();
923 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
924 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
925
926 // Forum with tracking off.
927 $record = new stdClass();
928 $record->course = $course1->id;
929 $record->type = 'qanda';
930 $forum1 = self::getDataGenerator()->create_module('forum', $record);
931 $forum1context = context_module::instance($forum1->cmid);
932
933 // Add discussions to the forums.
934 $record = new stdClass();
935 $record->course = $course1->id;
936 $record->userid = $user2->id;
937 $record->forum = $forum1->id;
938 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
939
940 // Add 1 reply (not the actual user).
941 $record = new stdClass();
942 $record->discussion = $discussion1->id;
943 $record->parent = $discussion1->firstpost;
944 $record->userid = $user2->id;
945 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
946
947 // We still see only the original post.
948 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
949 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
950 $this->assertEquals(1, count($posts['posts']));
951
952 // Add a new reply, the user is going to be able to see only the original post and their new post.
953 $record = new stdClass();
954 $record->discussion = $discussion1->id;
955 $record->parent = $discussion1->firstpost;
956 $record->userid = $user1->id;
957 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
958
959 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
960 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
961 $this->assertEquals(2, count($posts['posts']));
962
963 // Now, we can fake the time of the user post, so he can se the rest of the discussion posts.
964 $discussion1reply2->created -= $CFG->maxeditingtime * 2;
965 $DB->update_record('forum_posts', $discussion1reply2);
966
967 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
968 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
969 $this->assertEquals(3, count($posts['posts']));
b1aa7dfa
JL
970 }
971
c2586672
JL
972 /**
973 * Test get forum discussions paginated
974 */
975 public function test_mod_forum_get_forum_discussions_paginated() {
d85bedf7 976 global $USER, $CFG, $DB, $PAGE;
c2586672
JL
977
978 $this->resetAfterTest(true);
979
980 // Set the CFG variable to allow track forums.
981 $CFG->forum_trackreadposts = true;
982
983 // Create a user who can track forums.
984 $record = new stdClass();
985 $record->trackforums = true;
986 $user1 = self::getDataGenerator()->create_user($record);
987 // Create a bunch of other users to post.
988 $user2 = self::getDataGenerator()->create_user();
989 $user3 = self::getDataGenerator()->create_user();
990 $user4 = self::getDataGenerator()->create_user();
991
992 // Set the first created user to the test user.
993 self::setUser($user1);
994
995 // Create courses to add the modules.
996 $course1 = self::getDataGenerator()->create_course();
997
998 // First forum with tracking off.
999 $record = new stdClass();
1000 $record->course = $course1->id;
1001 $record->trackingtype = FORUM_TRACKING_OFF;
1002 $forum1 = self::getDataGenerator()->create_module('forum', $record);
1003
1004 // Add discussions to the forums.
1005 $record = new stdClass();
1006 $record->course = $course1->id;
1007 $record->userid = $user1->id;
1008 $record->forum = $forum1->id;
1009 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1010
1011 // Add three replies to the discussion 1 from different users.
1012 $record = new stdClass();
1013 $record->discussion = $discussion1->id;
1014 $record->parent = $discussion1->firstpost;
1015 $record->userid = $user2->id;
1016 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1017
1018 $record->parent = $discussion1reply1->id;
1019 $record->userid = $user3->id;
1020 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1021
1022 $record->userid = $user4->id;
1023 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1024
1025 // Enrol the user in the first course.
1026 $enrol = enrol_get_plugin('manual');
1027
1028 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
1029 $enrolinstances = enrol_get_instances($course1->id, true);
1030 foreach ($enrolinstances as $courseenrolinstance) {
1031 if ($courseenrolinstance->enrol == "manual") {
1032 $instance1 = $courseenrolinstance;
1033 break;
1034 }
1035 }
1036 $enrol->enrol_user($instance1, $user1->id);
1037
81f810dc
JL
1038 // Delete one user.
1039 delete_user($user4);
1040
c2586672
JL
1041 // Assign capabilities to view discussions for forum 1.
1042 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
1043 $context = context_module::instance($cm->id);
1044 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
1045 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1046
1047 // Create what we expect to be returned when querying the forums.
1048
1049 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
c2586672 1050
d85bedf7 1051 // User pictures are initially empty, we should get the links once the external function is called.
c2586672
JL
1052 $expecteddiscussions = array(
1053 'id' => $discussion1->firstpost,
1054 'name' => $discussion1->name,
1055 'groupid' => $discussion1->groupid,
1056 'timemodified' => $discussion1reply3->created,
1057 'usermodified' => $discussion1reply3->userid,
1058 'timestart' => $discussion1->timestart,
1059 'timeend' => $discussion1->timeend,
1060 'discussion' => $discussion1->id,
1061 'parent' => 0,
1062 'userid' => $discussion1->userid,
1063 'created' => $post1->created,
1064 'modified' => $post1->modified,
1065 'mailed' => $post1->mailed,
1066 'subject' => $post1->subject,
1067 'message' => $post1->message,
1068 'messageformat' => $post1->messageformat,
1069 'messagetrust' => $post1->messagetrust,
1070 'attachment' => $post1->attachment,
1071 'totalscore' => $post1->totalscore,
1072 'mailnow' => $post1->mailnow,
1073 'userfullname' => fullname($user1),
1074 'usermodifiedfullname' => fullname($user4),
d85bedf7
JL
1075 'userpictureurl' => '',
1076 'usermodifiedpictureurl' => '',
c2586672 1077 'numreplies' => 3,
5f219cf1 1078 'numunread' => 0,
0f3bbfd4
AN
1079 'pinned' => FORUM_DISCUSSION_UNPINNED,
1080 'locked' => false,
1081 'canreply' => false,
565cccfa 1082 'canlock' => false,
279128bb
P
1083 'starred' => false,
1084 'canfavourite' => true,
c2586672
JL
1085 );
1086
1087 // Call the external function passing forum id.
1088 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
1089 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1090 $expectedreturn = array(
1091 'discussions' => array($expecteddiscussions),
1092 'warnings' => array()
1093 );
d85bedf7
JL
1094
1095 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
1096 $userpicture = new user_picture($user1);
1097 $userpicture->size = 1; // Size f1.
1098 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1099
1100 $userpicture = new user_picture($user4);
1101 $userpicture->size = 1; // Size f1.
1102 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1103
c2586672
JL
1104 $this->assertEquals($expectedreturn, $discussions);
1105
d3cac88d 1106 // Test the starring functionality return.
279128bb
P
1107 $t = mod_forum_external::toggle_favourite_state($discussion1->id, 1);
1108 $expectedreturn['discussions'][0]['starred'] = true;
1109 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
1110 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1111 $this->assertEquals($expectedreturn, $discussions);
1112
c2586672
JL
1113 // Call without required view discussion capability.
1114 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1115 try {
1116 mod_forum_external::get_forum_discussions_paginated($forum1->id);
1117 $this->fail('Exception expected due to missing capability.');
1118 } catch (moodle_exception $e) {
1119 $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
1120 }
1121
1122 // Unenrol user from second course.
1123 $enrol->unenrol_user($instance1, $user1->id);
1124
1125 // Call for the second course we unenrolled the user from, make sure exception thrown.
1126 try {
1127 mod_forum_external::get_forum_discussions_paginated($forum1->id);
1128 $this->fail('Exception expected due to being unenrolled from the course.');
1129 } catch (moodle_exception $e) {
1130 $this->assertEquals('requireloginerror', $e->errorcode);
1131 }
565cccfa
P
1132
1133 $this->setAdminUser();
1134 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
1135 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1136 $this->assertTrue($discussions['discussions'][0]['canlock']);
c2586672 1137 }
039c81f0
JL
1138
1139 /**
1140 * Test get forum discussions paginated (qanda forums)
1141 */
1142 public function test_mod_forum_get_forum_discussions_paginated_qanda() {
1143
1144 $this->resetAfterTest(true);
1145
1146 // Create courses to add the modules.
1147 $course = self::getDataGenerator()->create_course();
1148
1149 $user1 = self::getDataGenerator()->create_user();
1150 $user2 = self::getDataGenerator()->create_user();
1151
1152 // First forum with tracking off.
1153 $record = new stdClass();
1154 $record->course = $course->id;
1155 $record->type = 'qanda';
1156 $forum = self::getDataGenerator()->create_module('forum', $record);
1157
1158 // Add discussions to the forums.
1159 $discussionrecord = new stdClass();
1160 $discussionrecord->course = $course->id;
1161 $discussionrecord->userid = $user2->id;
1162 $discussionrecord->forum = $forum->id;
1163 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
1164
1165 self::setAdminUser();
1166 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1167 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1168
1169 $this->assertCount(1, $discussions['discussions']);
1170 $this->assertCount(0, $discussions['warnings']);
1171
1172 self::setUser($user1);
1173 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1174
1175 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1176 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1177
1178 $this->assertCount(1, $discussions['discussions']);
1179 $this->assertCount(0, $discussions['warnings']);
1180
1181 }
50a20317
JL
1182
1183 /**
1184 * Test add_discussion_post
1185 */
1186 public function test_add_discussion_post() {
e881c4f5 1187 global $CFG;
50a20317
JL
1188
1189 $this->resetAfterTest(true);
1190
1191 $user = self::getDataGenerator()->create_user();
1192 $otheruser = self::getDataGenerator()->create_user();
1193
1194 self::setAdminUser();
1195
1196 // Create course to add the module.
1197 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
1198
1199 // Forum with tracking off.
1200 $record = new stdClass();
1201 $record->course = $course->id;
1202 $forum = self::getDataGenerator()->create_module('forum', $record);
1203 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
1204 $forumcontext = context_module::instance($forum->cmid);
1205
1206 // Add discussions to the forums.
1207 $record = new stdClass();
1208 $record->course = $course->id;
1209 $record->userid = $user->id;
1210 $record->forum = $forum->id;
1211 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1212
1213 // Try to post (user not enrolled).
1214 self::setUser($user);
1215 try {
1216 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1217 $this->fail('Exception expected due to being unenrolled from the course.');
1218 } catch (moodle_exception $e) {
1219 $this->assertEquals('requireloginerror', $e->errorcode);
1220 }
1221
1222 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1223 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
1224
41182118
BK
1225 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1226 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
50a20317
JL
1227
1228 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1229 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1230 // We receive the discussion and the post.
1231 $this->assertEquals(2, count($posts['posts']));
43ff833d
JL
1232
1233 $tested = false;
41182118
BK
1234 foreach ($posts['posts'] as $thispost) {
1235 if ($createdpost['postid'] == $thispost['id']) {
1236 $this->assertEquals('some subject', $thispost['subject']);
1237 $this->assertEquals('some text here...', $thispost['message']);
43ff833d
JL
1238 $tested = true;
1239 }
1240 }
1241 $this->assertTrue($tested);
50a20317 1242
e881c4f5 1243 // Test inline and regular attachment in post
41182118
BK
1244 // Create a file in a draft area for inline attachments.
1245 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5 1246 $draftidattach = file_get_unused_draft_itemid();
41182118
BK
1247 self::setUser($user);
1248 $usercontext = context_user::instance($user->id);
1249 $filepath = '/';
1250 $filearea = 'draft';
1251 $component = 'user';
1252 $filenameimg = 'shouldbeanimage.txt';
e881c4f5 1253 $filerecordinline = array(
41182118
BK
1254 'contextid' => $usercontext->id,
1255 'component' => $component,
1256 'filearea' => $filearea,
1257 'itemid' => $draftidinlineattach,
1258 'filepath' => $filepath,
1259 'filename' => $filenameimg,
1260 );
1261 $fs = get_file_storage();
41182118 1262
e881c4f5
BK
1263 // Create a file in a draft area for regular attachments.
1264 $filerecordattach = $filerecordinline;
1265 $attachfilename = 'attachment.txt';
1266 $filerecordattach['filename'] = $attachfilename;
1267 $filerecordattach['itemid'] = $draftidattach;
1268 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
1269 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
1270
48143990 1271 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 1272 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
1273 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
1274 . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
1275 . '" alt="inlineimage">.';
1276 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
1277 $dummytext, $options);
1278 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
1279
1280 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1281 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1282 // We receive the discussion and the post.
1283 // Can't guarantee order of posts during tests.
1284 $postfound = false;
1285 foreach ($posts['posts'] as $thispost) {
1286 if ($createdpost['postid'] == $thispost['id']) {
1287 $this->assertEquals($createdpost['postid'], $thispost['id']);
e881c4f5
BK
1288 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
1289 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
1290 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
1291 $this->assertContains('pluginfile.php', $thispost['message']);
1292 $postfound = true;
e881c4f5 1293 break;
41182118
BK
1294 }
1295 }
1296
1297 $this->assertTrue($postfound);
1298
50a20317
JL
1299 // Check not posting in groups the user is not member of.
1300 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1301 groups_add_member($group->id, $otheruser->id);
1302
1303 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
1304 $record->forum = $forum->id;
1305 $record->userid = $otheruser->id;
1306 $record->groupid = $group->id;
1307 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1308
1309 try {
1310 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1311 $this->fail('Exception expected due to invalid permissions for posting.');
1312 } catch (moodle_exception $e) {
50a20317
JL
1313 $this->assertEquals('nopostforum', $e->errorcode);
1314 }
1315
1316 }
7ab43ac8
JL
1317
1318 /*
1319 * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
1320 */
1321 public function test_add_discussion() {
41182118 1322 global $CFG, $USER;
7ab43ac8
JL
1323 $this->resetAfterTest(true);
1324
1325 // Create courses to add the modules.
1326 $course = self::getDataGenerator()->create_course();
1327
1328 $user1 = self::getDataGenerator()->create_user();
1329 $user2 = self::getDataGenerator()->create_user();
1330
1331 // First forum with tracking off.
1332 $record = new stdClass();
1333 $record->course = $course->id;
1334 $record->type = 'news';
1335 $forum = self::getDataGenerator()->create_module('forum', $record);
1336
1337 self::setUser($user1);
1338 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1339
1340 try {
1341 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1342 $this->fail('Exception expected due to invalid permissions.');
1343 } catch (moodle_exception $e) {
1344 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1345 }
1346
1347 self::setAdminUser();
41182118
BK
1348 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1349 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
7ab43ac8
JL
1350
1351 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1352 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1353
1354 $this->assertCount(1, $discussions['discussions']);
1355 $this->assertCount(0, $discussions['warnings']);
1356
41182118 1357 $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
7ab43ac8
JL
1358 $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
1359 $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
1360 $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
1361
5f219cf1
BK
1362 $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
1363 array('options' => array('name' => 'discussionpinned',
1364 'value' => true)));
1365 $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
1366 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1367 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1368 $this->assertCount(3, $discussions['discussions']);
1369 $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
41182118 1370
e881c4f5 1371 // Test inline and regular attachment in new discussion
41182118 1372 // Create a file in a draft area for inline attachments.
e881c4f5
BK
1373
1374 $fs = get_file_storage();
1375
41182118 1376 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5
BK
1377 $draftidattach = file_get_unused_draft_itemid();
1378
41182118
BK
1379 $usercontext = context_user::instance($USER->id);
1380 $filepath = '/';
1381 $filearea = 'draft';
1382 $component = 'user';
1383 $filenameimg = 'shouldbeanimage.txt';
1384 $filerecord = array(
1385 'contextid' => $usercontext->id,
1386 'component' => $component,
1387 'filearea' => $filearea,
1388 'itemid' => $draftidinlineattach,
1389 'filepath' => $filepath,
1390 'filename' => $filenameimg,
1391 );
e881c4f5 1392
e881c4f5
BK
1393 // Create a file in a draft area for regular attachments.
1394 $filerecordattach = $filerecord;
1395 $attachfilename = 'attachment.txt';
1396 $filerecordattach['filename'] = $attachfilename;
1397 $filerecordattach['itemid'] = $draftidattach;
41182118 1398 $fs->create_file_from_string($filerecord, 'image contents (not really)');
e881c4f5
BK
1399 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
1400
41182118
BK
1401 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
1402 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
1403 '" alt="inlineimage">.';
1404
48143990 1405 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 1406 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
1407 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
1408 $dummytext, -1, $options);
1409 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
1410
1411 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1412 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1413
1414 $this->assertCount(4, $discussions['discussions']);
1415 $this->assertCount(0, $createddiscussion['warnings']);
1416 // Can't guarantee order of posts during tests.
1417 $postfound = false;
1418 foreach ($discussions['discussions'] as $thisdiscussion) {
1419 if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
e881c4f5
BK
1420 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
1421 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
1422 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
1423 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
1424 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
1425 $postfound = true;
e881c4f5 1426 break;
41182118
BK
1427 }
1428 }
1429
1430 $this->assertTrue($postfound);
7ab43ac8
JL
1431 }
1432
1433 /**
1434 * Test adding discussions in a course with gorups
1435 */
1436 public function test_add_discussion_in_course_with_groups() {
1437 global $CFG;
1438
1439 $this->resetAfterTest(true);
1440
1441 // Create course to add the module.
1442 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
1443 $user = self::getDataGenerator()->create_user();
1444 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1445
1446 // Forum forcing separate gropus.
1447 $record = new stdClass();
1448 $record->course = $course->id;
1449 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
1450
1451 // Try to post (user not enrolled).
1452 self::setUser($user);
1453
1454 // The user is not enroled in any group, try to post in a forum with separate groups.
1455 try {
1456 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1457 $this->fail('Exception expected due to invalid group permissions.');
1458 } catch (moodle_exception $e) {
1459 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1460 }
1461
1462 try {
1463 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
1464 $this->fail('Exception expected due to invalid group permissions.');
1465 } catch (moodle_exception $e) {
1466 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1467 }
1468
1469 // Create a group.
1470 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1471
1472 // Try to post in a group the user is not enrolled.
1473 try {
1474 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
1475 $this->fail('Exception expected due to invalid group permissions.');
1476 } catch (moodle_exception $e) {
1477 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1478 }
1479
1480 // Add the user to a group.
1481 groups_add_member($group->id, $user->id);
1482
1483 // Try to post in a group the user is not enrolled.
1484 try {
1485 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
1486 $this->fail('Exception expected due to invalid group.');
1487 } catch (moodle_exception $e) {
1488 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1489 }
1490
1491 // Nost add the discussion using a valid group.
1492 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
1493 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1494
1495 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1496 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1497
1498 $this->assertCount(1, $discussions['discussions']);
1499 $this->assertCount(0, $discussions['warnings']);
1500 $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
1501 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1502
1503 // Now add a discussions without indicating a group. The function should guess the correct group.
1504 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1505 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1506
1507 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1508 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1509
1510 $this->assertCount(2, $discussions['discussions']);
1511 $this->assertCount(0, $discussions['warnings']);
1512 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1513 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1514
1515 // Enrol the same user in other group.
1516 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1517 groups_add_member($group2->id, $user->id);
1518
1519 // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
1520 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1521 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1522
1523 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1524 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1525
1526 $this->assertCount(3, $discussions['discussions']);
1527 $this->assertCount(0, $discussions['warnings']);
1528 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1529 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1530 $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
1531
1532 }
1533
f5b4320e
P
1534 /*
1535 * Test set_lock_state.
1536 */
1537 public function test_set_lock_state() {
1538 global $DB;
1539 $this->resetAfterTest(true);
1540
1541 // Create courses to add the modules.
1542 $course = self::getDataGenerator()->create_course();
1543 $user = self::getDataGenerator()->create_user();
1544 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1545
1546 // First forum with tracking off.
1547 $record = new stdClass();
1548 $record->course = $course->id;
1549 $record->type = 'news';
1550 $forum = self::getDataGenerator()->create_module('forum', $record);
1551
1552 $record = new stdClass();
1553 $record->course = $course->id;
1554 $record->userid = $user->id;
1555 $record->forum = $forum->id;
1556 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1557
1558 // User who is a student.
1559 self::setUser($user);
1560 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual');
1561
1562 // Only a teacher should be able to lock a discussion.
bdb4a87d
P
1563 try {
1564 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
1565 $this->fail('Exception expected due to missing capability.');
1566 } catch (moodle_exception $e) {
1567 $this->assertEquals('errorcannotlock', $e->errorcode);
1568 }
f5b4320e
P
1569
1570 // Set the lock.
1571 self::setAdminUser();
1572 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
1573 $result = external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
bdb4a87d 1574 $this->assertTrue($result['locked']);
f5b4320e
P
1575 $this->assertNotEquals(0, $result['times']['locked']);
1576
1577 // Unset the lock.
1578 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, time());
1579 $result = external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
bdb4a87d 1580 $this->assertFalse($result['locked']);
f5b4320e
P
1581 $this->assertEquals('0', $result['times']['locked']);
1582 }
1583
04cd8ae3
JL
1584 /*
1585 * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
1586 */
1587 public function test_can_add_discussion() {
581e75bf 1588 global $DB;
04cd8ae3
JL
1589 $this->resetAfterTest(true);
1590
1591 // Create courses to add the modules.
1592 $course = self::getDataGenerator()->create_course();
1593
1594 $user = self::getDataGenerator()->create_user();
1595
1596 // First forum with tracking off.
1597 $record = new stdClass();
1598 $record->course = $course->id;
1599 $record->type = 'news';
1600 $forum = self::getDataGenerator()->create_module('forum', $record);
1601
1602 // User with no permissions to add in a news forum.
1603 self::setUser($user);
1604 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1605
1606 $result = mod_forum_external::can_add_discussion($forum->id);
1607 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1608 $this->assertFalse($result['status']);
581e75bf
JL
1609 $this->assertFalse($result['canpindiscussions']);
1610 $this->assertTrue($result['cancreateattachment']);
1611
1612 // Disable attachments.
1613 $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id));
1614 $result = mod_forum_external::can_add_discussion($forum->id);
1615 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1616 $this->assertFalse($result['status']);
1617 $this->assertFalse($result['canpindiscussions']);
1618 $this->assertFalse($result['cancreateattachment']);
1619 $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id)); // Enable attachments again.
04cd8ae3
JL
1620
1621 self::setAdminUser();
1622 $result = mod_forum_external::can_add_discussion($forum->id);
1623 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1624 $this->assertTrue($result['status']);
581e75bf
JL
1625 $this->assertTrue($result['canpindiscussions']);
1626 $this->assertTrue($result['cancreateattachment']);
cbf63d8e
SR
1627 }
1628
1629 /*
1630 * A basic test to make sure users cannot post to forum after the cutoff date.
1631 */
1632 public function test_can_add_discussion_after_cutoff() {
1633 $this->resetAfterTest(true);
1634
1635 // Create courses to add the modules.
1636 $course = self::getDataGenerator()->create_course();
1637
1638 $user = self::getDataGenerator()->create_user();
1639
1640 // Create a forum with cutoff date set to a past date.
1641 $forum = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 'cutoffdate' => time() - 1]);
04cd8ae3 1642
cbf63d8e
SR
1643 // User with no mod/forum:canoverridecutoff capability.
1644 self::setUser($user);
1645 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1646
1647 $result = mod_forum_external::can_add_discussion($forum->id);
1648 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1649 $this->assertFalse($result['status']);
1650
1651 self::setAdminUser();
1652 $result = mod_forum_external::can_add_discussion($forum->id);
1653 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1654 $this->assertTrue($result['status']);
04cd8ae3
JL
1655 }
1656
b7ce46df
JL
1657 /**
1658 * Test get forum posts discussions including rating information.
1659 */
1660 public function test_mod_forum_get_forum_discussion_rating_information() {
1661 global $DB, $CFG;
1662 require_once($CFG->dirroot . '/rating/lib.php');
1663
1664 $this->resetAfterTest(true);
1665
1666 $user1 = self::getDataGenerator()->create_user();
1667 $user2 = self::getDataGenerator()->create_user();
1668 $user3 = self::getDataGenerator()->create_user();
1669 $teacher = self::getDataGenerator()->create_user();
1670
1671 // Create course to add the module.
1672 $course = self::getDataGenerator()->create_course();
1673
1674 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1675 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
1676 $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id, 'manual');
1677 $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id, 'manual');
1678 $this->getDataGenerator()->enrol_user($user3->id, $course->id, $studentrole->id, 'manual');
1679 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
1680
1681 // Create the forum.
1682 $record = new stdClass();
1683 $record->course = $course->id;
1684 // Set Aggregate type = Average of ratings.
1685 $record->assessed = RATING_AGGREGATE_AVERAGE;
1686 $record->scale = 100;
1687 $forum = self::getDataGenerator()->create_module('forum', $record);
1688 $context = context_module::instance($forum->cmid);
1689
1690 // Add discussion to the forum.
1691 $record = new stdClass();
1692 $record->course = $course->id;
1693 $record->userid = $user1->id;
1694 $record->forum = $forum->id;
1695 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1696
1697 // Retrieve the first post.
1698 $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
1699
1700 // Rate the discussion as user2.
1701 $rating1 = new stdClass();
1702 $rating1->contextid = $context->id;
1703 $rating1->component = 'mod_forum';
1704 $rating1->ratingarea = 'post';
1705 $rating1->itemid = $post->id;
1706 $rating1->rating = 50;
1707 $rating1->scaleid = 100;
1708 $rating1->userid = $user2->id;
1709 $rating1->timecreated = time();
1710 $rating1->timemodified = time();
1711 $rating1->id = $DB->insert_record('rating', $rating1);
1712
1713 // Rate the discussion as user3.
1714 $rating2 = new stdClass();
1715 $rating2->contextid = $context->id;
1716 $rating2->component = 'mod_forum';
1717 $rating2->ratingarea = 'post';
1718 $rating2->itemid = $post->id;
1719 $rating2->rating = 100;
1720 $rating2->scaleid = 100;
1721 $rating2->userid = $user3->id;
1722 $rating2->timecreated = time() + 1;
1723 $rating2->timemodified = time() + 1;
1724 $rating2->id = $DB->insert_record('rating', $rating2);
1725
1726 // Retrieve the rating for the post as student.
1727 $this->setUser($user1);
1728 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1729 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1730 $this->assertCount(1, $posts['ratinginfo']['ratings']);
1731 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
1732 $this->assertFalse($posts['ratinginfo']['canviewall']);
1733 $this->assertFalse($posts['ratinginfo']['ratings'][0]['canrate']);
1734 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
1735 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
1736
1737 // Retrieve the rating for the post as teacher.
1738 $this->setUser($teacher);
1739 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1740 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1741 $this->assertCount(1, $posts['ratinginfo']['ratings']);
1742 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
1743 $this->assertTrue($posts['ratinginfo']['canviewall']);
1744 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canrate']);
1745 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
1746 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
1747 }
4daa0d08
JL
1748
1749 /**
1750 * Test mod_forum_get_forum_access_information.
1751 */
1752 public function test_mod_forum_get_forum_access_information() {
1753 global $DB;
1754
1755 $this->resetAfterTest(true);
1756
1757 $student = self::getDataGenerator()->create_user();
1758 $course = self::getDataGenerator()->create_course();
1759 // Create the forum.
1760 $record = new stdClass();
1761 $record->course = $course->id;
1762 $forum = self::getDataGenerator()->create_module('forum', $record);
1763
1764 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1765 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
1766
1767 self::setUser($student);
1768 $result = mod_forum_external::get_forum_access_information($forum->id);
1769 $result = external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
1770
1771 // Check default values for capabilities.
1772 $enabledcaps = array('canviewdiscussion', 'canstartdiscussion', 'canreplypost', 'canviewrating', 'cancreateattachment',
13cd05ac 1773 'canexportownpost', 'cancantogglefavourite', 'candeleteownpost', 'canallowforcesubscribe');
4daa0d08
JL
1774
1775 unset($result['warnings']);
1776 foreach ($result as $capname => $capvalue) {
1777 if (in_array($capname, $enabledcaps)) {
1778 $this->assertTrue($capvalue);
1779 } else {
1780 $this->assertFalse($capvalue);
1781 }
1782 }
1783 // Now, unassign some capabilities.
1784 unassign_capability('mod/forum:deleteownpost', $studentrole->id);
1785 unassign_capability('mod/forum:allowforcesubscribe', $studentrole->id);
1786 array_pop($enabledcaps);
1787 array_pop($enabledcaps);
1788 accesslib_clear_all_caches_for_unit_testing();
1789
1790 $result = mod_forum_external::get_forum_access_information($forum->id);
1791 $result = external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
1792 unset($result['warnings']);
1793 foreach ($result as $capname => $capvalue) {
1794 if (in_array($capname, $enabledcaps)) {
1795 $this->assertTrue($capvalue);
1796 } else {
1797 $this->assertFalse($capvalue);
1798 }
1799 }
1800 }
bc4c7337
AN
1801
1802 /**
1803 * Test add_discussion_post
1804 */
1805 public function test_add_discussion_post_private() {
1806 global $DB;
1807
1808 $this->resetAfterTest(true);
1809
1810 self::setAdminUser();
1811
1812 // Create course to add the module.
1813 $course = self::getDataGenerator()->create_course();
1814
1815 // Standard forum.
1816 $record = new stdClass();
1817 $record->course = $course->id;
1818 $forum = self::getDataGenerator()->create_module('forum', $record);
1819 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
1820 $forumcontext = context_module::instance($forum->cmid);
1821 $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
1822
1823 // Create an enrol users.
1824 $student1 = self::getDataGenerator()->create_user();
1825 $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
1826 $student2 = self::getDataGenerator()->create_user();
1827 $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
1828 $teacher1 = self::getDataGenerator()->create_user();
1829 $this->getDataGenerator()->enrol_user($teacher1->id, $course->id, 'editingteacher');
1830 $teacher2 = self::getDataGenerator()->create_user();
1831 $this->getDataGenerator()->enrol_user($teacher2->id, $course->id, 'editingteacher');
1832
1833 // Add a new discussion to the forum.
1834 self::setUser($student1);
1835 $record = new stdClass();
1836 $record->course = $course->id;
1837 $record->userid = $student1->id;
1838 $record->forum = $forum->id;
1839 $discussion = $generator->create_discussion($record);
1840
1841 // Have the teacher reply privately.
1842 self::setUser($teacher1);
1843 $post = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...', [
1844 [
1845 'name' => 'private',
1846 'value' => true,
1847 ],
1848 ]);
1849 $post = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $post);
1850 $privatereply = $DB->get_record('forum_posts', array('id' => $post['postid']));
1851 $this->assertEquals($student1->id, $privatereply->privatereplyto);
1852 // Bump the time of the private reply to ensure order.
1853 $privatereply->created++;
1854 $privatereply->modified = $privatereply->created;
1855 $DB->update_record('forum_posts', $privatereply);
1856
1857 // The teacher will receive their private reply.
1858 self::setUser($teacher1);
1859 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1860 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1861 $this->assertEquals(2, count($posts['posts']));
1862 $this->assertTrue($posts['posts'][0]['isprivatereply']);
1863
1864 // Another teacher on the course will also receive the private reply.
1865 self::setUser($teacher2);
1866 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1867 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1868 $this->assertEquals(2, count($posts['posts']));
1869 $this->assertTrue($posts['posts'][0]['isprivatereply']);
1870
1871 // The student will receive the private reply.
1872 self::setUser($student1);
1873 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1874 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1875 $this->assertEquals(2, count($posts['posts']));
1876 $this->assertTrue($posts['posts'][0]['isprivatereply']);
1877
1878 // Another student will not receive the private reply.
1879 self::setUser($student2);
1880 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1881 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1882 $this->assertEquals(1, count($posts['posts']));
1883 $this->assertFalse($posts['posts'][0]['isprivatereply']);
1884
1885 // A user cannot reply to a private reply.
1886 self::setUser($teacher2);
1887 $this->expectException('coding_exception');
1888 $post = mod_forum_external::add_discussion_post($privatereply->id, 'some subject', 'some text here...', [
1889 'options' => [
1890 'name' => 'private',
1891 'value' => false,
1892 ],
1893 ]);
1894 }
2b9fe87d 1895}