MDL-65033 mod_forum: Add a new menu dropdown to the forum list
[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,
565cccfa 988 'canlock' => false,
c2586672
JL
989 );
990
991 // Call the external function passing forum id.
992 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
993 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
994 $expectedreturn = array(
995 'discussions' => array($expecteddiscussions),
996 'warnings' => array()
997 );
d85bedf7
JL
998
999 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
1000 $userpicture = new user_picture($user1);
1001 $userpicture->size = 1; // Size f1.
1002 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1003
1004 $userpicture = new user_picture($user4);
1005 $userpicture->size = 1; // Size f1.
1006 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1007
c2586672
JL
1008 $this->assertEquals($expectedreturn, $discussions);
1009
1010 // Call without required view discussion capability.
1011 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1012 try {
1013 mod_forum_external::get_forum_discussions_paginated($forum1->id);
1014 $this->fail('Exception expected due to missing capability.');
1015 } catch (moodle_exception $e) {
1016 $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
1017 }
1018
1019 // Unenrol user from second course.
1020 $enrol->unenrol_user($instance1, $user1->id);
1021
1022 // Call for the second course we unenrolled the user from, make sure exception thrown.
1023 try {
1024 mod_forum_external::get_forum_discussions_paginated($forum1->id);
1025 $this->fail('Exception expected due to being unenrolled from the course.');
1026 } catch (moodle_exception $e) {
1027 $this->assertEquals('requireloginerror', $e->errorcode);
1028 }
565cccfa
P
1029
1030 $this->setAdminUser();
1031 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
1032 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1033 $this->assertTrue($discussions['discussions'][0]['canlock']);
c2586672 1034 }
039c81f0
JL
1035
1036 /**
1037 * Test get forum discussions paginated (qanda forums)
1038 */
1039 public function test_mod_forum_get_forum_discussions_paginated_qanda() {
1040
1041 $this->resetAfterTest(true);
1042
1043 // Create courses to add the modules.
1044 $course = self::getDataGenerator()->create_course();
1045
1046 $user1 = self::getDataGenerator()->create_user();
1047 $user2 = self::getDataGenerator()->create_user();
1048
1049 // First forum with tracking off.
1050 $record = new stdClass();
1051 $record->course = $course->id;
1052 $record->type = 'qanda';
1053 $forum = self::getDataGenerator()->create_module('forum', $record);
1054
1055 // Add discussions to the forums.
1056 $discussionrecord = new stdClass();
1057 $discussionrecord->course = $course->id;
1058 $discussionrecord->userid = $user2->id;
1059 $discussionrecord->forum = $forum->id;
1060 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
1061
1062 self::setAdminUser();
1063 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1064 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1065
1066 $this->assertCount(1, $discussions['discussions']);
1067 $this->assertCount(0, $discussions['warnings']);
1068
1069 self::setUser($user1);
1070 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1071
1072 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1073 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1074
1075 $this->assertCount(1, $discussions['discussions']);
1076 $this->assertCount(0, $discussions['warnings']);
1077
1078 }
50a20317
JL
1079
1080 /**
1081 * Test add_discussion_post
1082 */
1083 public function test_add_discussion_post() {
e881c4f5 1084 global $CFG;
50a20317
JL
1085
1086 $this->resetAfterTest(true);
1087
1088 $user = self::getDataGenerator()->create_user();
1089 $otheruser = self::getDataGenerator()->create_user();
1090
1091 self::setAdminUser();
1092
1093 // Create course to add the module.
1094 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
1095
1096 // Forum with tracking off.
1097 $record = new stdClass();
1098 $record->course = $course->id;
1099 $forum = self::getDataGenerator()->create_module('forum', $record);
1100 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
1101 $forumcontext = context_module::instance($forum->cmid);
1102
1103 // Add discussions to the forums.
1104 $record = new stdClass();
1105 $record->course = $course->id;
1106 $record->userid = $user->id;
1107 $record->forum = $forum->id;
1108 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1109
1110 // Try to post (user not enrolled).
1111 self::setUser($user);
1112 try {
1113 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1114 $this->fail('Exception expected due to being unenrolled from the course.');
1115 } catch (moodle_exception $e) {
1116 $this->assertEquals('requireloginerror', $e->errorcode);
1117 }
1118
1119 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1120 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
1121
41182118
BK
1122 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1123 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
50a20317
JL
1124
1125 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1126 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1127 // We receive the discussion and the post.
1128 $this->assertEquals(2, count($posts['posts']));
43ff833d
JL
1129
1130 $tested = false;
41182118
BK
1131 foreach ($posts['posts'] as $thispost) {
1132 if ($createdpost['postid'] == $thispost['id']) {
1133 $this->assertEquals('some subject', $thispost['subject']);
1134 $this->assertEquals('some text here...', $thispost['message']);
43ff833d
JL
1135 $tested = true;
1136 }
1137 }
1138 $this->assertTrue($tested);
50a20317 1139
e881c4f5 1140 // Test inline and regular attachment in post
41182118
BK
1141 // Create a file in a draft area for inline attachments.
1142 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5 1143 $draftidattach = file_get_unused_draft_itemid();
41182118
BK
1144 self::setUser($user);
1145 $usercontext = context_user::instance($user->id);
1146 $filepath = '/';
1147 $filearea = 'draft';
1148 $component = 'user';
1149 $filenameimg = 'shouldbeanimage.txt';
e881c4f5 1150 $filerecordinline = array(
41182118
BK
1151 'contextid' => $usercontext->id,
1152 'component' => $component,
1153 'filearea' => $filearea,
1154 'itemid' => $draftidinlineattach,
1155 'filepath' => $filepath,
1156 'filename' => $filenameimg,
1157 );
1158 $fs = get_file_storage();
41182118 1159
e881c4f5
BK
1160 // Create a file in a draft area for regular attachments.
1161 $filerecordattach = $filerecordinline;
1162 $attachfilename = 'attachment.txt';
1163 $filerecordattach['filename'] = $attachfilename;
1164 $filerecordattach['itemid'] = $draftidattach;
1165 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
1166 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
1167
48143990 1168 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 1169 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
1170 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
1171 . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
1172 . '" alt="inlineimage">.';
1173 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
1174 $dummytext, $options);
1175 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
1176
1177 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1178 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1179 // We receive the discussion and the post.
1180 // Can't guarantee order of posts during tests.
1181 $postfound = false;
1182 foreach ($posts['posts'] as $thispost) {
1183 if ($createdpost['postid'] == $thispost['id']) {
1184 $this->assertEquals($createdpost['postid'], $thispost['id']);
e881c4f5
BK
1185 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
1186 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
1187 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
1188 $this->assertContains('pluginfile.php', $thispost['message']);
1189 $postfound = true;
e881c4f5 1190 break;
41182118
BK
1191 }
1192 }
1193
1194 $this->assertTrue($postfound);
1195
50a20317
JL
1196 // Check not posting in groups the user is not member of.
1197 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1198 groups_add_member($group->id, $otheruser->id);
1199
1200 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
1201 $record->forum = $forum->id;
1202 $record->userid = $otheruser->id;
1203 $record->groupid = $group->id;
1204 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1205
1206 try {
1207 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1208 $this->fail('Exception expected due to invalid permissions for posting.');
1209 } catch (moodle_exception $e) {
50a20317
JL
1210 $this->assertEquals('nopostforum', $e->errorcode);
1211 }
1212
1213 }
7ab43ac8
JL
1214
1215 /*
1216 * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
1217 */
1218 public function test_add_discussion() {
41182118 1219 global $CFG, $USER;
7ab43ac8
JL
1220 $this->resetAfterTest(true);
1221
1222 // Create courses to add the modules.
1223 $course = self::getDataGenerator()->create_course();
1224
1225 $user1 = self::getDataGenerator()->create_user();
1226 $user2 = self::getDataGenerator()->create_user();
1227
1228 // First forum with tracking off.
1229 $record = new stdClass();
1230 $record->course = $course->id;
1231 $record->type = 'news';
1232 $forum = self::getDataGenerator()->create_module('forum', $record);
1233
1234 self::setUser($user1);
1235 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1236
1237 try {
1238 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1239 $this->fail('Exception expected due to invalid permissions.');
1240 } catch (moodle_exception $e) {
1241 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1242 }
1243
1244 self::setAdminUser();
41182118
BK
1245 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1246 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
7ab43ac8
JL
1247
1248 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1249 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1250
1251 $this->assertCount(1, $discussions['discussions']);
1252 $this->assertCount(0, $discussions['warnings']);
1253
41182118 1254 $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
7ab43ac8
JL
1255 $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
1256 $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
1257 $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
1258
5f219cf1
BK
1259 $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
1260 array('options' => array('name' => 'discussionpinned',
1261 'value' => true)));
1262 $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
1263 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1264 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1265 $this->assertCount(3, $discussions['discussions']);
1266 $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
41182118 1267
e881c4f5 1268 // Test inline and regular attachment in new discussion
41182118 1269 // Create a file in a draft area for inline attachments.
e881c4f5
BK
1270
1271 $fs = get_file_storage();
1272
41182118 1273 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5
BK
1274 $draftidattach = file_get_unused_draft_itemid();
1275
41182118
BK
1276 $usercontext = context_user::instance($USER->id);
1277 $filepath = '/';
1278 $filearea = 'draft';
1279 $component = 'user';
1280 $filenameimg = 'shouldbeanimage.txt';
1281 $filerecord = array(
1282 'contextid' => $usercontext->id,
1283 'component' => $component,
1284 'filearea' => $filearea,
1285 'itemid' => $draftidinlineattach,
1286 'filepath' => $filepath,
1287 'filename' => $filenameimg,
1288 );
e881c4f5 1289
e881c4f5
BK
1290 // Create a file in a draft area for regular attachments.
1291 $filerecordattach = $filerecord;
1292 $attachfilename = 'attachment.txt';
1293 $filerecordattach['filename'] = $attachfilename;
1294 $filerecordattach['itemid'] = $draftidattach;
41182118 1295 $fs->create_file_from_string($filerecord, 'image contents (not really)');
e881c4f5
BK
1296 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
1297
41182118
BK
1298 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
1299 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
1300 '" alt="inlineimage">.';
1301
48143990 1302 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 1303 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
1304 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
1305 $dummytext, -1, $options);
1306 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
1307
1308 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1309 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1310
1311 $this->assertCount(4, $discussions['discussions']);
1312 $this->assertCount(0, $createddiscussion['warnings']);
1313 // Can't guarantee order of posts during tests.
1314 $postfound = false;
1315 foreach ($discussions['discussions'] as $thisdiscussion) {
1316 if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
e881c4f5
BK
1317 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
1318 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
1319 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
1320 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
1321 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
1322 $postfound = true;
e881c4f5 1323 break;
41182118
BK
1324 }
1325 }
1326
1327 $this->assertTrue($postfound);
7ab43ac8
JL
1328 }
1329
1330 /**
1331 * Test adding discussions in a course with gorups
1332 */
1333 public function test_add_discussion_in_course_with_groups() {
1334 global $CFG;
1335
1336 $this->resetAfterTest(true);
1337
1338 // Create course to add the module.
1339 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
1340 $user = self::getDataGenerator()->create_user();
1341 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1342
1343 // Forum forcing separate gropus.
1344 $record = new stdClass();
1345 $record->course = $course->id;
1346 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
1347
1348 // Try to post (user not enrolled).
1349 self::setUser($user);
1350
1351 // The user is not enroled in any group, try to post in a forum with separate groups.
1352 try {
1353 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1354 $this->fail('Exception expected due to invalid group permissions.');
1355 } catch (moodle_exception $e) {
1356 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1357 }
1358
1359 try {
1360 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
1361 $this->fail('Exception expected due to invalid group permissions.');
1362 } catch (moodle_exception $e) {
1363 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1364 }
1365
1366 // Create a group.
1367 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1368
1369 // Try to post in a group the user is not enrolled.
1370 try {
1371 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
1372 $this->fail('Exception expected due to invalid group permissions.');
1373 } catch (moodle_exception $e) {
1374 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1375 }
1376
1377 // Add the user to a group.
1378 groups_add_member($group->id, $user->id);
1379
1380 // Try to post in a group the user is not enrolled.
1381 try {
1382 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
1383 $this->fail('Exception expected due to invalid group.');
1384 } catch (moodle_exception $e) {
1385 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1386 }
1387
1388 // Nost add the discussion using a valid group.
1389 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
1390 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1391
1392 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1393 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1394
1395 $this->assertCount(1, $discussions['discussions']);
1396 $this->assertCount(0, $discussions['warnings']);
1397 $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
1398 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1399
1400 // Now add a discussions without indicating a group. The function should guess the correct group.
1401 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1402 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1403
1404 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1405 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1406
1407 $this->assertCount(2, $discussions['discussions']);
1408 $this->assertCount(0, $discussions['warnings']);
1409 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1410 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1411
1412 // Enrol the same user in other group.
1413 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1414 groups_add_member($group2->id, $user->id);
1415
1416 // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
1417 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1418 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1419
1420 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1421 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1422
1423 $this->assertCount(3, $discussions['discussions']);
1424 $this->assertCount(0, $discussions['warnings']);
1425 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1426 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1427 $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
1428
1429 }
1430
f5b4320e
P
1431 /*
1432 * Test set_lock_state.
1433 */
1434 public function test_set_lock_state() {
1435 global $DB;
1436 $this->resetAfterTest(true);
1437
1438 // Create courses to add the modules.
1439 $course = self::getDataGenerator()->create_course();
1440 $user = self::getDataGenerator()->create_user();
1441 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1442
1443 // First forum with tracking off.
1444 $record = new stdClass();
1445 $record->course = $course->id;
1446 $record->type = 'news';
1447 $forum = self::getDataGenerator()->create_module('forum', $record);
1448
1449 $record = new stdClass();
1450 $record->course = $course->id;
1451 $record->userid = $user->id;
1452 $record->forum = $forum->id;
1453 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1454
1455 // User who is a student.
1456 self::setUser($user);
1457 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual');
1458
1459 // Only a teacher should be able to lock a discussion.
bdb4a87d
P
1460 try {
1461 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
1462 $this->fail('Exception expected due to missing capability.');
1463 } catch (moodle_exception $e) {
1464 $this->assertEquals('errorcannotlock', $e->errorcode);
1465 }
f5b4320e
P
1466
1467 // Set the lock.
1468 self::setAdminUser();
1469 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
1470 $result = external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
bdb4a87d 1471 $this->assertTrue($result['locked']);
f5b4320e
P
1472 $this->assertNotEquals(0, $result['times']['locked']);
1473
1474 // Unset the lock.
1475 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, time());
1476 $result = external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
bdb4a87d 1477 $this->assertFalse($result['locked']);
f5b4320e
P
1478 $this->assertEquals('0', $result['times']['locked']);
1479 }
1480
04cd8ae3
JL
1481 /*
1482 * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
1483 */
1484 public function test_can_add_discussion() {
581e75bf 1485 global $DB;
04cd8ae3
JL
1486 $this->resetAfterTest(true);
1487
1488 // Create courses to add the modules.
1489 $course = self::getDataGenerator()->create_course();
1490
1491 $user = self::getDataGenerator()->create_user();
1492
1493 // First forum with tracking off.
1494 $record = new stdClass();
1495 $record->course = $course->id;
1496 $record->type = 'news';
1497 $forum = self::getDataGenerator()->create_module('forum', $record);
1498
1499 // User with no permissions to add in a news forum.
1500 self::setUser($user);
1501 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1502
1503 $result = mod_forum_external::can_add_discussion($forum->id);
1504 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1505 $this->assertFalse($result['status']);
581e75bf
JL
1506 $this->assertFalse($result['canpindiscussions']);
1507 $this->assertTrue($result['cancreateattachment']);
1508
1509 // Disable attachments.
1510 $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id));
1511 $result = mod_forum_external::can_add_discussion($forum->id);
1512 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1513 $this->assertFalse($result['status']);
1514 $this->assertFalse($result['canpindiscussions']);
1515 $this->assertFalse($result['cancreateattachment']);
1516 $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id)); // Enable attachments again.
04cd8ae3
JL
1517
1518 self::setAdminUser();
1519 $result = mod_forum_external::can_add_discussion($forum->id);
1520 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1521 $this->assertTrue($result['status']);
581e75bf
JL
1522 $this->assertTrue($result['canpindiscussions']);
1523 $this->assertTrue($result['cancreateattachment']);
cbf63d8e
SR
1524 }
1525
1526 /*
1527 * A basic test to make sure users cannot post to forum after the cutoff date.
1528 */
1529 public function test_can_add_discussion_after_cutoff() {
1530 $this->resetAfterTest(true);
1531
1532 // Create courses to add the modules.
1533 $course = self::getDataGenerator()->create_course();
1534
1535 $user = self::getDataGenerator()->create_user();
1536
1537 // Create a forum with cutoff date set to a past date.
1538 $forum = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 'cutoffdate' => time() - 1]);
04cd8ae3 1539
cbf63d8e
SR
1540 // User with no mod/forum:canoverridecutoff capability.
1541 self::setUser($user);
1542 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1543
1544 $result = mod_forum_external::can_add_discussion($forum->id);
1545 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1546 $this->assertFalse($result['status']);
1547
1548 self::setAdminUser();
1549 $result = mod_forum_external::can_add_discussion($forum->id);
1550 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
1551 $this->assertTrue($result['status']);
04cd8ae3
JL
1552 }
1553
b7ce46df
JL
1554 /**
1555 * Test get forum posts discussions including rating information.
1556 */
1557 public function test_mod_forum_get_forum_discussion_rating_information() {
1558 global $DB, $CFG;
1559 require_once($CFG->dirroot . '/rating/lib.php');
1560
1561 $this->resetAfterTest(true);
1562
1563 $user1 = self::getDataGenerator()->create_user();
1564 $user2 = self::getDataGenerator()->create_user();
1565 $user3 = self::getDataGenerator()->create_user();
1566 $teacher = self::getDataGenerator()->create_user();
1567
1568 // Create course to add the module.
1569 $course = self::getDataGenerator()->create_course();
1570
1571 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1572 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
1573 $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id, 'manual');
1574 $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id, 'manual');
1575 $this->getDataGenerator()->enrol_user($user3->id, $course->id, $studentrole->id, 'manual');
1576 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
1577
1578 // Create the forum.
1579 $record = new stdClass();
1580 $record->course = $course->id;
1581 // Set Aggregate type = Average of ratings.
1582 $record->assessed = RATING_AGGREGATE_AVERAGE;
1583 $record->scale = 100;
1584 $forum = self::getDataGenerator()->create_module('forum', $record);
1585 $context = context_module::instance($forum->cmid);
1586
1587 // Add discussion to the forum.
1588 $record = new stdClass();
1589 $record->course = $course->id;
1590 $record->userid = $user1->id;
1591 $record->forum = $forum->id;
1592 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1593
1594 // Retrieve the first post.
1595 $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
1596
1597 // Rate the discussion as user2.
1598 $rating1 = new stdClass();
1599 $rating1->contextid = $context->id;
1600 $rating1->component = 'mod_forum';
1601 $rating1->ratingarea = 'post';
1602 $rating1->itemid = $post->id;
1603 $rating1->rating = 50;
1604 $rating1->scaleid = 100;
1605 $rating1->userid = $user2->id;
1606 $rating1->timecreated = time();
1607 $rating1->timemodified = time();
1608 $rating1->id = $DB->insert_record('rating', $rating1);
1609
1610 // Rate the discussion as user3.
1611 $rating2 = new stdClass();
1612 $rating2->contextid = $context->id;
1613 $rating2->component = 'mod_forum';
1614 $rating2->ratingarea = 'post';
1615 $rating2->itemid = $post->id;
1616 $rating2->rating = 100;
1617 $rating2->scaleid = 100;
1618 $rating2->userid = $user3->id;
1619 $rating2->timecreated = time() + 1;
1620 $rating2->timemodified = time() + 1;
1621 $rating2->id = $DB->insert_record('rating', $rating2);
1622
1623 // Retrieve the rating for the post as student.
1624 $this->setUser($user1);
1625 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1626 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1627 $this->assertCount(1, $posts['ratinginfo']['ratings']);
1628 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
1629 $this->assertFalse($posts['ratinginfo']['canviewall']);
1630 $this->assertFalse($posts['ratinginfo']['ratings'][0]['canrate']);
1631 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
1632 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
1633
1634 // Retrieve the rating for the post as teacher.
1635 $this->setUser($teacher);
1636 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1637 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1638 $this->assertCount(1, $posts['ratinginfo']['ratings']);
1639 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
1640 $this->assertTrue($posts['ratinginfo']['canviewall']);
1641 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canrate']);
1642 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
1643 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
1644 }
4daa0d08
JL
1645
1646 /**
1647 * Test mod_forum_get_forum_access_information.
1648 */
1649 public function test_mod_forum_get_forum_access_information() {
1650 global $DB;
1651
1652 $this->resetAfterTest(true);
1653
1654 $student = self::getDataGenerator()->create_user();
1655 $course = self::getDataGenerator()->create_course();
1656 // Create the forum.
1657 $record = new stdClass();
1658 $record->course = $course->id;
1659 $forum = self::getDataGenerator()->create_module('forum', $record);
1660
1661 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1662 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
1663
1664 self::setUser($student);
1665 $result = mod_forum_external::get_forum_access_information($forum->id);
1666 $result = external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
1667
1668 // Check default values for capabilities.
1669 $enabledcaps = array('canviewdiscussion', 'canstartdiscussion', 'canreplypost', 'canviewrating', 'cancreateattachment',
1670 'canexportownpost', 'candeleteownpost', 'canallowforcesubscribe');
1671
1672 unset($result['warnings']);
1673 foreach ($result as $capname => $capvalue) {
1674 if (in_array($capname, $enabledcaps)) {
1675 $this->assertTrue($capvalue);
1676 } else {
1677 $this->assertFalse($capvalue);
1678 }
1679 }
1680 // Now, unassign some capabilities.
1681 unassign_capability('mod/forum:deleteownpost', $studentrole->id);
1682 unassign_capability('mod/forum:allowforcesubscribe', $studentrole->id);
1683 array_pop($enabledcaps);
1684 array_pop($enabledcaps);
1685 accesslib_clear_all_caches_for_unit_testing();
1686
1687 $result = mod_forum_external::get_forum_access_information($forum->id);
1688 $result = external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
1689 unset($result['warnings']);
1690 foreach ($result as $capname => $capvalue) {
1691 if (in_array($capname, $enabledcaps)) {
1692 $this->assertTrue($capvalue);
1693 } else {
1694 $this->assertFalse($capvalue);
1695 }
1696 }
1697 }
bc4c7337
AN
1698
1699 /**
1700 * Test add_discussion_post
1701 */
1702 public function test_add_discussion_post_private() {
1703 global $DB;
1704
1705 $this->resetAfterTest(true);
1706
1707 self::setAdminUser();
1708
1709 // Create course to add the module.
1710 $course = self::getDataGenerator()->create_course();
1711
1712 // Standard forum.
1713 $record = new stdClass();
1714 $record->course = $course->id;
1715 $forum = self::getDataGenerator()->create_module('forum', $record);
1716 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
1717 $forumcontext = context_module::instance($forum->cmid);
1718 $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
1719
1720 // Create an enrol users.
1721 $student1 = self::getDataGenerator()->create_user();
1722 $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
1723 $student2 = self::getDataGenerator()->create_user();
1724 $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
1725 $teacher1 = self::getDataGenerator()->create_user();
1726 $this->getDataGenerator()->enrol_user($teacher1->id, $course->id, 'editingteacher');
1727 $teacher2 = self::getDataGenerator()->create_user();
1728 $this->getDataGenerator()->enrol_user($teacher2->id, $course->id, 'editingteacher');
1729
1730 // Add a new discussion to the forum.
1731 self::setUser($student1);
1732 $record = new stdClass();
1733 $record->course = $course->id;
1734 $record->userid = $student1->id;
1735 $record->forum = $forum->id;
1736 $discussion = $generator->create_discussion($record);
1737
1738 // Have the teacher reply privately.
1739 self::setUser($teacher1);
1740 $post = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...', [
1741 [
1742 'name' => 'private',
1743 'value' => true,
1744 ],
1745 ]);
1746 $post = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $post);
1747 $privatereply = $DB->get_record('forum_posts', array('id' => $post['postid']));
1748 $this->assertEquals($student1->id, $privatereply->privatereplyto);
1749 // Bump the time of the private reply to ensure order.
1750 $privatereply->created++;
1751 $privatereply->modified = $privatereply->created;
1752 $DB->update_record('forum_posts', $privatereply);
1753
1754 // The teacher will receive their private reply.
1755 self::setUser($teacher1);
1756 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1757 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1758 $this->assertEquals(2, count($posts['posts']));
1759 $this->assertTrue($posts['posts'][0]['isprivatereply']);
1760
1761 // Another teacher on the course will also receive the private reply.
1762 self::setUser($teacher2);
1763 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1764 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1765 $this->assertEquals(2, count($posts['posts']));
1766 $this->assertTrue($posts['posts'][0]['isprivatereply']);
1767
1768 // The student will receive the private reply.
1769 self::setUser($student1);
1770 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1771 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1772 $this->assertEquals(2, count($posts['posts']));
1773 $this->assertTrue($posts['posts'][0]['isprivatereply']);
1774
1775 // Another student will not receive the private reply.
1776 self::setUser($student2);
1777 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1778 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1779 $this->assertEquals(1, count($posts['posts']));
1780 $this->assertFalse($posts['posts'][0]['isprivatereply']);
1781
1782 // A user cannot reply to a private reply.
1783 self::setUser($teacher2);
1784 $this->expectException('coding_exception');
1785 $post = mod_forum_external::add_discussion_post($privatereply->id, 'some subject', 'some text here...', [
1786 'options' => [
1787 'name' => 'private',
1788 'value' => false,
1789 ],
1790 ]);
1791 }
2b9fe87d 1792}