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