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