weekly release 3.8dev
[moodle.git] / mod / forum / tests / externallib_test.php
CommitLineData
2b9fe87d 1<?php
2b9fe87d
MN
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * The module forums external functions unit tests
19 *
20 * @package mod_forum
21 * @category external
22 * @copyright 2012 Mark Nelson <markn@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28global $CFG;
29
30require_once($CFG->dirroot . '/webservice/tests/helpers.php');
4669389d 31require_once($CFG->dirroot . '/mod/forum/lib.php');
2b9fe87d
MN
32
33class mod_forum_external_testcase extends externallib_advanced_testcase {
34
35 /**
36 * Tests set up
37 */
38 protected function setUp() {
39 global $CFG;
40
59075a43
AN
41 // We must clear the subscription caches. This has to be done both before each test, and after in case of other
42 // tests using these functions.
43 \mod_forum\subscriptions::reset_forum_cache();
44
2b9fe87d
MN
45 require_once($CFG->dirroot . '/mod/forum/externallib.php');
46 }
47
59075a43
AN
48 public function tearDown() {
49 // We must clear the subscription caches. This has to be done both before each test, and after in case of other
50 // tests using these functions.
51 \mod_forum\subscriptions::reset_forum_cache();
52 }
53
2b9fe87d
MN
54 /**
55 * Test get forums
56 */
57 public function test_mod_forum_get_forums_by_courses() {
58 global $USER, $CFG, $DB;
59
60 $this->resetAfterTest(true);
61
62 // Create a user.
4669389d 63 $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
2b9fe87d
MN
64
65 // Set to the user.
66 self::setUser($user);
67
68 // Create courses to add the modules.
69 $course1 = self::getDataGenerator()->create_course();
70 $course2 = self::getDataGenerator()->create_course();
71
72 // First forum.
73 $record = new stdClass();
74 $record->introformat = FORMAT_HTML;
75 $record->course = $course1->id;
4669389d 76 $record->trackingtype = FORUM_TRACKING_FORCED;
2b9fe87d
MN
77 $forum1 = self::getDataGenerator()->create_module('forum', $record);
78
79 // Second forum.
80 $record = new stdClass();
81 $record->introformat = FORMAT_HTML;
82 $record->course = $course2->id;
4669389d 83 $record->trackingtype = FORUM_TRACKING_OFF;
2b9fe87d 84 $forum2 = self::getDataGenerator()->create_module('forum', $record);
7ef49bd3 85 $forum2->introfiles = [];
2b9fe87d 86
7ea6ada3
JL
87 // Add discussions to the forums.
88 $record = new stdClass();
89 $record->course = $course1->id;
90 $record->userid = $user->id;
91 $record->forum = $forum1->id;
92 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
93 // Expect one discussion.
94 $forum1->numdiscussions = 1;
ea5b910b 95 $forum1->cancreatediscussions = true;
4669389d 96 $forum1->istracked = true;
2256bb74 97 $forum1->unreadpostscount = 0;
7ef49bd3 98 $forum1->introfiles = [];
7ea6ada3
JL
99
100 $record = new stdClass();
101 $record->course = $course2->id;
102 $record->userid = $user->id;
103 $record->forum = $forum2->id;
104 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
105 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
106 // Expect two discussions.
107 $forum2->numdiscussions = 2;
ea5b910b
JL
108 // Default limited role, no create discussion capability enabled.
109 $forum2->cancreatediscussions = false;
4669389d 110 $forum2->istracked = false;
7ea6ada3 111
2b9fe87d
MN
112 // Check the forum was correctly created.
113 $this->assertEquals(2, $DB->count_records_select('forum', 'id = :forum1 OR id = :forum2',
114 array('forum1' => $forum1->id, 'forum2' => $forum2->id)));
115
116 // Enrol the user in two courses.
909f27ac
JM
117 // DataGenerator->enrol_user automatically sets a role for the user with the permission mod/form:viewdiscussion.
118 $this->getDataGenerator()->enrol_user($user->id, $course1->id, null, 'manual');
119 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
2b9fe87d 120 $enrol = enrol_get_plugin('manual');
2b9fe87d
MN
121 $enrolinstances = enrol_get_instances($course2->id, true);
122 foreach ($enrolinstances as $courseenrolinstance) {
123 if ($courseenrolinstance->enrol == "manual") {
124 $instance2 = $courseenrolinstance;
125 break;
126 }
127 }
128 $enrol->enrol_user($instance2, $user->id);
129
2b9fe87d 130 // Assign capabilities to view forums for forum 2.
74b63eae 131 $cm2 = get_coursemodule_from_id('forum', $forum2->cmid, 0, false, MUST_EXIST);
2b9fe87d
MN
132 $context2 = context_module::instance($cm2->id);
133 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
134 $roleid2 = $this->assignUserCapability('mod/forum:viewdiscussion', $context2->id, $newrole);
135
136 // Create what we expect to be returned when querying the two courses.
c8f1d8a0
JL
137 unset($forum1->displaywordcount);
138 unset($forum2->displaywordcount);
139
2b9fe87d
MN
140 $expectedforums = array();
141 $expectedforums[$forum1->id] = (array) $forum1;
142 $expectedforums[$forum2->id] = (array) $forum2;
143
144 // Call the external function passing course ids.
145 $forums = mod_forum_external::get_forums_by_courses(array($course1->id, $course2->id));
c8f1d8a0
JL
146 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
147 $this->assertCount(2, $forums);
148 foreach ($forums as $forum) {
149 $this->assertEquals($expectedforums[$forum['id']], $forum);
150 }
2b9fe87d
MN
151
152 // Call the external function without passing course id.
153 $forums = mod_forum_external::get_forums_by_courses();
c8f1d8a0
JL
154 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
155 $this->assertCount(2, $forums);
156 foreach ($forums as $forum) {
157 $this->assertEquals($expectedforums[$forum['id']], $forum);
158 }
2b9fe87d
MN
159
160 // Unenrol user from second course and alter expected forums.
161 $enrol->unenrol_user($instance2, $user->id);
162 unset($expectedforums[$forum2->id]);
163
164 // Call the external function without passing course id.
165 $forums = mod_forum_external::get_forums_by_courses();
c8f1d8a0
JL
166 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
167 $this->assertCount(1, $forums);
168 $this->assertEquals($expectedforums[$forum1->id], $forums[0]);
ea5b910b
JL
169 $this->assertTrue($forums[0]['cancreatediscussions']);
170
171 // Change the type of the forum, the user shouldn't be able to add discussions.
172 $DB->set_field('forum', 'type', 'news', array('id' => $forum1->id));
173 $forums = mod_forum_external::get_forums_by_courses();
174 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
175 $this->assertFalse($forums[0]['cancreatediscussions']);
c8f1d8a0
JL
176
177 // Call for the second course we unenrolled the user from.
178 $forums = mod_forum_external::get_forums_by_courses(array($course2->id));
179 $forums = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $forums);
180 $this->assertCount(0, $forums);
a9a0cb69
MN
181 }
182
897ac0de
P
183 /**
184 * Test the toggle favourite state
185 */
186 public function test_mod_forum_toggle_favourite_state() {
187 global $USER, $CFG, $DB;
188
189 $this->resetAfterTest(true);
190
191 // Create a user.
192 $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
193
194 // Set to the user.
195 self::setUser($user);
196
197 // Create courses to add the modules.
198 $course1 = self::getDataGenerator()->create_course();
199 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
200
201 $record = new stdClass();
202 $record->introformat = FORMAT_HTML;
203 $record->course = $course1->id;
204 $record->trackingtype = FORUM_TRACKING_OFF;
205 $forum1 = self::getDataGenerator()->create_module('forum', $record);
206 $forum1->introfiles = [];
207
208 // Add discussions to the forums.
209 $record = new stdClass();
210 $record->course = $course1->id;
211 $record->userid = $user->id;
212 $record->forum = $forum1->id;
213 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
214
13cd05ac 215 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 1);
897ac0de
P
216 $response = external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
217 $this->assertTrue($response['userstate']['favourited']);
218
13cd05ac 219 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0);
897ac0de
P
220 $response = external_api::clean_returnvalue(mod_forum_external::toggle_favourite_state_returns(), $response);
221 $this->assertFalse($response['userstate']['favourited']);
d3cac88d
P
222
223 $this->setUser(0);
224 try {
225 $response = mod_forum_external::toggle_favourite_state($discussion1->id, 0);
226 } catch (moodle_exception $e) {
227 $this->assertEquals('requireloginerror', $e->errorcode);
228 }
897ac0de
P
229 }
230
309e8698
P
231 /**
232 * Test the toggle pin state
233 */
234 public function test_mod_forum_set_pin_state() {
235 $this->resetAfterTest(true);
236
237 // Create a user.
238 $user = self::getDataGenerator()->create_user(array('trackforums' => 1));
239
240 // Set to the user.
241 self::setUser($user);
242
243 // Create courses to add the modules.
244 $course1 = self::getDataGenerator()->create_course();
245 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
246
247 $record = new stdClass();
248 $record->introformat = FORMAT_HTML;
249 $record->course = $course1->id;
250 $record->trackingtype = FORUM_TRACKING_OFF;
251 $forum1 = self::getDataGenerator()->create_module('forum', $record);
252 $forum1->introfiles = [];
253
254 // Add discussions to the forums.
255 $record = new stdClass();
256 $record->course = $course1->id;
257 $record->userid = $user->id;
258 $record->forum = $forum1->id;
259 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
260
261 try {
13cd05ac 262 $response = mod_forum_external::set_pin_state($discussion1->id, 1);
309e8698
P
263 } catch (Exception $e) {
264 $this->assertEquals('cannotpindiscussions', $e->errorcode);
265 }
266
267 self::setAdminUser();
13cd05ac 268 $response = mod_forum_external::set_pin_state($discussion1->id, 1);
309e8698
P
269 $response = external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
270 $this->assertTrue($response['pinned']);
271
13cd05ac 272 $response = mod_forum_external::set_pin_state($discussion1->id, 0);
309e8698
P
273 $response = external_api::clean_returnvalue(mod_forum_external::set_pin_state_returns(), $response);
274 $this->assertFalse($response['pinned']);
275 }
276
e2ede426
JL
277 /**
278 * Test get forum posts
279 */
280 public function test_mod_forum_get_forum_discussion_posts() {
d85bedf7 281 global $CFG, $PAGE;
e2ede426
JL
282
283 $this->resetAfterTest(true);
284
285 // Set the CFG variable to allow track forums.
286 $CFG->forum_trackreadposts = true;
287
288 // Create a user who can track forums.
289 $record = new stdClass();
290 $record->trackforums = true;
291 $user1 = self::getDataGenerator()->create_user($record);
292 // Create a bunch of other users to post.
293 $user2 = self::getDataGenerator()->create_user();
294 $user3 = self::getDataGenerator()->create_user();
295
296 // Set the first created user to the test user.
297 self::setUser($user1);
298
299 // Create course to add the module.
300 $course1 = self::getDataGenerator()->create_course();
301
302 // Forum with tracking off.
303 $record = new stdClass();
304 $record->course = $course1->id;
305 $record->trackingtype = FORUM_TRACKING_OFF;
306 $forum1 = self::getDataGenerator()->create_module('forum', $record);
307 $forum1context = context_module::instance($forum1->cmid);
308
db3c9ff8
PFO
309 // Forum with tracking enabled.
310 $record = new stdClass();
311 $record->course = $course1->id;
312 $forum2 = self::getDataGenerator()->create_module('forum', $record);
2256bb74 313 $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
db3c9ff8
PFO
314 $forum2context = context_module::instance($forum2->cmid);
315
e2ede426
JL
316 // Add discussions to the forums.
317 $record = new stdClass();
318 $record->course = $course1->id;
319 $record->userid = $user1->id;
320 $record->forum = $forum1->id;
321 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
322
323 $record = new stdClass();
324 $record->course = $course1->id;
325 $record->userid = $user2->id;
326 $record->forum = $forum1->id;
327 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
328
db3c9ff8
PFO
329 $record = new stdClass();
330 $record->course = $course1->id;
331 $record->userid = $user2->id;
332 $record->forum = $forum2->id;
333 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
334
e2ede426
JL
335 // Add 2 replies to the discussion 1 from different users.
336 $record = new stdClass();
337 $record->discussion = $discussion1->id;
338 $record->parent = $discussion1->firstpost;
339 $record->userid = $user2->id;
340 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
c8743f62
JL
341 $filename = 'shouldbeanimage.jpg';
342 // Add a fake inline image to the post.
343 $filerecordinline = array(
344 'contextid' => $forum1context->id,
345 'component' => 'mod_forum',
346 'filearea' => 'post',
347 'itemid' => $discussion1reply1->id,
348 'filepath' => '/',
349 'filename' => $filename,
350 );
351 $fs = get_file_storage();
352 $timepost = time();
353 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
e2ede426
JL
354
355 $record->parent = $discussion1reply1->id;
356 $record->userid = $user3->id;
6c344ff2 357 $record->tags = array('Cats', 'Dogs');
e2ede426
JL
358 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
359
360 // Enrol the user in the course.
361 $enrol = enrol_get_plugin('manual');
362 // Following line enrol and assign default role id to the user.
363 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
364 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
365 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
81f810dc
JL
366
367 // Delete one user, to test that we still receive posts by this user.
368 delete_user($user3);
e2ede426
JL
369
370 // Create what we expect to be returned when querying the discussion.
371 $expectedposts = array(
372 'posts' => array(),
b7ce46df
JL
373 'ratinginfo' => array(
374 'contextid' => $forum1context->id,
375 'component' => 'mod_forum',
376 'ratingarea' => 'post',
377 'canviewall' => null,
378 'canviewany' => null,
379 'scales' => array(),
380 'ratings' => array(),
381 ),
e2ede426
JL
382 'warnings' => array(),
383 );
694bf0c7 384
d85bedf7 385 // User pictures are initially empty, we should get the links once the external function is called.
e2ede426
JL
386 $expectedposts['posts'][] = array(
387 'id' => $discussion1reply2->id,
388 'discussion' => $discussion1reply2->discussion,
389 'parent' => $discussion1reply2->parent,
48fb0250 390 'userid' => (int) $discussion1reply2->userid,
e2ede426
JL
391 'created' => $discussion1reply2->created,
392 'modified' => $discussion1reply2->modified,
393 'mailed' => $discussion1reply2->mailed,
394 'subject' => $discussion1reply2->subject,
395 'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
396 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
48fb0250 397 'messageformat' => 1, // This value is usually changed by external_format_text() function.
e2ede426
JL
398 'messagetrust' => $discussion1reply2->messagetrust,
399 'attachment' => $discussion1reply2->attachment,
400 'totalscore' => $discussion1reply2->totalscore,
401 'mailnow' => $discussion1reply2->mailnow,
402 'children' => array(),
403 'canreply' => true,
404 'postread' => false,
694bf0c7 405 'userfullname' => fullname($user3),
3e95e09b
AN
406 'userpictureurl' => '',
407 'deleted' => false,
bc4c7337 408 'isprivatereply' => false,
6c344ff2 409 'tags' => \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $discussion1reply2->id),
e2ede426 410 );
6c344ff2
JL
411 // Cast to expected.
412 $this->assertCount(2, $expectedposts['posts'][0]['tags']);
413 $expectedposts['posts'][0]['tags'][0]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][0]['isstandard'];
414 $expectedposts['posts'][0]['tags'][1]['isstandard'] = (bool) $expectedposts['posts'][0]['tags'][1]['isstandard'];
694bf0c7 415
e2ede426
JL
416 $expectedposts['posts'][] = array(
417 'id' => $discussion1reply1->id,
418 'discussion' => $discussion1reply1->discussion,
419 'parent' => $discussion1reply1->parent,
48fb0250 420 'userid' => (int) $discussion1reply1->userid,
e2ede426
JL
421 'created' => $discussion1reply1->created,
422 'modified' => $discussion1reply1->modified,
423 'mailed' => $discussion1reply1->mailed,
424 'subject' => $discussion1reply1->subject,
425 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
426 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
48fb0250 427 'messageformat' => 1, // This value is usually changed by external_format_text() function.
e2ede426
JL
428 'messagetrust' => $discussion1reply1->messagetrust,
429 'attachment' => $discussion1reply1->attachment,
c8743f62
JL
430 'messageinlinefiles' => array(
431 array(
432 'filename' => $filename,
433 'filepath' => '/',
434 'filesize' => '27',
435 'fileurl' => moodle_url::make_webservice_pluginfile_url($forum1context->id, 'mod_forum', 'post',
436 $discussion1reply1->id, '/', $filename),
437 'timemodified' => $timepost,
438 'mimetype' => 'image/jpeg',
1104a9fa 439 'isexternalfile' => false,
c8743f62
JL
440 )
441 ),
e2ede426
JL
442 'totalscore' => $discussion1reply1->totalscore,
443 'mailnow' => $discussion1reply1->mailnow,
d2c58b95 444 'children' => array($discussion1reply2->id),
e2ede426
JL
445 'canreply' => true,
446 'postread' => false,
694bf0c7 447 'userfullname' => fullname($user2),
3e95e09b
AN
448 'userpictureurl' => '',
449 'deleted' => false,
bc4c7337 450 'isprivatereply' => false,
6c344ff2 451 'tags' => array(),
e2ede426
JL
452 );
453
454 // Test a discussion with two additional posts (total 3 posts).
455 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
456 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
457 $this->assertEquals(3, count($posts['posts']));
458
d85bedf7
JL
459 // Generate here the pictures because we need to wait to the external function to init the theme.
460 $userpicture = new user_picture($user3);
461 $userpicture->size = 1; // Size f1.
462 $expectedposts['posts'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
463
464 $userpicture = new user_picture($user2);
465 $userpicture->size = 1; // Size f1.
466 $expectedposts['posts'][1]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
467
e2ede426
JL
468 // Unset the initial discussion post.
469 array_pop($posts['posts']);
470 $this->assertEquals($expectedposts, $posts);
471
2256bb74
JL
472 // Check we receive the unread count correctly on tracked forum.
473 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
474 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
475 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
476 foreach ($result as $f) {
477 if ($f['id'] == $forum2->id) {
478 $this->assertEquals(1, $f['unreadpostscount']);
479 }
480 }
481
e2ede426
JL
482 // Test discussion without additional posts. There should be only one post (the one created by the discussion).
483 $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id, 'modified', 'DESC');
484 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
485 $this->assertEquals(1, count($posts['posts']));
486
db3c9ff8
PFO
487 // Test discussion tracking on not tracked forum.
488 $result = mod_forum_external::view_forum_discussion($discussion1->id);
489 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
490 $this->assertTrue($result['status']);
491 $this->assertEmpty($result['warnings']);
492
493 // Test posts have not been marked as read.
494 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
495 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
496 foreach ($posts['posts'] as $post) {
497 $this->assertFalse($post['postread']);
498 }
499
500 // Test discussion tracking on tracked forum.
501 $result = mod_forum_external::view_forum_discussion($discussion3->id);
502 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
503 $this->assertTrue($result['status']);
504 $this->assertEmpty($result['warnings']);
505
506 // Test posts have been marked as read.
507 $posts = mod_forum_external::get_forum_discussion_posts($discussion3->id, 'modified', 'DESC');
508 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
509 foreach ($posts['posts'] as $post) {
510 $this->assertTrue($post['postread']);
511 }
2256bb74
JL
512
513 // Check we receive 0 unread posts.
514 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
515 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
516 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
517 foreach ($result as $f) {
518 if ($f['id'] == $forum2->id) {
519 $this->assertEquals(0, $f['unreadpostscount']);
520 }
521 }
e2ede426 522 }
c2586672 523
8245daba
P
524 /**
525 * Test get forum posts
526 *
527 * Tests is similar to the get_forum_discussion_posts only utilizing the new return structure and entities
528 */
529 public function test_mod_forum_get_discussion_posts() {
530 global $CFG, $PAGE;
531
532 $this->resetAfterTest(true);
533
534 // Set the CFG variable to allow track forums.
535 $CFG->forum_trackreadposts = true;
536
537 $urlfactory = mod_forum\local\container::get_url_factory();
538 $legacyfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
539 $entityfactory = mod_forum\local\container::get_entity_factory();
540
541 // Create a user who can track forums.
542 $record = new stdClass();
543 $record->trackforums = true;
544 $user1 = self::getDataGenerator()->create_user($record);
545 // Create a bunch of other users to post.
546 $user2 = self::getDataGenerator()->create_user();
547 $user2entity = $entityfactory->get_author_from_stdclass($user2);
548 $exporteduser2 = [
549 'id' => (int) $user2->id,
550 'fullname' => fullname($user2),
551 'groups' => [],
552 'urls' => [
553 'profile' => $urlfactory->get_author_profile_url($user2entity),
554 'profileimage' => $urlfactory->get_author_profile_image_url($user2entity),
555 ]
556 ];
557 $user2->fullname = $exporteduser2['fullname'];
558
559 $user3 = self::getDataGenerator()->create_user(['fullname' => "Mr Pants 1"]);
560 $user3entity = $entityfactory->get_author_from_stdclass($user3);
561 $exporteduser3 = [
562 'id' => (int) $user3->id,
563 'fullname' => fullname($user3),
564 'groups' => [],
565 'urls' => [
566 'profile' => $urlfactory->get_author_profile_url($user3entity),
567 'profileimage' => $urlfactory->get_author_profile_image_url($user3entity),
568 ]
569 ];
570 $user3->fullname = $exporteduser3['fullname'];
571 $forumgenerator = self::getDataGenerator()->get_plugin_generator('mod_forum');
572
573 // Set the first created user to the test user.
574 self::setUser($user1);
575
576 // Create course to add the module.
577 $course1 = self::getDataGenerator()->create_course();
578
579 // Forum with tracking off.
580 $record = new stdClass();
581 $record->course = $course1->id;
582 $record->trackingtype = FORUM_TRACKING_OFF;
583 $forum1 = self::getDataGenerator()->create_module('forum', $record);
584 $forum1context = context_module::instance($forum1->cmid);
585
586 // Forum with tracking enabled.
587 $record = new stdClass();
588 $record->course = $course1->id;
589 $forum2 = self::getDataGenerator()->create_module('forum', $record);
590 $forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
591 $forum2context = context_module::instance($forum2->cmid);
592
593 // Add discussions to the forums.
594 $record = new stdClass();
595 $record->course = $course1->id;
596 $record->userid = $user1->id;
597 $record->forum = $forum1->id;
598 $discussion1 = $forumgenerator->create_discussion($record);
599
600 $record = new stdClass();
601 $record->course = $course1->id;
602 $record->userid = $user2->id;
603 $record->forum = $forum1->id;
604 $discussion2 = $forumgenerator->create_discussion($record);
605
606 $record = new stdClass();
607 $record->course = $course1->id;
608 $record->userid = $user2->id;
609 $record->forum = $forum2->id;
610 $discussion3 = $forumgenerator->create_discussion($record);
611
612 // Add 2 replies to the discussion 1 from different users.
613 $record = new stdClass();
614 $record->discussion = $discussion1->id;
615 $record->parent = $discussion1->firstpost;
616 $record->userid = $user2->id;
617 $discussion1reply1 = $forumgenerator->create_post($record);
618 $filename = 'shouldbeanimage.jpg';
619 // Add a fake inline image to the post.
620 $filerecordinline = array(
621 'contextid' => $forum1context->id,
622 'component' => 'mod_forum',
623 'filearea' => 'post',
624 'itemid' => $discussion1reply1->id,
625 'filepath' => '/',
626 'filename' => $filename,
627 );
628 $fs = get_file_storage();
629 $timepost = time();
630 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
631
632 $record->parent = $discussion1reply1->id;
633 $record->userid = $user3->id;
634 $discussion1reply2 = $forumgenerator->create_post($record);
635
636 // Enrol the user in the course.
637 $enrol = enrol_get_plugin('manual');
638 // Following line enrol and assign default role id to the user.
639 // So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
640 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
641 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
642
643 // Delete one user, to test that we still receive posts by this user.
644 delete_user($user3);
645
646 // Create what we expect to be returned when querying the discussion.
647 $expectedposts = array(
648 'posts' => array(),
649 'ratinginfo' => array(
650 'contextid' => $forum1context->id,
651 'component' => 'mod_forum',
652 'ratingarea' => 'post',
653 'canviewall' => null,
654 'canviewany' => null,
655 'scales' => array(),
656 'ratings' => array(),
657 ),
658 'warnings' => array(),
659 );
660
661 // User pictures are initially empty, we should get the links once the external function is called.
662 $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion);
663 $isolatedurl->params(['parent' => $discussion1reply2->id]);
664 $expectedposts['posts'][] = array(
665 'id' => $discussion1reply2->id,
666 'discussionid' => $discussion1reply2->discussion,
667 'parentid' => $discussion1reply2->parent,
668 'hasparent' => true,
669 'timecreated' => $discussion1reply2->created,
670 'subject' => $discussion1reply2->subject,
9ba09a12 671 'replysubject' => get_string('re', 'mod_forum') . " {$discussion1reply2->subject}",
8245daba
P
672 'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
673 $forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
674 'messageformat' => 1, // This value is usually changed by external_format_text() function.
675 'unread' => null,
676 'isdeleted' => false,
677 'isprivatereply' => false,
678 'haswordcount' => false,
679 'wordcount' => null,
680 'author'=> $exporteduser3,
681 'attachments' => [],
682 'tags' => [],
683 'html' => [
684 'rating' => null,
685 'taglist' => null,
686 'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser3, $discussion1reply2->created)
687 ],
688 'capabilities' => [
689 'view' => 1,
690 'edit' => 0,
691 'delete' => 0,
692 'split' => 0,
693 'reply' => 1,
694 'export' => 0,
cee572aa 695 'controlreadstatus' => 0,
92ba55ee
P
696 'canreplyprivately' => 0,
697 'selfenrol' => 0
8245daba
P
698 ],
699 'urls' => [
700 'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->id),
701 'viewisolated' => $isolatedurl->out(false),
702 'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->parent),
703 'edit' => null,
704 'delete' =>null,
705 'split' => null,
706 'reply' => (new moodle_url('/mod/forum/post.php#mformforum', [
707 'reply' => $discussion1reply2->id
708 ]))->out(false),
709 'export' => null,
710 'markasread' => null,
711 'markasunread' => null,
712 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion),
713 ],
714 );
715
716
717 $isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion);
718 $isolatedurl->params(['parent' => $discussion1reply1->id]);
719 $expectedposts['posts'][] = array(
720 'id' => $discussion1reply1->id,
721 'discussionid' => $discussion1reply1->discussion,
722 'parentid' => $discussion1reply1->parent,
723 'hasparent' => true,
724 'timecreated' => $discussion1reply1->created,
725 'subject' => $discussion1reply1->subject,
9ba09a12 726 'replysubject' => get_string('re', 'mod_forum') . " {$discussion1reply1->subject}",
8245daba
P
727 'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
728 $forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
729 'messageformat' => 1, // This value is usually changed by external_format_text() function.
730 'unread' => null,
731 'isdeleted' => false,
732 'isprivatereply' => false,
733 'haswordcount' => false,
734 'wordcount' => null,
735 'author'=> $exporteduser2,
736 'attachments' => [],
737 'tags' => [],
738 'html' => [
739 'rating' => null,
740 'taglist' => null,
741 'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser2, $discussion1reply1->created)
742 ],
743 'capabilities' => [
744 'view' => 1,
745 'edit' => 0,
746 'delete' => 0,
747 'split' => 0,
748 'reply' => 1,
749 'export' => 0,
cee572aa 750 'controlreadstatus' => 0,
92ba55ee
P
751 'canreplyprivately' => 0,
752 'selfenrol' => 0
8245daba
P
753 ],
754 'urls' => [
755 'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->id),
756 'viewisolated' => $isolatedurl->out(false),
757 'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->parent),
758 'edit' => null,
759 'delete' =>null,
760 'split' => null,
761 'reply' => (new moodle_url('/mod/forum/post.php#mformforum', [
762 'reply' => $discussion1reply1->id
763 ]))->out(false),
764 'export' => null,
765 'markasread' => null,
766 'markasunread' => null,
767 'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion),
768 ],
769 );
770
771 // Test a discussion with two additional posts (total 3 posts).
772 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
773 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
774 $this->assertEquals(3, count($posts['posts']));
775
776 // Unset the initial discussion post.
777 array_pop($posts['posts']);
778 $this->assertEquals($expectedposts, $posts);
779
780 // Check we receive the unread count correctly on tracked forum.
781 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
782 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
783 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
784 foreach ($result as $f) {
785 if ($f['id'] == $forum2->id) {
786 $this->assertEquals(1, $f['unreadpostscount']);
787 }
788 }
789
790 // Test discussion without additional posts. There should be only one post (the one created by the discussion).
791 $posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'DESC');
792 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
793 $this->assertEquals(1, count($posts['posts']));
794
795 // Test discussion tracking on not tracked forum.
796 $result = mod_forum_external::view_forum_discussion($discussion1->id);
797 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
798 $this->assertTrue($result['status']);
799 $this->assertEmpty($result['warnings']);
800
801 // Test posts have not been marked as read.
802 $posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
803 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
804 foreach ($posts['posts'] as $post) {
805 $this->assertNull($post['unread']);
806 }
807
808 // Test discussion tracking on tracked forum.
809 $result = mod_forum_external::view_forum_discussion($discussion3->id);
810 $result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
811 $this->assertTrue($result['status']);
812 $this->assertEmpty($result['warnings']);
813
814 // Test posts have been marked as read.
815 $posts = mod_forum_external::get_discussion_posts($discussion3->id, 'modified', 'DESC');
816 $posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
817 foreach ($posts['posts'] as $post) {
818 $this->assertFalse($post['unread']);
819 }
820
821 // Check we receive 0 unread posts.
822 forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
823 $result = mod_forum_external::get_forums_by_courses(array($course1->id));
824 $result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
825 foreach ($result as $f) {
826 if ($f['id'] == $forum2->id) {
827 $this->assertEquals(0, $f['unreadpostscount']);
828 }
829 }
830 }
831
3e95e09b
AN
832 /**
833 * Test get forum posts
834 */
835 public function test_mod_forum_get_forum_discussion_posts_deleted() {
836 global $CFG, $PAGE;
837
838 $this->resetAfterTest(true);
839 $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
840
841 // Create a course and enrol some users in it.
842 $course1 = self::getDataGenerator()->create_course();
843
844 // Create users.
845 $user1 = self::getDataGenerator()->create_user();
846 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
847 $user2 = self::getDataGenerator()->create_user();
848 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
849
850 // Set the first created user to the test user.
851 self::setUser($user1);
852
853 // Create test data.
854 $forum1 = self::getDataGenerator()->create_module('forum', (object) [
855 'course' => $course1->id,
856 ]);
857 $forum1context = context_module::instance($forum1->cmid);
858
859 // Add discussions to the forum.
860 $discussion = $generator->create_discussion((object) [
861 'course' => $course1->id,
862 'userid' => $user1->id,
863 'forum' => $forum1->id,
864 ]);
865
866 $discussion2 = $generator->create_discussion((object) [
867 'course' => $course1->id,
868 'userid' => $user2->id,
869 'forum' => $forum1->id,
870 ]);
871
872 // Add replies to the discussion.
873 $discussionreply1 = $generator->create_post((object) [
874 'discussion' => $discussion->id,
875 'parent' => $discussion->firstpost,
876 'userid' => $user2->id,
877 ]);
878 $discussionreply2 = $generator->create_post((object) [
879 'discussion' => $discussion->id,
880 'parent' => $discussionreply1->id,
881 'userid' => $user2->id,
882 'subject' => '',
883 'message' => '',
884 'messageformat' => FORMAT_PLAIN,
885 'deleted' => 1,
886 ]);
887 $discussionreply3 = $generator->create_post((object) [
888 'discussion' => $discussion->id,
889 'parent' => $discussion->firstpost,
890 'userid' => $user2->id,
891 ]);
892
893 // Test where some posts have been marked as deleted.
894 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id, 'modified', 'DESC');
895 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
896 $deletedsubject = get_string('privacy:request:delete:post:subject', 'mod_forum');
897 $deletedmessage = get_string('privacy:request:delete:post:message', 'mod_forum');
898
899 foreach ($posts['posts'] as $post) {
900 if ($post['id'] == $discussionreply2->id) {
901 $this->assertTrue($post['deleted']);
902 $this->assertEquals($deletedsubject, $post['subject']);
903 $this->assertEquals($deletedmessage, $post['message']);
904 } else {
905 $this->assertFalse($post['deleted']);
906 $this->assertNotEquals($deletedsubject, $post['subject']);
907 $this->assertNotEquals($deletedmessage, $post['message']);
908 }
909 }
910 }
911
b1aa7dfa
JL
912 /**
913 * Test get forum posts (qanda forum)
914 */
915 public function test_mod_forum_get_forum_discussion_posts_qanda() {
916 global $CFG, $DB;
917
918 $this->resetAfterTest(true);
919
920 $record = new stdClass();
921 $user1 = self::getDataGenerator()->create_user($record);
922 $user2 = self::getDataGenerator()->create_user();
923
924 // Set the first created user to the test user.
925 self::setUser($user1);
926
927 // Create course to add the module.
928 $course1 = self::getDataGenerator()->create_course();
929 $this->getDataGenerator()->enrol_user($user1->id, $course1->id);
930 $this->getDataGenerator()->enrol_user($user2->id, $course1->id);
931
932 // Forum with tracking off.
933 $record = new stdClass();
934 $record->course = $course1->id;
935 $record->type = 'qanda';
936 $forum1 = self::getDataGenerator()->create_module('forum', $record);
937 $forum1context = context_module::instance($forum1->cmid);
938
939 // Add discussions to the forums.
940 $record = new stdClass();
941 $record->course = $course1->id;
942 $record->userid = $user2->id;
943 $record->forum = $forum1->id;
944 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
945
946 // Add 1 reply (not the actual user).
947 $record = new stdClass();
948 $record->discussion = $discussion1->id;
949 $record->parent = $discussion1->firstpost;
950 $record->userid = $user2->id;
951 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
952
953 // We still see only the original post.
954 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
955 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
956 $this->assertEquals(1, count($posts['posts']));
957
958 // Add a new reply, the user is going to be able to see only the original post and their new post.
959 $record = new stdClass();
960 $record->discussion = $discussion1->id;
961 $record->parent = $discussion1->firstpost;
962 $record->userid = $user1->id;
963 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
964
965 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
966 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
967 $this->assertEquals(2, count($posts['posts']));
968
969 // Now, we can fake the time of the user post, so he can se the rest of the discussion posts.
970 $discussion1reply2->created -= $CFG->maxeditingtime * 2;
971 $DB->update_record('forum_posts', $discussion1reply2);
972
973 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id, 'modified', 'DESC');
974 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
975 $this->assertEquals(3, count($posts['posts']));
b1aa7dfa
JL
976 }
977
c2586672
JL
978 /**
979 * Test get forum discussions paginated
980 */
981 public function test_mod_forum_get_forum_discussions_paginated() {
d85bedf7 982 global $USER, $CFG, $DB, $PAGE;
c2586672
JL
983
984 $this->resetAfterTest(true);
985
986 // Set the CFG variable to allow track forums.
987 $CFG->forum_trackreadposts = true;
988
989 // Create a user who can track forums.
990 $record = new stdClass();
991 $record->trackforums = true;
992 $user1 = self::getDataGenerator()->create_user($record);
993 // Create a bunch of other users to post.
994 $user2 = self::getDataGenerator()->create_user();
995 $user3 = self::getDataGenerator()->create_user();
996 $user4 = self::getDataGenerator()->create_user();
997
998 // Set the first created user to the test user.
999 self::setUser($user1);
1000
1001 // Create courses to add the modules.
1002 $course1 = self::getDataGenerator()->create_course();
1003
1004 // First forum with tracking off.
1005 $record = new stdClass();
1006 $record->course = $course1->id;
1007 $record->trackingtype = FORUM_TRACKING_OFF;
1008 $forum1 = self::getDataGenerator()->create_module('forum', $record);
1009
1010 // Add discussions to the forums.
1011 $record = new stdClass();
1012 $record->course = $course1->id;
1013 $record->userid = $user1->id;
1014 $record->forum = $forum1->id;
1015 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1016
1017 // Add three replies to the discussion 1 from different users.
1018 $record = new stdClass();
1019 $record->discussion = $discussion1->id;
1020 $record->parent = $discussion1->firstpost;
1021 $record->userid = $user2->id;
1022 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1023
1024 $record->parent = $discussion1reply1->id;
1025 $record->userid = $user3->id;
1026 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1027
1028 $record->userid = $user4->id;
1029 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1030
1031 // Enrol the user in the first course.
1032 $enrol = enrol_get_plugin('manual');
1033
1034 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
1035 $enrolinstances = enrol_get_instances($course1->id, true);
1036 foreach ($enrolinstances as $courseenrolinstance) {
1037 if ($courseenrolinstance->enrol == "manual") {
1038 $instance1 = $courseenrolinstance;
1039 break;
1040 }
1041 }
1042 $enrol->enrol_user($instance1, $user1->id);
1043
81f810dc
JL
1044 // Delete one user.
1045 delete_user($user4);
1046
c2586672
JL
1047 // Assign capabilities to view discussions for forum 1.
1048 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
1049 $context = context_module::instance($cm->id);
1050 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
1051 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1052
1053 // Create what we expect to be returned when querying the forums.
1054
1055 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
c2586672 1056
d85bedf7 1057 // User pictures are initially empty, we should get the links once the external function is called.
c2586672
JL
1058 $expecteddiscussions = array(
1059 'id' => $discussion1->firstpost,
1060 'name' => $discussion1->name,
28c2f2b2 1061 'groupid' => (int) $discussion1->groupid,
c2586672 1062 'timemodified' => $discussion1reply3->created,
28c2f2b2
MG
1063 'usermodified' => (int) $discussion1reply3->userid,
1064 'timestart' => (int) $discussion1->timestart,
1065 'timeend' => (int) $discussion1->timeend,
c2586672
JL
1066 'discussion' => $discussion1->id,
1067 'parent' => 0,
28c2f2b2
MG
1068 'userid' => (int) $discussion1->userid,
1069 'created' => (int) $post1->created,
1070 'modified' => (int) $post1->modified,
1071 'mailed' => (int) $post1->mailed,
c2586672
JL
1072 'subject' => $post1->subject,
1073 'message' => $post1->message,
28c2f2b2
MG
1074 'messageformat' => (int) $post1->messageformat,
1075 'messagetrust' => (int) $post1->messagetrust,
c2586672 1076 'attachment' => $post1->attachment,
28c2f2b2
MG
1077 'totalscore' => (int) $post1->totalscore,
1078 'mailnow' => (int) $post1->mailnow,
c2586672
JL
1079 'userfullname' => fullname($user1),
1080 'usermodifiedfullname' => fullname($user4),
d85bedf7
JL
1081 'userpictureurl' => '',
1082 'usermodifiedpictureurl' => '',
c2586672 1083 'numreplies' => 3,
5f219cf1 1084 'numunread' => 0,
28c2f2b2 1085 'pinned' => (bool) FORUM_DISCUSSION_UNPINNED,
0f3bbfd4
AN
1086 'locked' => false,
1087 'canreply' => false,
28c2f2b2 1088 'canlock' => false
c2586672
JL
1089 );
1090
1091 // Call the external function passing forum id.
1092 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
1093 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1094 $expectedreturn = array(
1095 'discussions' => array($expecteddiscussions),
1096 'warnings' => array()
1097 );
d85bedf7
JL
1098
1099 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
1100 $userpicture = new user_picture($user1);
1101 $userpicture->size = 1; // Size f1.
1102 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1103
1104 $userpicture = new user_picture($user4);
1105 $userpicture->size = 1; // Size f1.
1106 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1107
c2586672
JL
1108 $this->assertEquals($expectedreturn, $discussions);
1109
1110 // Call without required view discussion capability.
1111 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1112 try {
1113 mod_forum_external::get_forum_discussions_paginated($forum1->id);
1114 $this->fail('Exception expected due to missing capability.');
1115 } catch (moodle_exception $e) {
1116 $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
1117 }
1118
1119 // Unenrol user from second course.
1120 $enrol->unenrol_user($instance1, $user1->id);
1121
1122 // Call for the second course we unenrolled the user from, make sure exception thrown.
1123 try {
1124 mod_forum_external::get_forum_discussions_paginated($forum1->id);
1125 $this->fail('Exception expected due to being unenrolled from the course.');
1126 } catch (moodle_exception $e) {
1127 $this->assertEquals('requireloginerror', $e->errorcode);
1128 }
565cccfa
P
1129
1130 $this->setAdminUser();
1131 $discussions = mod_forum_external::get_forum_discussions_paginated($forum1->id);
1132 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1133 $this->assertTrue($discussions['discussions'][0]['canlock']);
c2586672 1134 }
039c81f0
JL
1135
1136 /**
1137 * Test get forum discussions paginated (qanda forums)
1138 */
1139 public function test_mod_forum_get_forum_discussions_paginated_qanda() {
1140
1141 $this->resetAfterTest(true);
1142
1143 // Create courses to add the modules.
1144 $course = self::getDataGenerator()->create_course();
1145
1146 $user1 = self::getDataGenerator()->create_user();
1147 $user2 = self::getDataGenerator()->create_user();
1148
1149 // First forum with tracking off.
1150 $record = new stdClass();
1151 $record->course = $course->id;
1152 $record->type = 'qanda';
1153 $forum = self::getDataGenerator()->create_module('forum', $record);
1154
1155 // Add discussions to the forums.
1156 $discussionrecord = new stdClass();
1157 $discussionrecord->course = $course->id;
1158 $discussionrecord->userid = $user2->id;
1159 $discussionrecord->forum = $forum->id;
1160 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($discussionrecord);
1161
1162 self::setAdminUser();
1163 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1164 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1165
1166 $this->assertCount(1, $discussions['discussions']);
1167 $this->assertCount(0, $discussions['warnings']);
1168
1169 self::setUser($user1);
1170 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1171
1172 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1173 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1174
1175 $this->assertCount(1, $discussions['discussions']);
1176 $this->assertCount(0, $discussions['warnings']);
1177
1178 }
50a20317 1179
3f9ed16e
MG
1180 /**
1181 * Test get forum discussions
1182 */
1183 public function test_mod_forum_get_forum_discussions() {
1184 global $CFG, $DB, $PAGE;
1185
1186 $this->resetAfterTest(true);
1187
1188 // Set the CFG variable to allow track forums.
1189 $CFG->forum_trackreadposts = true;
1190
1191 // Create a user who can track forums.
1192 $record = new stdClass();
1193 $record->trackforums = true;
1194 $user1 = self::getDataGenerator()->create_user($record);
1195 // Create a bunch of other users to post.
1196 $user2 = self::getDataGenerator()->create_user();
1197 $user3 = self::getDataGenerator()->create_user();
1198 $user4 = self::getDataGenerator()->create_user();
1199
1200 // Set the first created user to the test user.
1201 self::setUser($user1);
1202
1203 // Create courses to add the modules.
1204 $course1 = self::getDataGenerator()->create_course();
1205
1206 // First forum with tracking off.
1207 $record = new stdClass();
1208 $record->course = $course1->id;
1209 $record->trackingtype = FORUM_TRACKING_OFF;
1210 $forum1 = self::getDataGenerator()->create_module('forum', $record);
1211
1212 // Add discussions to the forums.
1213 $record = new stdClass();
1214 $record->course = $course1->id;
1215 $record->userid = $user1->id;
1216 $record->forum = $forum1->id;
1217 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1218
1219 // Add three replies to the discussion 1 from different users.
1220 $record = new stdClass();
1221 $record->discussion = $discussion1->id;
1222 $record->parent = $discussion1->firstpost;
1223 $record->userid = $user2->id;
1224 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1225
1226 $record->parent = $discussion1reply1->id;
1227 $record->userid = $user3->id;
1228 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1229
1230 $record->userid = $user4->id;
1231 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1232
1233 // Enrol the user in the first course.
1234 $enrol = enrol_get_plugin('manual');
1235
1236 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
1237 $enrolinstances = enrol_get_instances($course1->id, true);
1238 foreach ($enrolinstances as $courseenrolinstance) {
1239 if ($courseenrolinstance->enrol == "manual") {
1240 $instance1 = $courseenrolinstance;
1241 break;
1242 }
1243 }
1244 $enrol->enrol_user($instance1, $user1->id);
1245
1246 // Delete one user.
1247 delete_user($user4);
1248
1249 // Assign capabilities to view discussions for forum 1.
1250 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
1251 $context = context_module::instance($cm->id);
1252 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
1253 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1254
1255 // Create what we expect to be returned when querying the forums.
1256
1257 $post1 = $DB->get_record('forum_posts', array('id' => $discussion1->firstpost), '*', MUST_EXIST);
1258
1259 // User pictures are initially empty, we should get the links once the external function is called.
1260 $expecteddiscussions = array(
1261 'id' => $discussion1->firstpost,
1262 'name' => $discussion1->name,
1263 'groupid' => (int) $discussion1->groupid,
1264 'timemodified' => (int) $discussion1reply3->created,
1265 'usermodified' => (int) $discussion1reply3->userid,
1266 'timestart' => (int) $discussion1->timestart,
1267 'timeend' => (int) $discussion1->timeend,
1268 'discussion' => (int) $discussion1->id,
1269 'parent' => 0,
1270 'userid' => (int) $discussion1->userid,
1271 'created' => (int) $post1->created,
1272 'modified' => (int) $post1->modified,
1273 'mailed' => (int) $post1->mailed,
1274 'subject' => $post1->subject,
1275 'message' => $post1->message,
1276 'messageformat' => (int) $post1->messageformat,
1277 'messagetrust' => (int) $post1->messagetrust,
1278 'attachment' => $post1->attachment,
1279 'totalscore' => (int) $post1->totalscore,
1280 'mailnow' => (int) $post1->mailnow,
1281 'userfullname' => fullname($user1),
1282 'usermodifiedfullname' => fullname($user4),
1283 'userpictureurl' => '',
1284 'usermodifiedpictureurl' => '',
1285 'numreplies' => 3,
1286 'numunread' => 0,
1287 'pinned' => (bool) FORUM_DISCUSSION_UNPINNED,
1288 'locked' => false,
1289 'canreply' => false,
1290 'canlock' => false,
28c2f2b2
MG
1291 'starred' => false,
1292 'canfavourite' => true
3f9ed16e
MG
1293 );
1294
1295 // Call the external function passing forum id.
1296 $discussions = mod_forum_external::get_forum_discussions($forum1->id);
1297 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1298 $expectedreturn = array(
1299 'discussions' => array($expecteddiscussions),
1300 'warnings' => array()
1301 );
1302
1303 // Wait the theme to be loaded (the external_api call does that) to generate the user profiles.
1304 $userpicture = new user_picture($user1);
1305 $userpicture->size = 2; // Size f2.
1306 $expectedreturn['discussions'][0]['userpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1307
1308 $userpicture = new user_picture($user4);
1309 $userpicture->size = 2; // Size f2.
1310 $expectedreturn['discussions'][0]['usermodifiedpictureurl'] = $userpicture->get_url($PAGE)->out(false);
1311
1312 $this->assertEquals($expectedreturn, $discussions);
1313
28c2f2b2
MG
1314 // Test the starring functionality return.
1315 $t = mod_forum_external::toggle_favourite_state($discussion1->id, 1);
1316 $expectedreturn['discussions'][0]['starred'] = true;
1317 $discussions = mod_forum_external::get_forum_discussions($forum1->id);
1318 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1319 $this->assertEquals($expectedreturn, $discussions);
1320
3f9ed16e
MG
1321 // Call without required view discussion capability.
1322 $this->unassignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1323 try {
1324 mod_forum_external::get_forum_discussions($forum1->id);
1325 $this->fail('Exception expected due to missing capability.');
1326 } catch (moodle_exception $e) {
1327 $this->assertEquals('noviewdiscussionspermission', $e->errorcode);
1328 }
1329
1330 // Unenrol user from second course.
1331 $enrol->unenrol_user($instance1, $user1->id);
1332
1333 // Call for the second course we unenrolled the user from, make sure exception thrown.
1334 try {
1335 mod_forum_external::get_forum_discussions($forum1->id);
1336 $this->fail('Exception expected due to being unenrolled from the course.');
1337 } catch (moodle_exception $e) {
1338 $this->assertEquals('requireloginerror', $e->errorcode);
1339 }
1340
1341 $this->setAdminUser();
1342 $discussions = mod_forum_external::get_forum_discussions($forum1->id);
1343 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1344 $this->assertTrue($discussions['discussions'][0]['canlock']);
1345 }
1346
1347 /**
1348 * Test the sorting in get forum discussions
1349 */
1350 public function test_mod_forum_get_forum_discussions_sorting() {
1351 global $CFG, $DB, $PAGE;
1352
1353 $this->resetAfterTest(true);
1354
1355 // Set the CFG variable to allow track forums.
1356 $CFG->forum_trackreadposts = true;
1357
1358 // Create a user who can track forums.
1359 $record = new stdClass();
1360 $record->trackforums = true;
1361 $user1 = self::getDataGenerator()->create_user($record);
1362 // Create a bunch of other users to post.
1363 $user2 = self::getDataGenerator()->create_user();
1364 $user3 = self::getDataGenerator()->create_user();
1365 $user4 = self::getDataGenerator()->create_user();
1366
1367 // Set the first created user to the test user.
1368 self::setUser($user1);
1369
1370 // Create courses to add the modules.
1371 $course1 = self::getDataGenerator()->create_course();
1372
1373 // Enrol the user in the first course.
1374 $enrol = enrol_get_plugin('manual');
1375
1376 // We don't use the dataGenerator as we need to get the $instance2 to unenrol later.
1377 $enrolinstances = enrol_get_instances($course1->id, true);
1378 foreach ($enrolinstances as $courseenrolinstance) {
1379 if ($courseenrolinstance->enrol == "manual") {
1380 $instance1 = $courseenrolinstance;
1381 break;
1382 }
1383 }
1384 $enrol->enrol_user($instance1, $user1->id);
1385
1386 // First forum with tracking off.
1387 $record = new stdClass();
1388 $record->course = $course1->id;
1389 $record->trackingtype = FORUM_TRACKING_OFF;
1390 $forum1 = self::getDataGenerator()->create_module('forum', $record);
1391
1392 // Assign capabilities to view discussions for forum 1.
1393 $cm = get_coursemodule_from_id('forum', $forum1->cmid, 0, false, MUST_EXIST);
1394 $context = context_module::instance($cm->id);
1395 $newrole = create_role('Role 2', 'role2', 'Role 2 description');
1396 $this->assignUserCapability('mod/forum:viewdiscussion', $context->id, $newrole);
1397
1398 // Add discussions to the forums.
1399 $record = new stdClass();
1400 $record->course = $course1->id;
1401 $record->userid = $user1->id;
1402 $record->forum = $forum1->id;
1403 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1404 sleep(1);
1405
1406 // Add three replies to the discussion 1 from different users.
1407 $record = new stdClass();
1408 $record->discussion = $discussion1->id;
1409 $record->parent = $discussion1->firstpost;
1410 $record->userid = $user2->id;
1411 $discussion1reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1412 sleep(1);
1413
1414 $record->parent = $discussion1reply1->id;
1415 $record->userid = $user3->id;
1416 $discussion1reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1417 sleep(1);
1418
1419 $record->userid = $user4->id;
1420 $discussion1reply3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record);
1421 sleep(1);
1422
1423 // Create discussion2.
1424 $record2 = new stdClass();
1425 $record2->course = $course1->id;
1426 $record2->userid = $user1->id;
1427 $record2->forum = $forum1->id;
1428 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record2);
1429 sleep(1);
1430
1431 // Add one reply to the discussion 2.
1432 $record2 = new stdClass();
1433 $record2->discussion = $discussion2->id;
1434 $record2->parent = $discussion2->firstpost;
1435 $record2->userid = $user2->id;
1436 $discussion2reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record2);
1437 sleep(1);
1438
1439 // Create discussion 3.
1440 $record3 = new stdClass();
1441 $record3->course = $course1->id;
1442 $record3->userid = $user1->id;
1443 $record3->forum = $forum1->id;
1444 $discussion3 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record3);
1445 sleep(1);
1446
1447 // Add two replies to the discussion 3.
1448 $record3 = new stdClass();
1449 $record3->discussion = $discussion3->id;
1450 $record3->parent = $discussion3->firstpost;
1451 $record3->userid = $user2->id;
1452 $discussion3reply1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record3);
1453 sleep(1);
1454
1455 $record3->parent = $discussion3reply1->id;
1456 $record3->userid = $user3->id;
1457 $discussion3reply2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_post($record3);
1458
1459 // Call the external function passing forum id.
1460 $discussions = mod_forum_external::get_forum_discussions($forum1->id);
1461 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1462 // Discussions should be ordered by last post date in descending order by default.
1463 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion3->id);
1464 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
1465 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
1466
1467 $vaultfactory = \mod_forum\local\container::get_vault_factory();
1468 $discussionlistvault = $vaultfactory->get_discussions_in_forum_vault();
1469
1470 // Call the external function passing forum id and sort order parameter.
1471 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_LASTPOST_ASC);
1472 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1473 // Discussions should be ordered by last post date in ascending order.
1474 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id);
1475 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
1476 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id);
1477
1478 // Call the external function passing forum id and sort order parameter.
1479 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_CREATED_DESC);
1480 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1481 // Discussions should be ordered by discussion creation date in descending order.
1482 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion3->id);
1483 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
1484 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
1485
1486 // Call the external function passing forum id and sort order parameter.
1487 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_CREATED_ASC);
1488 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1489 // Discussions should be ordered by discussion creation date in ascending order.
1490 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id);
1491 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion2->id);
1492 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id);
1493
1494 // Call the external function passing forum id and sort order parameter.
1495 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_REPLIES_DESC);
1496 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1497 // Discussions should be ordered by the number of replies in descending order.
1498 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion1->id);
1499 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id);
1500 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion2->id);
1501
1502 // Call the external function passing forum id and sort order parameter.
1503 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_REPLIES_ASC);
1504 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1505 // Discussions should be ordered by the number of replies in ascending order.
1506 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id);
1507 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id);
1508 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
1509
1510 // Pin discussion2.
1511 $DB->update_record('forum_discussions',
1512 (object) array('id' => $discussion2->id, 'pinned' => FORUM_DISCUSSION_PINNED));
1513
1514 // Call the external function passing forum id.
1515 $discussions = mod_forum_external::get_forum_discussions($forum1->id);
1516 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1517 // Discussions should be ordered by last post date in descending order by default.
1518 // Pinned discussions should be at the top of the list.
1519 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id);
1520 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion3->id);
1521 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion1->id);
1522
1523 // Call the external function passing forum id and sort order parameter.
1524 $discussions = mod_forum_external::get_forum_discussions($forum1->id, $discussionlistvault::SORTORDER_LASTPOST_ASC);
1525 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_returns(), $discussions);
1526 // Discussions should be ordered by last post date in ascending order.
1527 // Pinned discussions should be at the top of the list.
1528 $this->assertEquals($discussions['discussions'][0]['discussion'], $discussion2->id);
1529 $this->assertEquals($discussions['discussions'][1]['discussion'], $discussion1->id);
1530 $this->assertEquals($discussions['discussions'][2]['discussion'], $discussion3->id);
1531 }
1532
50a20317
JL
1533 /**
1534 * Test add_discussion_post
1535 */
1536 public function test_add_discussion_post() {
e881c4f5 1537 global $CFG;
50a20317
JL
1538
1539 $this->resetAfterTest(true);
1540
1541 $user = self::getDataGenerator()->create_user();
1542 $otheruser = self::getDataGenerator()->create_user();
1543
1544 self::setAdminUser();
1545
1546 // Create course to add the module.
1547 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
1548
1549 // Forum with tracking off.
1550 $record = new stdClass();
1551 $record->course = $course->id;
1552 $forum = self::getDataGenerator()->create_module('forum', $record);
1553 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
1554 $forumcontext = context_module::instance($forum->cmid);
1555
1556 // Add discussions to the forums.
1557 $record = new stdClass();
1558 $record->course = $course->id;
1559 $record->userid = $user->id;
1560 $record->forum = $forum->id;
1561 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1562
1563 // Try to post (user not enrolled).
1564 self::setUser($user);
1565 try {
1566 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1567 $this->fail('Exception expected due to being unenrolled from the course.');
1568 } catch (moodle_exception $e) {
1569 $this->assertEquals('requireloginerror', $e->errorcode);
1570 }
1571
1572 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1573 $this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
1574
41182118
BK
1575 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1576 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
50a20317
JL
1577
1578 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1579 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1580 // We receive the discussion and the post.
1581 $this->assertEquals(2, count($posts['posts']));
43ff833d
JL
1582
1583 $tested = false;
41182118
BK
1584 foreach ($posts['posts'] as $thispost) {
1585 if ($createdpost['postid'] == $thispost['id']) {
1586 $this->assertEquals('some subject', $thispost['subject']);
1587 $this->assertEquals('some text here...', $thispost['message']);
f2eb04c1 1588 $this->assertEquals(FORMAT_HTML, $thispost['messageformat']); // This is the default if format was not specified.
43ff833d
JL
1589 $tested = true;
1590 }
1591 }
1592 $this->assertTrue($tested);
50a20317 1593
f2eb04c1
EL
1594 // Let's simulate a call with any other format, it should be stored that way.
1595 global $DB; // Yes, we are going to use DB facilities too, because cannot rely on other functions for checking
1596 // the format. They eat it completely (going back to FORMAT_HTML. So we only can trust DB for further
1597 // processing.
1598 $formats = [FORMAT_PLAIN, FORMAT_MOODLE, FORMAT_MARKDOWN, FORMAT_HTML];
1599 $options = [];
1600 foreach ($formats as $format) {
1601 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost,
1602 'with some format', 'some formatted here...', $options, $format);
1603 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
1604 $dbformat = $DB->get_field('forum_posts', 'messageformat', ['id' => $createdpost['postid']]);
1605 $this->assertEquals($format, $dbformat);
1606 }
1607
117e4bd4
EL
1608 // Now let's try the 'topreferredformat' option. That should end with the content
1609 // transformed and the format being FORMAT_HTML (when, like in this case, user preferred
1610 // format is HTML, inferred from editor in preferences).
1611 $options = [['name' => 'topreferredformat', 'value' => true]];
f2eb04c1
EL
1612 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost,
1613 'interesting subject', 'with some https://example.com link', $options, FORMAT_MOODLE);
1614 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
1615 $dbpost = $DB->get_record('forum_posts', ['id' => $createdpost['postid']]);
1616 // Format HTML and content converted, we should get.
1617 $this->assertEquals(FORMAT_HTML, $dbpost->messageformat);
1618 $this->assertEquals('<div class="text_to_html">with some https://example.com link</div>', $dbpost->message);
1619
e881c4f5 1620 // Test inline and regular attachment in post
41182118
BK
1621 // Create a file in a draft area for inline attachments.
1622 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5 1623 $draftidattach = file_get_unused_draft_itemid();
41182118
BK
1624 self::setUser($user);
1625 $usercontext = context_user::instance($user->id);
1626 $filepath = '/';
1627 $filearea = 'draft';
1628 $component = 'user';
1629 $filenameimg = 'shouldbeanimage.txt';
e881c4f5 1630 $filerecordinline = array(
41182118
BK
1631 'contextid' => $usercontext->id,
1632 'component' => $component,
1633 'filearea' => $filearea,
1634 'itemid' => $draftidinlineattach,
1635 'filepath' => $filepath,
1636 'filename' => $filenameimg,
1637 );
1638 $fs = get_file_storage();
41182118 1639
e881c4f5
BK
1640 // Create a file in a draft area for regular attachments.
1641 $filerecordattach = $filerecordinline;
1642 $attachfilename = 'attachment.txt';
1643 $filerecordattach['filename'] = $attachfilename;
1644 $filerecordattach['itemid'] = $draftidattach;
1645 $fs->create_file_from_string($filerecordinline, 'image contents (not really)');
1646 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
1647
48143990 1648 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 1649 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
1650 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot
1651 . "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}"
1652 . '" alt="inlineimage">.';
1653 $createdpost = mod_forum_external::add_discussion_post($discussion->firstpost, 'new post inline attachment',
1654 $dummytext, $options);
1655 $createdpost = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $createdpost);
1656
1657 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
1658 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1659 // We receive the discussion and the post.
1660 // Can't guarantee order of posts during tests.
1661 $postfound = false;
1662 foreach ($posts['posts'] as $thispost) {
1663 if ($createdpost['postid'] == $thispost['id']) {
1664 $this->assertEquals($createdpost['postid'], $thispost['id']);
e881c4f5
BK
1665 $this->assertEquals($thispost['attachment'], 1, "There should be a non-inline attachment");
1666 $this->assertCount(1, $thispost['attachments'], "There should be 1 attachment");
1667 $this->assertEquals($thispost['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
1668 $this->assertContains('pluginfile.php', $thispost['message']);
1669 $postfound = true;
e881c4f5 1670 break;
41182118
BK
1671 }
1672 }
1673
1674 $this->assertTrue($postfound);
1675
50a20317
JL
1676 // Check not posting in groups the user is not member of.
1677 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1678 groups_add_member($group->id, $otheruser->id);
1679
1680 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
1681 $record->forum = $forum->id;
1682 $record->userid = $otheruser->id;
1683 $record->groupid = $group->id;
1684 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1685
1686 try {
1687 mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...');
1688 $this->fail('Exception expected due to invalid permissions for posting.');
1689 } catch (moodle_exception $e) {
50a20317
JL
1690 $this->assertEquals('nopostforum', $e->errorcode);
1691 }
50a20317 1692 }
7ab43ac8 1693
e321b282
MG
1694 /**
1695 * Test add_discussion_post and auto subscription to a discussion.
1696 */
1697 public function test_add_discussion_post_subscribe_discussion() {
1698 global $USER;
1699
1700 $this->resetAfterTest(true);
1701
1702 self::setAdminUser();
1703
1704 $user = self::getDataGenerator()->create_user();
1705 $admin = get_admin();
1706 // Create course to add the module.
1707 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
1708
1709 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1710
1711 // Forum with tracking off.
1712 $record = new stdClass();
1713 $record->course = $course->id;
1714 $forum = self::getDataGenerator()->create_module('forum', $record);
1715 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
1716
1717 // Add discussions to the forums.
1718 $record = new stdClass();
1719 $record->course = $course->id;
1720 $record->userid = $admin->id;
1721 $record->forum = $forum->id;
1722 $discussion1 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1723 $discussion2 = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
1724
1725 // Try to post as user.
1726 self::setUser($user);
1727 // Enable auto subscribe discussion.
1728 $USER->autosubscribe = true;
1729 // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference enabled).
1730 mod_forum_external::add_discussion_post($discussion1->firstpost, 'some subject', 'some text here...');
1731
1732 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id);
1733 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1734 // We receive the discussion and the post.
1735 $this->assertEquals(2, count($posts['posts']));
1736 // The user should be subscribed to the discussion after adding a discussion post.
1737 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm));
1738
1739 // Disable auto subscribe discussion.
1740 $USER->autosubscribe = false;
1741 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm));
1742 // Add a discussion post in a forum discussion where the user is subscribed (auto-subscribe preference disabled).
1743 mod_forum_external::add_discussion_post($discussion1->firstpost, 'some subject 1', 'some text here 1...');
1744
1745 $posts = mod_forum_external::get_forum_discussion_posts($discussion1->id);
1746 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1747 // We receive the discussion and the post.
1748 $this->assertEquals(3, count($posts['posts']));
1749 // The user should still be subscribed to the discussion after adding a discussion post.
1750 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion1->id, $cm));
1751
1752 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
1753 // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference disabled).
1754 mod_forum_external::add_discussion_post($discussion2->firstpost, 'some subject 2', 'some text here 2...');
1755
1756 $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id);
1757 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1758 // We receive the discussion and the post.
1759 $this->assertEquals(2, count($posts['posts']));
1760 // The user should still not be subscribed to the discussion after adding a discussion post.
1761 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
1762
1763 // Passing a value for the discussionsubscribe option parameter.
1764 $this->assertFalse(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
1765 // Add a discussion post in a forum discussion where the user is not subscribed (auto-subscribe preference disabled),
1766 // and the option parameter 'discussionsubscribe' => true in the webservice.
1767 $option = array('name' => 'discussionsubscribe', 'value' => true);
1768 $options[] = $option;
1769 mod_forum_external::add_discussion_post($discussion2->firstpost, 'some subject 2', 'some text here 2...',
1770 $options);
1771
1772 $posts = mod_forum_external::get_forum_discussion_posts($discussion2->id);
1773 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
1774 // We receive the discussion and the post.
1775 $this->assertEquals(3, count($posts['posts']));
1776 // The user should now be subscribed to the discussion after adding a discussion post.
1777 $this->assertTrue(\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussion2->id, $cm));
1778 }
1779
7ab43ac8
JL
1780 /*
1781 * Test add_discussion. A basic test since all the API functions are already covered by unit tests.
1782 */
1783 public function test_add_discussion() {
41182118 1784 global $CFG, $USER;
7ab43ac8
JL
1785 $this->resetAfterTest(true);
1786
1787 // Create courses to add the modules.
1788 $course = self::getDataGenerator()->create_course();
1789
1790 $user1 = self::getDataGenerator()->create_user();
1791 $user2 = self::getDataGenerator()->create_user();
1792
1793 // First forum with tracking off.
1794 $record = new stdClass();
1795 $record->course = $course->id;
1796 $record->type = 'news';
1797 $forum = self::getDataGenerator()->create_module('forum', $record);
1798
1799 self::setUser($user1);
1800 $this->getDataGenerator()->enrol_user($user1->id, $course->id);
1801
1802 try {
1803 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1804 $this->fail('Exception expected due to invalid permissions.');
1805 } catch (moodle_exception $e) {
1806 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1807 }
1808
1809 self::setAdminUser();
41182118
BK
1810 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1811 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
7ab43ac8
JL
1812
1813 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1814 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1815
1816 $this->assertCount(1, $discussions['discussions']);
1817 $this->assertCount(0, $discussions['warnings']);
1818
41182118 1819 $this->assertEquals($createddiscussion['discussionid'], $discussions['discussions'][0]['discussion']);
7ab43ac8
JL
1820 $this->assertEquals(-1, $discussions['discussions'][0]['groupid']);
1821 $this->assertEquals('the subject', $discussions['discussions'][0]['subject']);
1822 $this->assertEquals('some text here...', $discussions['discussions'][0]['message']);
1823
5f219cf1
BK
1824 $discussion2pinned = mod_forum_external::add_discussion($forum->id, 'the pinned subject', 'some 2 text here...', -1,
1825 array('options' => array('name' => 'discussionpinned',
1826 'value' => true)));
1827 $discussion3 = mod_forum_external::add_discussion($forum->id, 'the non pinnedsubject', 'some 3 text here...');
1828 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1829 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1830 $this->assertCount(3, $discussions['discussions']);
1831 $this->assertEquals($discussion2pinned['discussionid'], $discussions['discussions'][0]['discussion']);
41182118 1832
e881c4f5 1833 // Test inline and regular attachment in new discussion
41182118 1834 // Create a file in a draft area for inline attachments.
e881c4f5
BK
1835
1836 $fs = get_file_storage();
1837
41182118 1838 $draftidinlineattach = file_get_unused_draft_itemid();
e881c4f5
BK
1839 $draftidattach = file_get_unused_draft_itemid();
1840
41182118
BK
1841 $usercontext = context_user::instance($USER->id);
1842 $filepath = '/';
1843 $filearea = 'draft';
1844 $component = 'user';
1845 $filenameimg = 'shouldbeanimage.txt';
1846 $filerecord = array(
1847 'contextid' => $usercontext->id,
1848 'component' => $component,
1849 'filearea' => $filearea,
1850 'itemid' => $draftidinlineattach,
1851 'filepath' => $filepath,
1852 'filename' => $filenameimg,
1853 );
e881c4f5 1854
e881c4f5
BK
1855 // Create a file in a draft area for regular attachments.
1856 $filerecordattach = $filerecord;
1857 $attachfilename = 'attachment.txt';
1858 $filerecordattach['filename'] = $attachfilename;
1859 $filerecordattach['itemid'] = $draftidattach;
41182118 1860 $fs->create_file_from_string($filerecord, 'image contents (not really)');
e881c4f5
BK
1861 $fs->create_file_from_string($filerecordattach, 'simple text attachment');
1862
41182118
BK
1863 $dummytext = 'Here is an inline image: <img src="' . $CFG->wwwroot .
1864 "/draftfile.php/{$usercontext->id}/user/draft/{$draftidinlineattach}/{$filenameimg}" .
1865 '" alt="inlineimage">.';
1866
48143990 1867 $options = array(array('name' => 'inlineattachmentsid', 'value' => $draftidinlineattach),
e881c4f5 1868 array('name' => 'attachmentsid', 'value' => $draftidattach));
41182118
BK
1869 $createddiscussion = mod_forum_external::add_discussion($forum->id, 'the inline attachment subject',
1870 $dummytext, -1, $options);
1871 $createddiscussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $createddiscussion);
1872
1873 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1874 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1875
1876 $this->assertCount(4, $discussions['discussions']);
1877 $this->assertCount(0, $createddiscussion['warnings']);
1878 // Can't guarantee order of posts during tests.
1879 $postfound = false;
1880 foreach ($discussions['discussions'] as $thisdiscussion) {
1881 if ($createddiscussion['discussionid'] == $thisdiscussion['discussion']) {
e881c4f5
BK
1882 $this->assertEquals($thisdiscussion['attachment'], 1, "There should be a non-inline attachment");
1883 $this->assertCount(1, $thisdiscussion['attachments'], "There should be 1 attachment");
1884 $this->assertEquals($thisdiscussion['attachments'][0]['filename'], $attachfilename, "There should be 1 attachment");
41182118
BK
1885 $this->assertNotContains('draftfile.php', $thisdiscussion['message']);
1886 $this->assertContains('pluginfile.php', $thisdiscussion['message']);
1887 $postfound = true;
e881c4f5 1888 break;
41182118
BK
1889 }
1890 }
1891
1892 $this->assertTrue($postfound);
7ab43ac8
JL
1893 }
1894
1895 /**
1896 * Test adding discussions in a course with gorups
1897 */
1898 public function test_add_discussion_in_course_with_groups() {
1899 global $CFG;
1900
1901 $this->resetAfterTest(true);
1902
1903 // Create course to add the module.
1904 $course = self::getDataGenerator()->create_course(array('groupmode' => VISIBLEGROUPS, 'groupmodeforce' => 0));
1905 $user = self::getDataGenerator()->create_user();
1906 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1907
1908 // Forum forcing separate gropus.
1909 $record = new stdClass();
1910 $record->course = $course->id;
1911 $forum = self::getDataGenerator()->create_module('forum', $record, array('groupmode' => SEPARATEGROUPS));
1912
1913 // Try to post (user not enrolled).
1914 self::setUser($user);
1915
1916 // The user is not enroled in any group, try to post in a forum with separate groups.
1917 try {
1918 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1919 $this->fail('Exception expected due to invalid group permissions.');
1920 } catch (moodle_exception $e) {
1921 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1922 }
1923
1924 try {
1925 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', 0);
1926 $this->fail('Exception expected due to invalid group permissions.');
1927 } catch (moodle_exception $e) {
1928 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1929 }
1930
1931 // Create a group.
1932 $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1933
1934 // Try to post in a group the user is not enrolled.
1935 try {
1936 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
1937 $this->fail('Exception expected due to invalid group permissions.');
1938 } catch (moodle_exception $e) {
1939 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1940 }
1941
1942 // Add the user to a group.
1943 groups_add_member($group->id, $user->id);
1944
1945 // Try to post in a group the user is not enrolled.
1946 try {
1947 mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id + 1);
1948 $this->fail('Exception expected due to invalid group.');
1949 } catch (moodle_exception $e) {
1950 $this->assertEquals('cannotcreatediscussion', $e->errorcode);
1951 }
1952
1953 // Nost add the discussion using a valid group.
1954 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...', $group->id);
1955 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1956
1957 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1958 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1959
1960 $this->assertCount(1, $discussions['discussions']);
1961 $this->assertCount(0, $discussions['warnings']);
1962 $this->assertEquals($discussion['discussionid'], $discussions['discussions'][0]['discussion']);
1963 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1964
1965 // Now add a discussions without indicating a group. The function should guess the correct group.
1966 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1967 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1968
1969 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1970 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1971
1972 $this->assertCount(2, $discussions['discussions']);
1973 $this->assertCount(0, $discussions['warnings']);
1974 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1975 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1976
1977 // Enrol the same user in other group.
1978 $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
1979 groups_add_member($group2->id, $user->id);
1980
1981 // Now add a discussions without indicating a group. The function should guess the correct group (the first one).
1982 $discussion = mod_forum_external::add_discussion($forum->id, 'the subject', 'some text here...');
1983 $discussion = external_api::clean_returnvalue(mod_forum_external::add_discussion_returns(), $discussion);
1984
1985 $discussions = mod_forum_external::get_forum_discussions_paginated($forum->id);
1986 $discussions = external_api::clean_returnvalue(mod_forum_external::get_forum_discussions_paginated_returns(), $discussions);
1987
1988 $this->assertCount(3, $discussions['discussions']);
1989 $this->assertCount(0, $discussions['warnings']);
1990 $this->assertEquals($group->id, $discussions['discussions'][0]['groupid']);
1991 $this->assertEquals($group->id, $discussions['discussions'][1]['groupid']);
1992 $this->assertEquals($group->id, $discussions['discussions'][2]['groupid']);
1993
1994 }
1995
f5b4320e
P
1996 /*
1997 * Test set_lock_state.
1998 */
1999 public function test_set_lock_state() {
2000 global $DB;
2001 $this->resetAfterTest(true);
2002
2003 // Create courses to add the modules.
2004 $course = self::getDataGenerator()->create_course();
2005 $user = self::getDataGenerator()->create_user();
2006 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2007
2008 // First forum with tracking off.
2009 $record = new stdClass();
2010 $record->course = $course->id;
2011 $record->type = 'news';
2012 $forum = self::getDataGenerator()->create_module('forum', $record);
2013
2014 $record = new stdClass();
2015 $record->course = $course->id;
2016 $record->userid = $user->id;
2017 $record->forum = $forum->id;
2018 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2019
2020 // User who is a student.
2021 self::setUser($user);
2022 $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id, 'manual');
2023
2024 // Only a teacher should be able to lock a discussion.
bdb4a87d
P
2025 try {
2026 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
2027 $this->fail('Exception expected due to missing capability.');
2028 } catch (moodle_exception $e) {
2029 $this->assertEquals('errorcannotlock', $e->errorcode);
2030 }
f5b4320e
P
2031
2032 // Set the lock.
2033 self::setAdminUser();
2034 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, 0);
2035 $result = external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
bdb4a87d 2036 $this->assertTrue($result['locked']);
f5b4320e
P
2037 $this->assertNotEquals(0, $result['times']['locked']);
2038
2039 // Unset the lock.
2040 $result = mod_forum_external::set_lock_state($forum->id, $discussion->id, time());
2041 $result = external_api::clean_returnvalue(mod_forum_external::set_lock_state_returns(), $result);
bdb4a87d 2042 $this->assertFalse($result['locked']);
f5b4320e
P
2043 $this->assertEquals('0', $result['times']['locked']);
2044 }
2045
04cd8ae3
JL
2046 /*
2047 * Test can_add_discussion. A basic test since all the API functions are already covered by unit tests.
2048 */
2049 public function test_can_add_discussion() {
581e75bf 2050 global $DB;
04cd8ae3
JL
2051 $this->resetAfterTest(true);
2052
2053 // Create courses to add the modules.
2054 $course = self::getDataGenerator()->create_course();
2055
2056 $user = self::getDataGenerator()->create_user();
2057
2058 // First forum with tracking off.
2059 $record = new stdClass();
2060 $record->course = $course->id;
2061 $record->type = 'news';
2062 $forum = self::getDataGenerator()->create_module('forum', $record);
2063
2064 // User with no permissions to add in a news forum.
2065 self::setUser($user);
2066 $this->getDataGenerator()->enrol_user($user->id, $course->id);
2067
2068 $result = mod_forum_external::can_add_discussion($forum->id);
2069 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
2070 $this->assertFalse($result['status']);
581e75bf
JL
2071 $this->assertFalse($result['canpindiscussions']);
2072 $this->assertTrue($result['cancreateattachment']);
2073
2074 // Disable attachments.
2075 $DB->set_field('forum', 'maxattachments', 0, array('id' => $forum->id));
2076 $result = mod_forum_external::can_add_discussion($forum->id);
2077 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
2078 $this->assertFalse($result['status']);
2079 $this->assertFalse($result['canpindiscussions']);
2080 $this->assertFalse($result['cancreateattachment']);
2081 $DB->set_field('forum', 'maxattachments', 1, array('id' => $forum->id)); // Enable attachments again.
04cd8ae3
JL
2082
2083 self::setAdminUser();
2084 $result = mod_forum_external::can_add_discussion($forum->id);
2085 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
2086 $this->assertTrue($result['status']);
581e75bf
JL
2087 $this->assertTrue($result['canpindiscussions']);
2088 $this->assertTrue($result['cancreateattachment']);
cbf63d8e
SR
2089 }
2090
2091 /*
2092 * A basic test to make sure users cannot post to forum after the cutoff date.
2093 */
2094 public function test_can_add_discussion_after_cutoff() {
2095 $this->resetAfterTest(true);
2096
2097 // Create courses to add the modules.
2098 $course = self::getDataGenerator()->create_course();
2099
2100 $user = self::getDataGenerator()->create_user();
2101
2102 // Create a forum with cutoff date set to a past date.
2103 $forum = self::getDataGenerator()->create_module('forum', ['course' => $course->id, 'cutoffdate' => time() - 1]);
04cd8ae3 2104
cbf63d8e
SR
2105 // User with no mod/forum:canoverridecutoff capability.
2106 self::setUser($user);
2107 $this->getDataGenerator()->enrol_user($user->id, $course->id);
2108
2109 $result = mod_forum_external::can_add_discussion($forum->id);
2110 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
2111 $this->assertFalse($result['status']);
2112
2113 self::setAdminUser();
2114 $result = mod_forum_external::can_add_discussion($forum->id);
2115 $result = external_api::clean_returnvalue(mod_forum_external::can_add_discussion_returns(), $result);
2116 $this->assertTrue($result['status']);
04cd8ae3
JL
2117 }
2118
b7ce46df
JL
2119 /**
2120 * Test get forum posts discussions including rating information.
2121 */
2122 public function test_mod_forum_get_forum_discussion_rating_information() {
2123 global $DB, $CFG;
2124 require_once($CFG->dirroot . '/rating/lib.php');
2125
2126 $this->resetAfterTest(true);
2127
2128 $user1 = self::getDataGenerator()->create_user();
2129 $user2 = self::getDataGenerator()->create_user();
2130 $user3 = self::getDataGenerator()->create_user();
2131 $teacher = self::getDataGenerator()->create_user();
2132
2133 // Create course to add the module.
2134 $course = self::getDataGenerator()->create_course();
2135
2136 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2137 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
2138 $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id, 'manual');
2139 $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id, 'manual');
2140 $this->getDataGenerator()->enrol_user($user3->id, $course->id, $studentrole->id, 'manual');
2141 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
2142
2143 // Create the forum.
2144 $record = new stdClass();
2145 $record->course = $course->id;
2146 // Set Aggregate type = Average of ratings.
2147 $record->assessed = RATING_AGGREGATE_AVERAGE;
2148 $record->scale = 100;
2149 $forum = self::getDataGenerator()->create_module('forum', $record);
2150 $context = context_module::instance($forum->cmid);
2151
2152 // Add discussion to the forum.
2153 $record = new stdClass();
2154 $record->course = $course->id;
2155 $record->userid = $user1->id;
2156 $record->forum = $forum->id;
2157 $discussion = self::getDataGenerator()->get_plugin_generator('mod_forum')->create_discussion($record);
2158
2159 // Retrieve the first post.
2160 $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
2161
2162 // Rate the discussion as user2.
2163 $rating1 = new stdClass();
2164 $rating1->contextid = $context->id;
2165 $rating1->component = 'mod_forum';
2166 $rating1->ratingarea = 'post';
2167 $rating1->itemid = $post->id;
2168 $rating1->rating = 50;
2169 $rating1->scaleid = 100;
2170 $rating1->userid = $user2->id;
2171 $rating1->timecreated = time();
2172 $rating1->timemodified = time();
2173 $rating1->id = $DB->insert_record('rating', $rating1);
2174
2175 // Rate the discussion as user3.
2176 $rating2 = new stdClass();
2177 $rating2->contextid = $context->id;
2178 $rating2->component = 'mod_forum';
2179 $rating2->ratingarea = 'post';
2180 $rating2->itemid = $post->id;
2181 $rating2->rating = 100;
2182 $rating2->scaleid = 100;
2183 $rating2->userid = $user3->id;
2184 $rating2->timecreated = time() + 1;
2185 $rating2->timemodified = time() + 1;
2186 $rating2->id = $DB->insert_record('rating', $rating2);
2187
2188 // Retrieve the rating for the post as student.
2189 $this->setUser($user1);
2190 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
2191 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
2192 $this->assertCount(1, $posts['ratinginfo']['ratings']);
2193 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
2194 $this->assertFalse($posts['ratinginfo']['canviewall']);
2195 $this->assertFalse($posts['ratinginfo']['ratings'][0]['canrate']);
2196 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
2197 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
2198
2199 // Retrieve the rating for the post as teacher.
2200 $this->setUser($teacher);
2201 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
2202 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
2203 $this->assertCount(1, $posts['ratinginfo']['ratings']);
2204 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canviewaggregate']);
2205 $this->assertTrue($posts['ratinginfo']['canviewall']);
2206 $this->assertTrue($posts['ratinginfo']['ratings'][0]['canrate']);
2207 $this->assertEquals(2, $posts['ratinginfo']['ratings'][0]['count']);
2208 $this->assertEquals(($rating1->rating + $rating2->rating) / 2, $posts['ratinginfo']['ratings'][0]['aggregate']);
2209 }
4daa0d08
JL
2210
2211 /**
2212 * Test mod_forum_get_forum_access_information.
2213 */
2214 public function test_mod_forum_get_forum_access_information() {
2215 global $DB;
2216
2217 $this->resetAfterTest(true);
2218
2219 $student = self::getDataGenerator()->create_user();
2220 $course = self::getDataGenerator()->create_course();
2221 // Create the forum.
2222 $record = new stdClass();
2223 $record->course = $course->id;
2224 $forum = self::getDataGenerator()->create_module('forum', $record);
2225
2226 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
2227 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
2228
2229 self::setUser($student);
2230 $result = mod_forum_external::get_forum_access_information($forum->id);
2231 $result = external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
2232
2233 // Check default values for capabilities.
2234 $enabledcaps = array('canviewdiscussion', 'canstartdiscussion', 'canreplypost', 'canviewrating', 'cancreateattachment',
13cd05ac 2235 'canexportownpost', 'cancantogglefavourite', 'candeleteownpost', 'canallowforcesubscribe');
4daa0d08
JL
2236
2237 unset($result['warnings']);
2238 foreach ($result as $capname => $capvalue) {
2239 if (in_array($capname, $enabledcaps)) {
2240 $this->assertTrue($capvalue);
2241 } else {
2242 $this->assertFalse($capvalue);
2243 }
2244 }
2245 // Now, unassign some capabilities.
2246 unassign_capability('mod/forum:deleteownpost', $studentrole->id);
2247 unassign_capability('mod/forum:allowforcesubscribe', $studentrole->id);
2248 array_pop($enabledcaps);
2249 array_pop($enabledcaps);
2250 accesslib_clear_all_caches_for_unit_testing();
2251
2252 $result = mod_forum_external::get_forum_access_information($forum->id);
2253 $result = external_api::clean_returnvalue(mod_forum_external::get_forum_access_information_returns(), $result);
2254 unset($result['warnings']);
2255 foreach ($result as $capname => $capvalue) {
2256 if (in_array($capname, $enabledcaps)) {
2257 $this->assertTrue($capvalue);
2258 } else {
2259 $this->assertFalse($capvalue);
2260 }
2261 }
2262 }
bc4c7337
AN
2263
2264 /**
2265 * Test add_discussion_post
2266 */
2267 public function test_add_discussion_post_private() {
2268 global $DB;
2269
2270 $this->resetAfterTest(true);
2271
2272 self::setAdminUser();
2273
2274 // Create course to add the module.
2275 $course = self::getDataGenerator()->create_course();
2276
2277 // Standard forum.
2278 $record = new stdClass();
2279 $record->course = $course->id;
2280 $forum = self::getDataGenerator()->create_module('forum', $record);
2281 $cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
2282 $forumcontext = context_module::instance($forum->cmid);
2283 $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
2284
2285 // Create an enrol users.
2286 $student1 = self::getDataGenerator()->create_user();
2287 $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
2288 $student2 = self::getDataGenerator()->create_user();
2289 $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
2290 $teacher1 = self::getDataGenerator()->create_user();
2291 $this->getDataGenerator()->enrol_user($teacher1->id, $course->id, 'editingteacher');
2292 $teacher2 = self::getDataGenerator()->create_user();
2293 $this->getDataGenerator()->enrol_user($teacher2->id, $course->id, 'editingteacher');
2294
2295 // Add a new discussion to the forum.
2296 self::setUser($student1);
2297 $record = new stdClass();
2298 $record->course = $course->id;
2299 $record->userid = $student1->id;
2300 $record->forum = $forum->id;
2301 $discussion = $generator->create_discussion($record);
2302
2303 // Have the teacher reply privately.
2304 self::setUser($teacher1);
2305 $post = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...', [
2306 [
2307 'name' => 'private',
2308 'value' => true,
2309 ],
2310 ]);
2311 $post = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $post);
2312 $privatereply = $DB->get_record('forum_posts', array('id' => $post['postid']));
2313 $this->assertEquals($student1->id, $privatereply->privatereplyto);
2314 // Bump the time of the private reply to ensure order.
2315 $privatereply->created++;
2316 $privatereply->modified = $privatereply->created;
2317 $DB->update_record('forum_posts', $privatereply);
2318
2319 // The teacher will receive their private reply.
2320 self::setUser($teacher1);
2321 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
2322 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
2323 $this->assertEquals(2, count($posts['posts']));
2324 $this->assertTrue($posts['posts'][0]['isprivatereply']);
2325
2326 // Another teacher on the course will also receive the private reply.
2327 self::setUser($teacher2);
2328 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
2329 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
2330 $this->assertEquals(2, count($posts['posts']));
2331 $this->assertTrue($posts['posts'][0]['isprivatereply']);
2332
2333 // The student will receive the private reply.
2334 self::setUser($student1);
2335 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
2336 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
2337 $this->assertEquals(2, count($posts['posts']));
2338 $this->assertTrue($posts['posts'][0]['isprivatereply']);
2339
2340 // Another student will not receive the private reply.
2341 self::setUser($student2);
2342 $posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
2343 $posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
2344 $this->assertEquals(1, count($posts['posts']));
2345 $this->assertFalse($posts['posts'][0]['isprivatereply']);
2346
2347 // A user cannot reply to a private reply.
2348 self::setUser($teacher2);
2349 $this->expectException('coding_exception');
2350 $post = mod_forum_external::add_discussion_post($privatereply->id, 'some subject', 'some text here...', [
2351 'options' => [
2352 'name' => 'private',
2353 'value' => false,
2354 ],
2355 ]);
2356 }
2b9fe87d 2357}