MDL-65071 forum: Fix webservice unit tests
[moodle.git] / mod / forum / externallib.php
CommitLineData
2b9fe87d
MN
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * External forum API
20 *
21 * @package mod_forum
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
28require_once("$CFG->libdir/externallib.php");
29
9b4f09ba 30use mod_forum\local\exporters\post as post_exporter;
99bda8a7 31use mod_forum\local\exporters\discussion as discussion_exporter;
9b4f09ba 32
2b9fe87d
MN
33class mod_forum_external extends external_api {
34
35 /**
a9a0cb69 36 * Describes the parameters for get_forum.
2b9fe87d 37 *
9db43c73 38 * @return external_function_parameters
2b9fe87d
MN
39 * @since Moodle 2.5
40 */
41 public static function get_forums_by_courses_parameters() {
42 return new external_function_parameters (
43 array(
44 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID',
a69c9abd 45 VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of Course IDs', VALUE_DEFAULT, array()),
2b9fe87d
MN
46 )
47 );
48 }
49
50 /**
51 * Returns a list of forums in a provided list of courses,
52 * if no list is provided all forums that the user can view
53 * will be returned.
54 *
55 * @param array $courseids the course ids
56 * @return array the forum details
57 * @since Moodle 2.5
58 */
59 public static function get_forums_by_courses($courseids = array()) {
c8f1d8a0 60 global $CFG;
2b9fe87d
MN
61
62 require_once($CFG->dirroot . "/mod/forum/lib.php");
63
a9a0cb69
MN
64 $params = self::validate_parameters(self::get_forums_by_courses_parameters(), array('courseids' => $courseids));
65
583b02e4 66 $courses = array();
a9a0cb69 67 if (empty($params['courseids'])) {
583b02e4
FM
68 $courses = enrol_get_my_courses();
69 $params['courseids'] = array_keys($courses);
2b9fe87d
MN
70 }
71
72 // Array to store the forums to return.
73 $arrforums = array();
ea5b910b 74 $warnings = array();
2b9fe87d 75
0c246ae5 76 // Ensure there are courseids to loop through.
ea5b910b
JL
77 if (!empty($params['courseids'])) {
78
583b02e4 79 list($courses, $warnings) = external_util::validate_courses($params['courseids'], $courses);
c8f1d8a0
JL
80
81 // Get the forums in this course. This function checks users visibility permissions.
ea5b910b
JL
82 $forums = get_all_instances_in_courses("forum", $courses);
83 foreach ($forums as $forum) {
84
85 $course = $courses[$forum->course];
86 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
87 $context = context_module::instance($cm->id);
88
89 // Skip forums we are not allowed to see discussions.
90 if (!has_capability('mod/forum:viewdiscussion', $context)) {
91 continue;
2b9fe87d 92 }
ea5b910b 93
5b587c75 94 $forum->name = external_format_string($forum->name, $context->id);
ea5b910b
JL
95 // Format the intro before being returning using the format setting.
96 list($forum->intro, $forum->introformat) = external_format_text($forum->intro, $forum->introformat,
d33c67bc 97 $context->id, 'mod_forum', 'intro', null);
7ef49bd3 98 $forum->introfiles = external_util::get_area_files($context->id, 'mod_forum', 'intro', false, false);
ea5b910b
JL
99 // Discussions count. This function does static request cache.
100 $forum->numdiscussions = forum_count_discussions($forum, $cm, $course);
101 $forum->cmid = $forum->coursemodule;
102 $forum->cancreatediscussions = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
4669389d 103 $forum->istracked = forum_tp_is_tracked($forum);
2256bb74
JL
104 if ($forum->istracked) {
105 $forum->unreadpostscount = forum_tp_count_forum_unread_posts($cm, $course);
106 }
ea5b910b
JL
107
108 // Add the forum to the array to return.
109 $arrforums[$forum->id] = $forum;
2b9fe87d
MN
110 }
111 }
112
113 return $arrforums;
114 }
115
116 /**
a9a0cb69 117 * Describes the get_forum return value.
2b9fe87d
MN
118 *
119 * @return external_single_structure
120 * @since Moodle 2.5
121 */
0f3bbfd4 122 public static function get_forums_by_courses_returns() {
2b9fe87d
MN
123 return new external_multiple_structure(
124 new external_single_structure(
125 array(
126 'id' => new external_value(PARAM_INT, 'Forum id'),
5b587c75 127 'course' => new external_value(PARAM_INT, 'Course id'),
2b9fe87d 128 'type' => new external_value(PARAM_TEXT, 'The forum type'),
5b587c75 129 'name' => new external_value(PARAM_RAW, 'Forum name'),
2b9fe87d
MN
130 'intro' => new external_value(PARAM_RAW, 'The forum intro'),
131 'introformat' => new external_format_value('intro'),
7ef49bd3 132 'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
99bcb318
SR
133 'duedate' => new external_value(PARAM_INT, 'duedate for the user', VALUE_OPTIONAL),
134 'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user', VALUE_OPTIONAL),
2b9fe87d
MN
135 'assessed' => new external_value(PARAM_INT, 'Aggregate type'),
136 'assesstimestart' => new external_value(PARAM_INT, 'Assess start time'),
137 'assesstimefinish' => new external_value(PARAM_INT, 'Assess finish time'),
138 'scale' => new external_value(PARAM_INT, 'Scale'),
139 'maxbytes' => new external_value(PARAM_INT, 'Maximum attachment size'),
140 'maxattachments' => new external_value(PARAM_INT, 'Maximum number of attachments'),
141 'forcesubscribe' => new external_value(PARAM_INT, 'Force users to subscribe'),
142 'trackingtype' => new external_value(PARAM_INT, 'Subscription mode'),
143 'rsstype' => new external_value(PARAM_INT, 'RSS feed for this activity'),
144 'rssarticles' => new external_value(PARAM_INT, 'Number of RSS recent articles'),
145 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
146 'warnafter' => new external_value(PARAM_INT, 'Post threshold for warning'),
147 'blockafter' => new external_value(PARAM_INT, 'Post threshold for blocking'),
148 'blockperiod' => new external_value(PARAM_INT, 'Time period for blocking'),
149 'completiondiscussions' => new external_value(PARAM_INT, 'Student must create discussions'),
150 'completionreplies' => new external_value(PARAM_INT, 'Student must post replies'),
151 'completionposts' => new external_value(PARAM_INT, 'Student must post discussions or replies'),
7ea6ada3 152 'cmid' => new external_value(PARAM_INT, 'Course module id'),
ea5b910b
JL
153 'numdiscussions' => new external_value(PARAM_INT, 'Number of discussions in the forum', VALUE_OPTIONAL),
154 'cancreatediscussions' => new external_value(PARAM_BOOL, 'If the user can create discussions', VALUE_OPTIONAL),
0f3bbfd4 155 'lockdiscussionafter' => new external_value(PARAM_INT, 'After what period a discussion is locked', VALUE_OPTIONAL),
4669389d 156 'istracked' => new external_value(PARAM_BOOL, 'If the user is tracking the forum', VALUE_OPTIONAL),
2256bb74
JL
157 'unreadpostscount' => new external_value(PARAM_INT, 'The number of unread posts for tracked forums',
158 VALUE_OPTIONAL),
2b9fe87d
MN
159 ), 'forum'
160 )
161 );
162 }
a9a0cb69 163
bc4c7337
AN
164 /**
165 * Get the forum posts in the specified discussion.
166 *
167 * @param int $discussionid
168 * @param string $sortby
169 * @param string $sortdirection
170 * @return array
171 */
172 public static function get_discussion_posts(int $discussionid, ?string $sortby, ?string $sortdirection) {
173 global $USER;
174 // Validate the parameter.
175 $params = self::validate_parameters(self::get_discussion_posts_parameters(), [
176 'discussionid' => $discussionid,
177 'sortby' => $sortby,
178 'sortdirection' => $sortdirection,
179 ]);
180 $warnings = [];
181
182 $vaultfactory = mod_forum\local\container::get_vault_factory();
183
184 $discussionvault = $vaultfactory->get_discussion_vault();
185 $discussion = $discussionvault->get_from_id($params['discussionid']);
186
187 $forumvault = $vaultfactory->get_forum_vault();
188 $forum = $forumvault->get_from_id($discussion->get_forum_id());
189
190 $sortby = $params['sortby'];
191 $sortdirection = $params['sortdirection'];
192 $sortallowedvalues = ['id', 'created', 'modified'];
193 $directionallowedvalues = ['ASC', 'DESC'];
194
195 if (!in_array(strtolower($sortby), $sortallowedvalues)) {
196 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
197 'allowed values are: ' . implode(', ', $sortallowedvalues));
198 }
199
200 $sortdirection = strtoupper($sortdirection);
201 if (!in_array($sortdirection, $directionallowedvalues)) {
202 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
203 'allowed values are: ' . implode(',', $directionallowedvalues));
204 }
205
206 $managerfactory = mod_forum\local\container::get_manager_factory();
207 $capabilitymanager = $managerfactory->get_capability_manager($forum);
208
209 $postvault = $vaultfactory->get_post_vault();
210 $posts = $postvault->get_from_discussion_id(
211 $USER,
212 $discussion->get_id(),
213 $capabilitymanager->can_view_any_private_reply($USER),
214 "{$sortby} {$sortdirection}"
215 );
216
217 $builderfactory = mod_forum\local\container::get_builder_factory();
218 $postbuilder = $builderfactory->get_exported_posts_builder();
219
220 $legacydatamapper = mod_forum\local\container::get_legacy_data_mapper_factory();
221
222 return [
223 'posts' => $postbuilder->build($USER, [$forum], [$discussion], $posts),
224 'ratinginfo' => \core_rating\external\util::get_rating_info(
225 $legacydatamapper->get_forum_data_mapper()->to_legacy_object($forum),
226 $forum->get_context(),
227 'mod_forum',
228 'post',
229 $legacydatamapper->get_post_data_mapper()->to_legacy_objects($posts)
230 ),
231 'warnings' => $warnings,
232 ];
233 }
234
235 /**
236 * Describe the post parameters.
237 *
238 * @return external_function_parameters
239 */
240 public static function get_discussion_posts_parameters() {
241 return new external_function_parameters ([
242 'discussionid' => new external_value(PARAM_INT, 'The ID of the discussion from which to fetch posts.', VALUE_REQUIRED),
243 'sortby' => new external_value(PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
244 'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
245 ]);
246 }
247
248 /**
249 * Describe the post return format.
250 *
251 * @return external_single_structure
252 */
253 public static function get_discussion_posts_returns() {
254 return new external_single_structure([
255 'posts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
256 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
257 'warnings' => new external_warnings()
258 ]);
259 }
260
e2ede426
JL
261 /**
262 * Describes the parameters for get_forum_discussion_posts.
263 *
9db43c73 264 * @return external_function_parameters
e2ede426
JL
265 * @since Moodle 2.7
266 */
267 public static function get_forum_discussion_posts_parameters() {
268 return new external_function_parameters (
269 array(
270 'discussionid' => new external_value(PARAM_INT, 'discussion ID', VALUE_REQUIRED),
271 'sortby' => new external_value(PARAM_ALPHA,
272 'sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
273 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
274 )
275 );
276 }
277
278 /**
279 * Returns a list of forum posts for a discussion
280 *
281 * @param int $discussionid the post ids
282 * @param string $sortby sort by this element (id, created or modified)
283 * @param string $sortdirection sort direction: ASC or DESC
284 *
285 * @return array the forum post details
286 * @since Moodle 2.7
8245daba 287 * @todo MDL-65252 This will be removed in Moodle 4.1
e2ede426 288 */
fb8840d2 289 public static function get_forum_discussion_posts($discussionid, $sortby = "created", $sortdirection = "DESC") {
d85bedf7 290 global $CFG, $DB, $USER, $PAGE;
e2ede426 291
b1aa7dfa 292 $posts = array();
e2ede426
JL
293 $warnings = array();
294
295 // Validate the parameter.
296 $params = self::validate_parameters(self::get_forum_discussion_posts_parameters(),
297 array(
298 'discussionid' => $discussionid,
299 'sortby' => $sortby,
300 'sortdirection' => $sortdirection));
301
302 // Compact/extract functions are not recommended.
303 $discussionid = $params['discussionid'];
304 $sortby = $params['sortby'];
305 $sortdirection = $params['sortdirection'];
306
307 $sortallowedvalues = array('id', 'created', 'modified');
308 if (!in_array($sortby, $sortallowedvalues)) {
309 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
310 'allowed values are: ' . implode(',', $sortallowedvalues));
311 }
312
313 $sortdirection = strtoupper($sortdirection);
314 $directionallowedvalues = array('ASC', 'DESC');
315 if (!in_array($sortdirection, $directionallowedvalues)) {
316 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
317 'allowed values are: ' . implode(',', $directionallowedvalues));
318 }
319
320 $discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
321 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
322 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
323 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
324
325 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
326 $modcontext = context_module::instance($cm->id);
327 self::validate_context($modcontext);
328
329 // This require must be here, see mod/forum/discuss.php.
330 require_once($CFG->dirroot . "/mod/forum/lib.php");
331
332 // Check they have the view forum capability.
333 require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
334
335 if (! $post = forum_get_post_full($discussion->firstpost)) {
336 throw new moodle_exception('notexists', 'forum');
337 }
338
339 // This function check groups, qanda, timed discussions, etc.
340 if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
341 throw new moodle_exception('noviewdiscussionspermission', 'forum');
342 }
343
344 $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
345
346 // We will add this field in the response.
347 $canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
348
349 $forumtracked = forum_tp_is_tracked($forum);
350
351 $sort = 'p.' . $sortby . ' ' . $sortdirection;
b1aa7dfa 352 $allposts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
e2ede426 353
b1aa7dfa 354 foreach ($allposts as $post) {
3e95e09b 355 if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
e2ede426
JL
356 $warning = array();
357 $warning['item'] = 'post';
358 $warning['itemid'] = $post->id;
359 $warning['warningcode'] = '1';
360 $warning['message'] = 'You can\'t see this post';
361 $warnings[] = $warning;
362 continue;
363 }
364
365 // Function forum_get_all_discussion_posts adds postread field.
f47eeafd
JL
366 // Note that the value returned can be a boolean or an integer. The WS expects a boolean.
367 if (empty($post->postread)) {
b1aa7dfa 368 $post->postread = false;
f47eeafd 369 } else {
b1aa7dfa 370 $post->postread = true;
e2ede426 371 }
f47eeafd 372
bc4c7337
AN
373 $post->isprivatereply = !empty($post->privatereplyto);
374
b1aa7dfa
JL
375 $post->canreply = $canreply;
376 if (!empty($post->children)) {
377 $post->children = array_keys($post->children);
fb8840d2 378 } else {
b1aa7dfa 379 $post->children = array();
e2ede426
JL
380 }
381
3e95e09b
AN
382 if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
383 // The post is available, but has been marked as deleted.
384 // It will still be available but filled with a placeholder.
385 $post->userid = null;
386 $post->userfullname = null;
387 $post->userpictureurl = null;
388
389 $post->subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
390 $post->message = get_string('privacy:request:delete:post:message', 'mod_forum');
391
392 $post->deleted = true;
393 $posts[] = $post;
394
395 continue;
396 }
397 $post->deleted = false;
398
30861fbd
JL
399 if (forum_is_author_hidden($post, $forum)) {
400 $post->userid = null;
401 $post->userfullname = null;
402 $post->userpictureurl = null;
403 } else {
404 $user = new stdclass();
405 $user->id = $post->userid;
406 $user = username_load_fields_from_object($user, $post, null, array('picture', 'imagealt', 'email'));
407 $post->userfullname = fullname($user, $canviewfullname);
81f810dc 408
30861fbd
JL
409 $userpicture = new user_picture($user);
410 $userpicture->size = 1; // Size f1.
411 $post->userpictureurl = $userpicture->get_url($PAGE)->out(false);
412 }
14ebc396 413
55bb8189 414 $post->subject = external_format_string($post->subject, $modcontext->id);
48fb0250
JL
415 // Rewrite embedded images URLs.
416 list($post->message, $post->messageformat) =
417 external_format_text($post->message, $post->messageformat, $modcontext->id, 'mod_forum', 'post', $post->id);
418
419 // List attachments.
420 if (!empty($post->attachment)) {
14590070 421 $post->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment', $post->id);
48fb0250 422 }
c8743f62
JL
423 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $post->id);
424 if (!empty($messageinlinefiles)) {
425 $post->messageinlinefiles = $messageinlinefiles;
426 }
6c344ff2
JL
427 // Post tags.
428 $post->tags = \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $post->id);
e2ede426 429
b1aa7dfa 430 $posts[] = $post;
e2ede426
JL
431 }
432
433 $result = array();
434 $result['posts'] = $posts;
b7ce46df 435 $result['ratinginfo'] = \core_rating\external\util::get_rating_info($forum, $modcontext, 'mod_forum', 'post', $posts);
e2ede426
JL
436 $result['warnings'] = $warnings;
437 return $result;
438 }
439
440 /**
441 * Describes the get_forum_discussion_posts return value.
442 *
443 * @return external_single_structure
444 * @since Moodle 2.7
445 */
446 public static function get_forum_discussion_posts_returns() {
447 return new external_single_structure(
448 array(
449 'posts' => new external_multiple_structure(
450 new external_single_structure(
451 array(
452 'id' => new external_value(PARAM_INT, 'Post id'),
453 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
454 'parent' => new external_value(PARAM_INT, 'Parent id'),
455 'userid' => new external_value(PARAM_INT, 'User id'),
456 'created' => new external_value(PARAM_INT, 'Creation time'),
457 'modified' => new external_value(PARAM_INT, 'Time modified'),
458 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
459 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
460 'message' => new external_value(PARAM_RAW, 'The post message'),
48fb0250 461 'messageformat' => new external_format_value('message'),
e2ede426 462 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
c8743f62 463 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
48fb0250 464 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
14590070 465 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
e2ede426
JL
466 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
467 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
468 'children' => new external_multiple_structure(new external_value(PARAM_INT, 'children post id')),
469 'canreply' => new external_value(PARAM_BOOL, 'The user can reply to posts?'),
470 'postread' => new external_value(PARAM_BOOL, 'The post was read'),
694bf0c7 471 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
3e95e09b
AN
472 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL),
473 'deleted' => new external_value(PARAM_BOOL, 'This post has been removed.'),
bc4c7337 474 'isprivatereply' => new external_value(PARAM_BOOL, 'The post is a private reply'),
6c344ff2
JL
475 'tags' => new external_multiple_structure(
476 \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
477 ),
e2ede426
JL
478 ), 'post'
479 )
480 ),
b7ce46df 481 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
e2ede426
JL
482 'warnings' => new external_warnings()
483 )
484 );
485 }
486
bc4c7337
AN
487 /**
488 * Mark the get_forum_discussion_posts web service as deprecated.
489 *
490 * @return bool
491 */
492 public static function get_forum_discussion_posts_is_deprecated() {
493 return true;
494 }
495
7c51b40a
JL
496 /**
497 * Describes the parameters for get_forum_discussions_paginated.
498 *
1a9c60e9 499 * @deprecated since 3.7
9db43c73 500 * @return external_function_parameters
7c51b40a
JL
501 * @since Moodle 2.8
502 */
503 public static function get_forum_discussions_paginated_parameters() {
504 return new external_function_parameters (
505 array(
506 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
507 'sortby' => new external_value(PARAM_ALPHA,
508 'sort by this element: id, timemodified, timestart or timeend', VALUE_DEFAULT, 'timemodified'),
509 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
510 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
511 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
512 )
513 );
514 }
515
516 /**
517 * Returns a list of forum discussions optionally sorted and paginated.
518 *
1a9c60e9 519 * @deprecated since 3.7
7c51b40a
JL
520 * @param int $forumid the forum instance id
521 * @param string $sortby sort by this element (id, timemodified, timestart or timeend)
522 * @param string $sortdirection sort direction: ASC or DESC
523 * @param int $page page number
524 * @param int $perpage items per page
525 *
526 * @return array the forum discussion details including warnings
527 * @since Moodle 2.8
528 */
529 public static function get_forum_discussions_paginated($forumid, $sortby = 'timemodified', $sortdirection = 'DESC',
1a9c60e9 530 $page = -1, $perpage = 0) {
d85bedf7 531 global $CFG, $DB, $USER, $PAGE;
7c51b40a
JL
532
533 require_once($CFG->dirroot . "/mod/forum/lib.php");
534
535 $warnings = array();
039c81f0 536 $discussions = array();
7c51b40a
JL
537
538 $params = self::validate_parameters(self::get_forum_discussions_paginated_parameters(),
539 array(
540 'forumid' => $forumid,
541 'sortby' => $sortby,
542 'sortdirection' => $sortdirection,
543 'page' => $page,
544 'perpage' => $perpage
545 )
546 );
547
548 // Compact/extract functions are not recommended.
549 $forumid = $params['forumid'];
550 $sortby = $params['sortby'];
551 $sortdirection = $params['sortdirection'];
552 $page = $params['page'];
553 $perpage = $params['perpage'];
554
555 $sortallowedvalues = array('id', 'timemodified', 'timestart', 'timeend');
556 if (!in_array($sortby, $sortallowedvalues)) {
557 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
558 'allowed values are: ' . implode(',', $sortallowedvalues));
559 }
560
561 $sortdirection = strtoupper($sortdirection);
562 $directionallowedvalues = array('ASC', 'DESC');
563 if (!in_array($sortdirection, $directionallowedvalues)) {
564 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
565 'allowed values are: ' . implode(',', $directionallowedvalues));
566 }
567
568 $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
569 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
570 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
571
572 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
573 $modcontext = context_module::instance($cm->id);
574 self::validate_context($modcontext);
575
576 // Check they have the view forum capability.
577 require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
578
5f219cf1 579 $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
4f3a2d21 580 $alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
7c51b40a 581
039c81f0 582 if ($alldiscussions) {
7c51b40a
JL
583 $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
584
585 // Get the unreads array, this takes a forum id and returns data for all discussions.
586 $unreads = array();
587 if ($cantrack = forum_tp_can_track_forums($forum)) {
588 if ($forumtracked = forum_tp_is_tracked($forum)) {
589 $unreads = forum_get_discussions_unread($cm);
590 }
591 }
592 // The forum function returns the replies for all the discussions in a given forum.
bc4c7337 593 $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
565cccfa 594 $canlock = has_capability('moodle/course:manageactivities', $modcontext, $USER);
bc4c7337 595 $replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage, $canseeprivatereplies);
7c51b40a 596
039c81f0
JL
597 foreach ($alldiscussions as $discussion) {
598
7c51b40a 599 // This function checks for qanda forums.
039c81f0
JL
600 // Note that the forum_get_discussions returns as id the post id, not the discussion id so we need to do this.
601 $discussionrec = clone $discussion;
602 $discussionrec->id = $discussion->discussion;
603 if (!forum_user_can_see_discussion($forum, $discussionrec, $modcontext)) {
7c51b40a
JL
604 $warning = array();
605 // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
606 $warning['item'] = 'post';
607 $warning['itemid'] = $discussion->id;
608 $warning['warningcode'] = '1';
609 $warning['message'] = 'You can\'t see this discussion';
610 $warnings[] = $warning;
611 continue;
612 }
613
614 $discussion->numunread = 0;
615 if ($cantrack && $forumtracked) {
616 if (isset($unreads[$discussion->discussion])) {
617 $discussion->numunread = (int) $unreads[$discussion->discussion];
618 }
619 }
620
621 $discussion->numreplies = 0;
622 if (!empty($replies[$discussion->discussion])) {
623 $discussion->numreplies = (int) $replies[$discussion->discussion]->replies;
624 }
625
55bb8189
JL
626 $discussion->name = external_format_string($discussion->name, $modcontext->id);
627 $discussion->subject = external_format_string($discussion->subject, $modcontext->id);
7c51b40a
JL
628 // Rewrite embedded images URLs.
629 list($discussion->message, $discussion->messageformat) =
630 external_format_text($discussion->message, $discussion->messageformat,
631 $modcontext->id, 'mod_forum', 'post', $discussion->id);
632
633 // List attachments.
634 if (!empty($discussion->attachment)) {
14590070
JL
635 $discussion->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment',
636 $discussion->id);
7c51b40a 637 }
c8743f62
JL
638 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $discussion->id);
639 if (!empty($messageinlinefiles)) {
640 $discussion->messageinlinefiles = $messageinlinefiles;
641 }
7c51b40a 642
565cccfa
P
643 $discussion->locked = forum_discussion_is_locked($forum, $discussion);
644 $discussion->canlock = $canlock;
0f3bbfd4
AN
645 $discussion->canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
646
30861fbd
JL
647 if (forum_is_author_hidden($discussion, $forum)) {
648 $discussion->userid = null;
649 $discussion->userfullname = null;
650 $discussion->userpictureurl = null;
651
652 $discussion->usermodified = null;
653 $discussion->usermodifiedfullname = null;
654 $discussion->usermodifiedpictureurl = null;
655 } else {
656 $picturefields = explode(',', user_picture::fields());
657
658 // Load user objects from the results of the query.
659 $user = new stdclass();
660 $user->id = $discussion->userid;
661 $user = username_load_fields_from_object($user, $discussion, null, $picturefields);
662 // Preserve the id, it can be modified by username_load_fields_from_object.
663 $user->id = $discussion->userid;
664 $discussion->userfullname = fullname($user, $canviewfullname);
665
666 $userpicture = new user_picture($user);
667 $userpicture->size = 1; // Size f1.
668 $discussion->userpictureurl = $userpicture->get_url($PAGE)->out(false);
669
670 $usermodified = new stdclass();
671 $usermodified->id = $discussion->usermodified;
672 $usermodified = username_load_fields_from_object($usermodified, $discussion, 'um', $picturefields);
673 // Preserve the id (it can be overwritten due to the prefixed $picturefields).
674 $usermodified->id = $discussion->usermodified;
675 $discussion->usermodifiedfullname = fullname($usermodified, $canviewfullname);
676
677 $userpicture = new user_picture($usermodified);
678 $userpicture->size = 1; // Size f1.
679 $discussion->usermodifiedpictureurl = $userpicture->get_url($PAGE)->out(false);
680 }
681
039c81f0 682 $discussions[] = $discussion;
7c51b40a
JL
683 }
684 }
685
686 $result = array();
687 $result['discussions'] = $discussions;
688 $result['warnings'] = $warnings;
689 return $result;
690
691 }
692
693 /**
694 * Describes the get_forum_discussions_paginated return value.
695 *
1a9c60e9 696 * @deprecated since 3.7
7c51b40a
JL
697 * @return external_single_structure
698 * @since Moodle 2.8
699 */
700 public static function get_forum_discussions_paginated_returns() {
701 return new external_single_structure(
702 array(
703 'discussions' => new external_multiple_structure(
704 new external_single_structure(
705 array(
706 'id' => new external_value(PARAM_INT, 'Post id'),
707 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
708 'groupid' => new external_value(PARAM_INT, 'Group id'),
709 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
710 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
711 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
712 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
713 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
714 'parent' => new external_value(PARAM_INT, 'Parent id'),
715 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
716 'created' => new external_value(PARAM_INT, 'Creation time'),
717 'modified' => new external_value(PARAM_INT, 'Time modified'),
718 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
719 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
720 'message' => new external_value(PARAM_RAW, 'The post message'),
721 'messageformat' => new external_format_value('message'),
722 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
c8743f62 723 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
7c51b40a 724 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
14590070 725 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
7c51b40a
JL
726 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
727 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
728 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
729 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
730 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
731 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
28c2f2b2 732 'numreplies' => new external_value(PARAM_INT, 'The number of replies in the discussion'),
5f219cf1 733 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
0f3bbfd4
AN
734 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
735 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
736 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
565cccfa 737 'canlock' => new external_value(PARAM_BOOL, 'Can the user lock the discussion'),
7c51b40a
JL
738 ), 'post'
739 )
740 ),
741 'warnings' => new external_warnings()
742 )
743 );
744 }
745
1a9c60e9
MG
746 /**
747 * Describes the parameters for get_forum_discussions.
748 *
749 * @return external_function_parameters
750 * @since Moodle 3.7
751 */
752 public static function get_forum_discussions_parameters() {
753 return new external_function_parameters (
754 array(
755 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
756 'sortorder' => new external_value(PARAM_INT,
757 'sort by this element: numreplies, , created or timemodified', VALUE_DEFAULT, -1),
758 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
759 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
760 'groupid' => new external_value(PARAM_INT, 'group id', VALUE_DEFAULT, 0),
761 )
762 );
763 }
764
765 /**
766 * Returns a list of forum discussions optionally sorted and paginated.
767 *
768 * @param int $forumid the forum instance id
769 * @param int $sortorder The sort order
770 * @param int $page page number
771 * @param int $perpage items per page
772 * @param int $groupid the user course group
773 *
774 *
775 * @return array the forum discussion details including warnings
776 * @since Moodle 3.7
777 */
778 public static function get_forum_discussions(int $forumid, ?int $sortorder = -1, ?int $page = -1,
779 ?int $perpage = 0, ?int $groupid = 0) {
780
781 global $CFG, $DB, $USER;
782
783 require_once($CFG->dirroot . "/mod/forum/lib.php");
784
785 $warnings = array();
786 $discussions = array();
787
788 $params = self::validate_parameters(self::get_forum_discussions_parameters(),
789 array(
790 'forumid' => $forumid,
791 'sortorder' => $sortorder,
792 'page' => $page,
793 'perpage' => $perpage,
794 'groupid' => $groupid
795 )
796 );
797
798 // Compact/extract functions are not recommended.
799 $forumid = $params['forumid'];
800 $sortorder = $params['sortorder'];
801 $page = $params['page'];
802 $perpage = $params['perpage'];
803 $groupid = $params['groupid'];
804
805 $vaultfactory = \mod_forum\local\container::get_vault_factory();
806 $discussionlistvault = $vaultfactory->get_discussions_in_forum_vault();
807
808 $sortallowedvalues = array(
809 $discussionlistvault::SORTORDER_LASTPOST_DESC,
810 $discussionlistvault::SORTORDER_LASTPOST_ASC,
811 $discussionlistvault::SORTORDER_CREATED_DESC,
812 $discussionlistvault::SORTORDER_CREATED_ASC,
813 $discussionlistvault::SORTORDER_REPLIES_DESC,
814 $discussionlistvault::SORTORDER_REPLIES_ASC
815 );
816
817 // If sortorder not defined set a default one.
818 if ($sortorder == -1) {
819 $sortorder = $discussionlistvault::SORTORDER_LASTPOST_DESC;
820 }
821
822 if (!in_array($sortorder, $sortallowedvalues)) {
823 throw new invalid_parameter_exception('Invalid value for sortorder parameter (value: ' . $sortorder . '),' .
824 ' allowed values are: ' . implode(',', $sortallowedvalues));
825 }
826
827 $managerfactory = \mod_forum\local\container::get_manager_factory();
828 $urlfactory = \mod_forum\local\container::get_url_factory();
829 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
830
831 $forumvault = $vaultfactory->get_forum_vault();
832 $forum = $forumvault->get_from_id($forumid);
833 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
834 $forumrecord = $forumdatamapper->to_legacy_object($forum);
835
836 $capabilitymanager = $managerfactory->get_capability_manager($forum);
837
838 $course = $DB->get_record('course', array('id' => $forum->get_course_id()), '*', MUST_EXIST);
839 $cm = get_coursemodule_from_instance('forum', $forum->get_id(), $course->id, false, MUST_EXIST);
840
841 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
842 $modcontext = context_module::instance($cm->id);
843 self::validate_context($modcontext);
844
845 $canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($USER);
846
847 // Check they have the view forum capability.
848 if (!$capabilitymanager->can_view_discussions($USER)) {
849 throw new moodle_exception('noviewdiscussionspermission', 'forum');
850 }
851
852 $alldiscussions = get_discussions($forum, $USER, $groupid, $sortorder, $page, $perpage);
853
854 if ($alldiscussions) {
855 $discussionids = array_keys($alldiscussions);
856
857 $postvault = $vaultfactory->get_post_vault();
858 // Return the reply count for each discussion in a given forum.
859 $replies = $postvault->get_reply_count_for_discussion_ids($USER, $discussionids, $canseeanyprivatereply);
860 // Return the first post for each discussion in a given forum.
861 $firstposts = $postvault->get_first_post_for_discussion_ids($discussionids);
862
863 // Get the unreads array, this takes a forum id and returns data for all discussions.
864 $unreads = array();
865 if ($cantrack = forum_tp_can_track_forums($forumrecord)) {
866 if ($forumtracked = forum_tp_is_tracked($forumrecord)) {
867 $unreads = $postvault->get_unread_count_for_discussion_ids($USER, $discussionids, $canseeanyprivatereply);
868 }
869 }
870
871 $canlock = $capabilitymanager->can_manage_forum($USER);
872
121ccf15
MG
873 $usercontext = context_user::instance($USER->id);
874 $ufservice = core_favourites\service_factory::get_service_for_user_context($usercontext);
875
876 $canfavourite = has_capability('mod/forum:cantogglefavourite', $modcontext, $USER);
877
1a9c60e9
MG
878 foreach ($alldiscussions as $discussionsummary) {
879 $discussion = $discussionsummary->get_discussion();
880 $firstpostauthor = $discussionsummary->get_first_post_author();
881 $latestpostauthor = $discussionsummary->get_latest_post_author();
882
883 // This function checks for qanda forums.
884 $canviewdiscussion = $capabilitymanager->can_view_discussion($USER, $discussion);
885 if (!$canviewdiscussion) {
886 $warning = array();
887 // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
888 $warning['item'] = 'post';
889 $warning['itemid'] = $discussion->get_id();
890 $warning['warningcode'] = '1';
891 $warning['message'] = 'You can\'t see this discussion';
892 $warnings[] = $warning;
893 continue;
894 }
895
896 $discussionobject = $firstposts[$discussion->get_first_post_id()];
897 $discussionobject->groupid = $discussion->get_group_id();
898 $discussionobject->timemodified = $discussion->get_time_modified();
899 $discussionobject->usermodified = $discussion->get_user_modified();
900 $discussionobject->timestart = $discussion->get_time_start();
901 $discussionobject->timeend = $discussion->get_time_end();
902 $discussionobject->pinned = $discussion->is_pinned();
903
904 $discussionobject->numunread = 0;
905 if ($cantrack && $forumtracked) {
906 if (isset($unreads[$discussion->get_id()])) {
907 $discussionobject->numunread = (int) $unreads[$discussion->get_id()];
908 }
909 }
910
911 $discussionobject->numreplies = 0;
912 if (!empty($replies[$discussion->get_id()])) {
913 $discussionobject->numreplies = (int) $replies[$discussion->get_id()];
914 }
915
916 $discussionobject->name = external_format_string($discussion->get_name(), $modcontext->id);
917 $discussionobject->subject = external_format_string($discussionobject->subject, $modcontext->id);
918 // Rewrite embedded images URLs.
919 list($discussionobject->message, $discussionobject->messageformat) =
920 external_format_text($discussionobject->message, $discussionobject->messageformat,
921 $modcontext->id, 'mod_forum', 'post', $discussionobject->id);
922
923 // List attachments.
924 if (!empty($discussionobject->attachment)) {
925 $discussionobject->attachments = external_util::get_area_files($modcontext->id, 'mod_forum',
926 'attachment', $discussionobject->id);
927 }
928 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post',
929 $discussionobject->id);
930 if (!empty($messageinlinefiles)) {
931 $discussionobject->messageinlinefiles = $messageinlinefiles;
932 }
933
934 $discussionobject->locked = $forum->is_discussion_locked($discussion);
935 $discussionobject->canlock = $canlock;
121ccf15
MG
936 $discussionobject->starred = !empty($ufservice) ? $ufservice->favourite_exists('mod_forum', 'discussions',
937 $discussion->get_id(), $modcontext) : false;
1a9c60e9 938 $discussionobject->canreply = $capabilitymanager->can_post_in_discussion($USER, $discussion);
121ccf15 939 $discussionobject->canfavourite = $canfavourite;
1a9c60e9
MG
940
941 if (forum_is_author_hidden($discussionobject, $forumrecord)) {
942 $discussionobject->userid = null;
943 $discussionobject->userfullname = null;
944 $discussionobject->userpictureurl = null;
945
946 $discussionobject->usermodified = null;
947 $discussionobject->usermodifiedfullname = null;
948 $discussionobject->usermodifiedpictureurl = null;
949
950 } else {
951 $discussionobject->userfullname = $firstpostauthor->get_full_name();
952 $discussionobject->userpictureurl = $urlfactory->get_author_profile_image_url($firstpostauthor)
953 ->out(false);
954
955 $discussionobject->usermodifiedfullname = $latestpostauthor->get_full_name();
956 $discussionobject->usermodifiedpictureurl = $urlfactory->get_author_profile_image_url($latestpostauthor)
957 ->out(false);
958 }
959
960 $discussions[] = (array) $discussionobject;
961 }
962 }
963 $result = array();
964 $result['discussions'] = $discussions;
965 $result['warnings'] = $warnings;
966
967 return $result;
968 }
969
970 /**
971 * Describes the get_forum_discussions return value.
972 *
973 * @return external_single_structure
974 * @since Moodle 3.7
975 */
976 public static function get_forum_discussions_returns() {
977 return new external_single_structure(
978 array(
979 'discussions' => new external_multiple_structure(
980 new external_single_structure(
981 array(
982 'id' => new external_value(PARAM_INT, 'Post id'),
983 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
984 'groupid' => new external_value(PARAM_INT, 'Group id'),
985 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
986 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
987 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
988 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
989 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
990 'parent' => new external_value(PARAM_INT, 'Parent id'),
991 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
992 'created' => new external_value(PARAM_INT, 'Creation time'),
993 'modified' => new external_value(PARAM_INT, 'Time modified'),
994 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
995 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
996 'message' => new external_value(PARAM_RAW, 'The post message'),
997 'messageformat' => new external_format_value('message'),
998 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
999 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
1000 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
1001 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
1002 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
1003 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
1004 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
1005 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
1006 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
1007 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
1008 'numreplies' => new external_value(PARAM_INT, 'The number of replies in the discussion'),
1009 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
1010 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
1011 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
121ccf15 1012 'starred' => new external_value(PARAM_BOOL, 'Is the discussion starred'),
1a9c60e9
MG
1013 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
1014 'canlock' => new external_value(PARAM_BOOL, 'Can the user lock the discussion'),
121ccf15 1015 'canfavourite' => new external_value(PARAM_BOOL, 'Can the user star the discussion'),
1a9c60e9
MG
1016 ), 'post'
1017 )
1018 ),
1019 'warnings' => new external_warnings()
1020 )
1021 );
1022 }
1023
4a1e44a1
JL
1024 /**
1025 * Returns description of method parameters
1026 *
1027 * @return external_function_parameters
1028 * @since Moodle 2.9
1029 */
1030 public static function view_forum_parameters() {
1031 return new external_function_parameters(
1032 array(
1033 'forumid' => new external_value(PARAM_INT, 'forum instance id')
1034 )
1035 );
1036 }
1037
1038 /**
1c2b7882 1039 * Trigger the course module viewed event and update the module completion status.
4a1e44a1
JL
1040 *
1041 * @param int $forumid the forum instance id
1042 * @return array of warnings and status result
1043 * @since Moodle 2.9
1044 * @throws moodle_exception
1045 */
1046 public static function view_forum($forumid) {
1047 global $DB, $CFG;
1048 require_once($CFG->dirroot . "/mod/forum/lib.php");
1049
1050 $params = self::validate_parameters(self::view_forum_parameters(),
1051 array(
1052 'forumid' => $forumid
1053 ));
1054 $warnings = array();
1055
1056 // Request and permission validation.
ca7e2fc2 1057 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
4a1e44a1
JL
1058 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1059
1060 $context = context_module::instance($cm->id);
1061 self::validate_context($context);
1062
ea2fa324
JL
1063 require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
1064
4a1e44a1
JL
1065 // Call the forum/lib API.
1066 forum_view($forum, $course, $cm, $context);
1067
1068 $result = array();
1069 $result['status'] = true;
1070 $result['warnings'] = $warnings;
1071 return $result;
1072 }
1073
1074 /**
1075 * Returns description of method result value
1076 *
1077 * @return external_description
1078 * @since Moodle 2.9
1079 */
1080 public static function view_forum_returns() {
1081 return new external_single_structure(
1082 array(
1083 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1084 'warnings' => new external_warnings()
1085 )
1086 );
1087 }
1088
a3c315dd
JL
1089 /**
1090 * Returns description of method parameters
1091 *
1092 * @return external_function_parameters
1093 * @since Moodle 2.9
1094 */
1095 public static function view_forum_discussion_parameters() {
1096 return new external_function_parameters(
1097 array(
1098 'discussionid' => new external_value(PARAM_INT, 'discussion id')
1099 )
1100 );
1101 }
1102
1103 /**
1c2b7882 1104 * Trigger the discussion viewed event.
a3c315dd
JL
1105 *
1106 * @param int $discussionid the discussion id
1107 * @return array of warnings and status result
1108 * @since Moodle 2.9
1109 * @throws moodle_exception
1110 */
1111 public static function view_forum_discussion($discussionid) {
db3c9ff8 1112 global $DB, $CFG, $USER;
a3c315dd
JL
1113 require_once($CFG->dirroot . "/mod/forum/lib.php");
1114
1115 $params = self::validate_parameters(self::view_forum_discussion_parameters(),
1116 array(
1117 'discussionid' => $discussionid
1118 ));
1119 $warnings = array();
1120
1121 $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
1122 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
1123 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1124
1125 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
1126 $modcontext = context_module::instance($cm->id);
1127 self::validate_context($modcontext);
1128
ea2fa324
JL
1129 require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
1130
a3c315dd
JL
1131 // Call the forum/lib API.
1132 forum_discussion_view($modcontext, $forum, $discussion);
1133
db3c9ff8
PFO
1134 // Mark as read if required.
1135 if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
1136 forum_tp_mark_discussion_read($USER, $discussion->id);
1137 }
1138
a3c315dd
JL
1139 $result = array();
1140 $result['status'] = true;
1141 $result['warnings'] = $warnings;
1142 return $result;
1143 }
1144
1145 /**
1146 * Returns description of method result value
1147 *
1148 * @return external_description
1149 * @since Moodle 2.9
1150 */
1151 public static function view_forum_discussion_returns() {
1152 return new external_single_structure(
1153 array(
1154 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1155 'warnings' => new external_warnings()
1156 )
1157 );
1158 }
1159
50a20317
JL
1160 /**
1161 * Returns description of method parameters
1162 *
1163 * @return external_function_parameters
1164 * @since Moodle 3.0
1165 */
1166 public static function add_discussion_post_parameters() {
1167 return new external_function_parameters(
1168 array(
1169 'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
1170 (can be the initial discussion post'),
1171 'subject' => new external_value(PARAM_TEXT, 'new post subject'),
1172 'message' => new external_value(PARAM_RAW, 'new post message (only html format allowed)'),
1173 'options' => new external_multiple_structure (
1174 new external_single_structure(
1175 array(
1176 'name' => new external_value(PARAM_ALPHANUM,
1177 'The allowed keys (value format) are:
1178 discussionsubscribe (bool); subscribe to the discussion?, default to true
bc4c7337 1179 private (bool); make this reply private to the author of the parent post, default to false.
48143990 1180 inlineattachmentsid (int); the draft file area id for inline attachments
e881c4f5 1181 attachmentsid (int); the draft file area id for attachments
50a20317
JL
1182 '),
1183 'value' => new external_value(PARAM_RAW, 'the value of the option,
1184 this param is validated in the external function.'
1185 )
1186 )
1187 ), 'Options', VALUE_DEFAULT, array())
1188 )
1189 );
1190 }
1191
1192 /**
1193 * Create new posts into an existing discussion.
1194 *
1195 * @param int $postid the post id we are going to reply to
1196 * @param string $subject new post subject
1197 * @param string $message new post message (only html format allowed)
1198 * @param array $options optional settings
1199 * @return array of warnings and the new post id
1200 * @since Moodle 3.0
1201 * @throws moodle_exception
1202 */
1203 public static function add_discussion_post($postid, $subject, $message, $options = array()) {
a0c9b6af 1204 global $CFG, $USER;
50a20317
JL
1205 require_once($CFG->dirroot . "/mod/forum/lib.php");
1206
9b4f09ba
P
1207 // Get all the factories that are required.
1208 $vaultfactory = mod_forum\local\container::get_vault_factory();
1209 $entityfactory = mod_forum\local\container::get_entity_factory();
1210 $datamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1211 $managerfactory = mod_forum\local\container::get_manager_factory();
1212 $discussionvault = $vaultfactory->get_discussion_vault();
1213 $forumvault = $vaultfactory->get_forum_vault();
1214 $discussiondatamapper = $datamapperfactory->get_discussion_data_mapper();
1215 $forumdatamapper = $datamapperfactory->get_forum_data_mapper();
1216
50a20317 1217 $params = self::validate_parameters(self::add_discussion_post_parameters(),
609a1073
JL
1218 array(
1219 'postid' => $postid,
1220 'subject' => $subject,
1221 'message' => $message,
1222 'options' => $options
1223 )
1224 );
bc4c7337 1225
609a1073
JL
1226 $warnings = array();
1227
1228 if (!$parent = forum_get_post_full($params['postid'])) {
1229 throw new moodle_exception('invalidparentpostid', 'forum');
1230 }
1231
9b4f09ba 1232 if (!$discussion = $discussionvault->get_from_id($parent->discussion)) {
609a1073
JL
1233 throw new moodle_exception('notpartofdiscussion', 'forum');
1234 }
1235
1236 // Request and permission validation.
9b4f09ba
P
1237 $forum = $forumvault->get_from_id($discussion->get_forum_id());
1238 $capabilitymanager = $managerfactory->get_capability_manager($forum);
1239 $course = $forum->get_course_record();
1240 $cm = $forum->get_course_module_record();
609a1073 1241
9b4f09ba
P
1242 $discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
1243 $forumrecord = $forumdatamapper->to_legacy_object($forum);
609a1073
JL
1244 $context = context_module::instance($cm->id);
1245 self::validate_context($context);
1246
50a20317
JL
1247 // Validate options.
1248 $options = array(
41182118 1249 'discussionsubscribe' => true,
bc4c7337 1250 'private' => false,
48143990 1251 'inlineattachmentsid' => 0,
e881c4f5 1252 'attachmentsid' => null
50a20317
JL
1253 );
1254 foreach ($params['options'] as $option) {
1255 $name = trim($option['name']);
1256 switch ($name) {
1257 case 'discussionsubscribe':
1258 $value = clean_param($option['value'], PARAM_BOOL);
1259 break;
bc4c7337
AN
1260 case 'private':
1261 $value = clean_param($option['value'], PARAM_BOOL);
1262 break;
48143990 1263 case 'inlineattachmentsid':
41182118
BK
1264 $value = clean_param($option['value'], PARAM_INT);
1265 break;
e881c4f5
BK
1266 case 'attachmentsid':
1267 $value = clean_param($option['value'], PARAM_INT);
609a1073
JL
1268 // Ensure that the user has permissions to create attachments.
1269 if (!has_capability('mod/forum:createattachment', $context)) {
1270 $value = 0;
1271 }
e881c4f5 1272 break;
50a20317
JL
1273 default:
1274 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1275 }
1276 $options[$name] = $value;
1277 }
1278
9b4f09ba 1279 if (!$capabilitymanager->can_post_in_discussion($USER, $discussion)) {
50a20317
JL
1280 throw new moodle_exception('nopostforum', 'forum');
1281 }
1282
9b4f09ba 1283 $thresholdwarning = forum_check_throttling($forumrecord, $cm);
50a20317
JL
1284 forum_check_blocking_threshold($thresholdwarning);
1285
1286 // Create the post.
1287 $post = new stdClass();
9b4f09ba 1288 $post->discussion = $discussion->get_id();
50a20317
JL
1289 $post->parent = $parent->id;
1290 $post->subject = $params['subject'];
1291 $post->message = $params['message'];
1292 $post->messageformat = FORMAT_HTML; // Force formatting for now.
1293 $post->messagetrust = trusttext_trusted($context);
48143990 1294 $post->itemid = $options['inlineattachmentsid'];
3e95e09b 1295 $post->attachments = $options['attachmentsid'];
bc4c7337 1296 $post->isprivatereply = $options['private'];
3e95e09b 1297 $post->deleted = 0;
e881c4f5
BK
1298 $fakemform = $post->attachments;
1299 if ($postid = forum_add_new_post($post, $fakemform)) {
50a20317
JL
1300
1301 $post->id = $postid;
1302
1303 // Trigger events and completion.
1304 $params = array(
1305 'context' => $context,
1306 'objectid' => $post->id,
1307 'other' => array(
9b4f09ba
P
1308 'discussionid' => $discussion->get_id(),
1309 'forumid' => $forum->get_id(),
1310 'forumtype' => $forum->get_type(),
50a20317
JL
1311 )
1312 );
1313 $event = \mod_forum\event\post_created::create($params);
1314 $event->add_record_snapshot('forum_posts', $post);
9b4f09ba 1315 $event->add_record_snapshot('forum_discussions', $discussionrecord);
50a20317
JL
1316 $event->trigger();
1317
1318 // Update completion state.
1319 $completion = new completion_info($course);
1320 if ($completion->is_enabled($cm) &&
9b4f09ba 1321 ($forum->get_completion_replies() || $forum->get_completion_posts())) {
50a20317
JL
1322 $completion->update_state($cm, COMPLETION_COMPLETE);
1323 }
1324
1325 $settings = new stdClass();
1326 $settings->discussionsubscribe = $options['discussionsubscribe'];
9b4f09ba 1327 forum_post_subscription($settings, $forumrecord, $discussionrecord);
50a20317
JL
1328 } else {
1329 throw new moodle_exception('couldnotadd', 'forum');
1330 }
1331
9b4f09ba
P
1332 $builderfactory = \mod_forum\local\container::get_builder_factory();
1333 $exportedpostsbuilder = $builderfactory->get_exported_posts_builder();
1334 $postentity = $entityfactory->get_post_from_stdClass($post);
1335 $exportedposts = $exportedpostsbuilder->build($USER, [$forum], [$discussion], [$postentity]);
1336 $exportedpost = $exportedposts[0];
1337
a0c9b6af
P
1338 $message = [];
1339 $message[] = [
1340 'type' => 'success',
1341 'message' => get_string("postaddedsuccess", "forum")
1342 ];
1343
1344 $message[] = [
1345 'type' => 'success',
1346 'message' => get_string("postaddedtimeleft", "forum", format_time($CFG->maxeditingtime))
1347 ];
1348
50a20317
JL
1349 $result = array();
1350 $result['postid'] = $postid;
1351 $result['warnings'] = $warnings;
9b4f09ba 1352 $result['post'] = $exportedpost;
a0c9b6af 1353 $result['messages'] = $message;
50a20317
JL
1354 return $result;
1355 }
1356
1357 /**
1358 * Returns description of method result value
1359 *
1360 * @return external_description
1361 * @since Moodle 3.0
1362 */
1363 public static function add_discussion_post_returns() {
1364 return new external_single_structure(
1365 array(
1366 'postid' => new external_value(PARAM_INT, 'new post id'),
9b4f09ba 1367 'warnings' => new external_warnings(),
a0c9b6af
P
1368 'post' => post_exporter::get_read_structure(),
1369 'messages' => new external_multiple_structure(
1370 new external_single_structure(
1371 array(
1372 'type' => new external_value(PARAM_TEXT, "The classification to be used in the client side", VALUE_REQUIRED),
1373 'message' => new external_value(PARAM_TEXT,'untranslated english message to explain the warning', VALUE_REQUIRED)
1374 ), 'Messages'), 'list of warnings', VALUE_OPTIONAL
1375 ),
1376 //'alertmessage' => new external_value(PARAM_RAW, 'Success message to be displayed to the user.'),
50a20317
JL
1377 )
1378 );
1379 }
1380
309e8698
P
1381 /**
1382 * Toggle the favouriting value for the discussion provided
1383 *
309e8698
P
1384 * @param int $discussionid The discussion we need to favourite
1385 * @param bool $targetstate The state of the favourite value
1386 * @return array The exported discussion
1387 */
8885cd57 1388 public static function toggle_favourite_state($discussionid, $targetstate) {
99bda8a7
P
1389 global $DB, $PAGE, $USER;
1390
1391 $params = self::validate_parameters(self::toggle_favourite_state_parameters(), [
99bda8a7
P
1392 'discussionid' => $discussionid,
1393 'targetstate' => $targetstate
1394 ]);
1395
1396 $vaultfactory = mod_forum\local\container::get_vault_factory();
8885cd57
P
1397 // Get the discussion vault and the corresponding discussion entity.
1398 $discussionvault = $vaultfactory->get_discussion_vault();
1399 $discussion = $discussionvault->get_from_id($params['discussionid']);
1400
99bda8a7 1401 $forumvault = $vaultfactory->get_forum_vault();
8885cd57 1402 $forum = $forumvault->get_from_id($discussion->get_forum_id());
99bda8a7 1403 $forumcontext = $forum->get_context();
99bda8a7
P
1404 self::validate_context($forumcontext);
1405
1406 $managerfactory = mod_forum\local\container::get_manager_factory();
1407 $capabilitymanager = $managerfactory->get_capability_manager($forum);
1408
99bda8a7
P
1409 // Does the user have the ability to favourite the discussion?
1410 if (!$capabilitymanager->can_favourite_discussion($USER, $discussion)) {
1411 throw new moodle_exception('cannotfavourite', 'forum');
1412 }
d3cac88d 1413 $usercontext = context_user::instance($USER->id);
99bda8a7
P
1414 $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
1415 $isfavourited = $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
1416
1417 $favouritefunction = $targetstate ? 'create_favourite' : 'delete_favourite';
1418 if ($isfavourited != (bool) $params['targetstate']) {
1419 $ufservice->{$favouritefunction}('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
1420 }
1421
1422 $exporterfactory = mod_forum\local\container::get_exporter_factory();
13cd05ac
P
1423 $builder = mod_forum\local\container::get_builder_factory()->get_exported_discussion_builder();
1424 $favourited = ($builder->is_favourited($discussion, $forumcontext, $USER) ? [$discussion->get_id()] : []);
1425 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion, [], $favourited);
99bda8a7
P
1426 return $exporter->export($PAGE->get_renderer('mod_forum'));
1427 }
1428
1429 /**
1430 * Returns description of method result value
1431 *
1432 * @return external_description
1433 * @since Moodle 3.0
1434 */
1435 public static function toggle_favourite_state_returns() {
1436 return discussion_exporter::get_read_structure();
1437 }
1438
309e8698
P
1439 /**
1440 * Defines the parameters for the toggle_favourite_state method
1441 *
1442 * @return external_function_parameters
1443 */
897ac0de 1444 public static function toggle_favourite_state_parameters() {
99bda8a7
P
1445 return new external_function_parameters(
1446 [
99bda8a7
P
1447 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1448 'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1449 ]
1450 );
1451 }
1452
7ab43ac8
JL
1453 /**
1454 * Returns description of method parameters
1455 *
1456 * @return external_function_parameters
1457 * @since Moodle 3.0
1458 */
1459 public static function add_discussion_parameters() {
1460 return new external_function_parameters(
1461 array(
7267daa8
AN
1462 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1463 'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
1464 'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
aa9059b9 1465 'groupid' => new external_value(PARAM_INT, 'The group, default to 0', VALUE_DEFAULT, 0),
7ab43ac8
JL
1466 'options' => new external_multiple_structure (
1467 new external_single_structure(
1468 array(
1469 'name' => new external_value(PARAM_ALPHANUM,
1470 'The allowed keys (value format) are:
1471 discussionsubscribe (bool); subscribe to the discussion?, default to true
5f219cf1 1472 discussionpinned (bool); is the discussion pinned, default to false
48143990 1473 inlineattachmentsid (int); the draft file area id for inline attachments
e881c4f5 1474 attachmentsid (int); the draft file area id for attachments
7ab43ac8 1475 '),
7267daa8
AN
1476 'value' => new external_value(PARAM_RAW, 'The value of the option,
1477 This param is validated in the external function.'
7ab43ac8
JL
1478 )
1479 )
1480 ), 'Options', VALUE_DEFAULT, array())
1481 )
1482 );
1483 }
1484
1485 /**
1486 * Add a new discussion into an existing forum.
1487 *
1488 * @param int $forumid the forum instance id
1489 * @param string $subject new discussion subject
1490 * @param string $message new discussion message (only html format allowed)
1491 * @param int $groupid the user course group
1492 * @param array $options optional settings
1493 * @return array of warnings and the new discussion id
1494 * @since Moodle 3.0
1495 * @throws moodle_exception
1496 */
aa9059b9 1497 public static function add_discussion($forumid, $subject, $message, $groupid = 0, $options = array()) {
7ab43ac8
JL
1498 global $DB, $CFG;
1499 require_once($CFG->dirroot . "/mod/forum/lib.php");
1500
1501 $params = self::validate_parameters(self::add_discussion_parameters(),
1502 array(
1503 'forumid' => $forumid,
1504 'subject' => $subject,
1505 'message' => $message,
1506 'groupid' => $groupid,
1507 'options' => $options
1508 ));
609a1073
JL
1509
1510 $warnings = array();
1511
1512 // Request and permission validation.
1513 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1514 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1515
1516 $context = context_module::instance($cm->id);
1517 self::validate_context($context);
1518
7ab43ac8
JL
1519 // Validate options.
1520 $options = array(
5f219cf1 1521 'discussionsubscribe' => true,
41182118 1522 'discussionpinned' => false,
48143990 1523 'inlineattachmentsid' => 0,
e881c4f5 1524 'attachmentsid' => null
7ab43ac8
JL
1525 );
1526 foreach ($params['options'] as $option) {
1527 $name = trim($option['name']);
1528 switch ($name) {
1529 case 'discussionsubscribe':
1530 $value = clean_param($option['value'], PARAM_BOOL);
1531 break;
5f219cf1
BK
1532 case 'discussionpinned':
1533 $value = clean_param($option['value'], PARAM_BOOL);
1534 break;
48143990 1535 case 'inlineattachmentsid':
41182118
BK
1536 $value = clean_param($option['value'], PARAM_INT);
1537 break;
e881c4f5
BK
1538 case 'attachmentsid':
1539 $value = clean_param($option['value'], PARAM_INT);
609a1073
JL
1540 // Ensure that the user has permissions to create attachments.
1541 if (!has_capability('mod/forum:createattachment', $context)) {
1542 $value = 0;
1543 }
e881c4f5 1544 break;
7ab43ac8
JL
1545 default:
1546 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1547 }
1548 $options[$name] = $value;
1549 }
1550
7ab43ac8
JL
1551 // Normalize group.
1552 if (!groups_get_activity_groupmode($cm)) {
1553 // Groups not supported, force to -1.
1554 $groupid = -1;
1555 } else {
1556 // Check if we receive the default or and empty value for groupid,
1557 // in this case, get the group for the user in the activity.
aa9059b9 1558 if (empty($params['groupid'])) {
7ab43ac8
JL
1559 $groupid = groups_get_activity_group($cm);
1560 } else {
1561 // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
1562 $groupid = $params['groupid'];
1563 }
1564 }
1565
1566 if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
1567 throw new moodle_exception('cannotcreatediscussion', 'forum');
1568 }
1569
1570 $thresholdwarning = forum_check_throttling($forum, $cm);
1571 forum_check_blocking_threshold($thresholdwarning);
1572
1573 // Create the discussion.
1574 $discussion = new stdClass();
1575 $discussion->course = $course->id;
1576 $discussion->forum = $forum->id;
1577 $discussion->message = $params['message'];
1578 $discussion->messageformat = FORMAT_HTML; // Force formatting for now.
1579 $discussion->messagetrust = trusttext_trusted($context);
48143990 1580 $discussion->itemid = $options['inlineattachmentsid'];
7ab43ac8
JL
1581 $discussion->groupid = $groupid;
1582 $discussion->mailnow = 0;
1583 $discussion->subject = $params['subject'];
1584 $discussion->name = $discussion->subject;
1585 $discussion->timestart = 0;
1586 $discussion->timeend = 0;
2893812e 1587 $discussion->timelocked = 0;
e881c4f5
BK
1588 $discussion->attachments = $options['attachmentsid'];
1589
5f219cf1
BK
1590 if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
1591 $discussion->pinned = FORUM_DISCUSSION_PINNED;
1592 } else {
1593 $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
1594 }
e881c4f5
BK
1595 $fakemform = $options['attachmentsid'];
1596 if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
7ab43ac8
JL
1597
1598 $discussion->id = $discussionid;
1599
1600 // Trigger events and completion.
1601
1602 $params = array(
1603 'context' => $context,
1604 'objectid' => $discussion->id,
1605 'other' => array(
1606 'forumid' => $forum->id,
1607 )
1608 );
1609 $event = \mod_forum\event\discussion_created::create($params);
1610 $event->add_record_snapshot('forum_discussions', $discussion);
1611 $event->trigger();
1612
1613 $completion = new completion_info($course);
1614 if ($completion->is_enabled($cm) &&
1615 ($forum->completiondiscussions || $forum->completionposts)) {
1616 $completion->update_state($cm, COMPLETION_COMPLETE);
1617 }
1618
1619 $settings = new stdClass();
1620 $settings->discussionsubscribe = $options['discussionsubscribe'];
1621 forum_post_subscription($settings, $forum, $discussion);
1622 } else {
1623 throw new moodle_exception('couldnotadd', 'forum');
1624 }
1625
1626 $result = array();
1627 $result['discussionid'] = $discussionid;
1628 $result['warnings'] = $warnings;
1629 return $result;
1630 }
1631
1632 /**
1633 * Returns description of method result value
1634 *
1635 * @return external_description
1636 * @since Moodle 3.0
1637 */
1638 public static function add_discussion_returns() {
1639 return new external_single_structure(
1640 array(
7267daa8 1641 'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
7ab43ac8
JL
1642 'warnings' => new external_warnings()
1643 )
1644 );
1645 }
1646
04cd8ae3
JL
1647 /**
1648 * Returns description of method parameters
1649 *
1650 * @return external_function_parameters
1651 * @since Moodle 3.1
1652 */
1653 public static function can_add_discussion_parameters() {
1654 return new external_function_parameters(
1655 array(
1656 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1657 'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1658 Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1659 )
1660 );
1661 }
1662
1663 /**
1664 * Check if the current user can add discussions in the given forum (and optionally for the given group).
1665 *
1666 * @param int $forumid the forum instance id
1667 * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1668 * @return array of warnings and the status (true if the user can add discussions)
1669 * @since Moodle 3.1
1670 * @throws moodle_exception
1671 */
1672 public static function can_add_discussion($forumid, $groupid = null) {
1673 global $DB, $CFG;
1674 require_once($CFG->dirroot . "/mod/forum/lib.php");
1675
1676 $params = self::validate_parameters(self::can_add_discussion_parameters(),
1677 array(
1678 'forumid' => $forumid,
1679 'groupid' => $groupid,
1680 ));
1681 $warnings = array();
1682
1683 // Request and permission validation.
1684 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1685 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1686
1687 $context = context_module::instance($cm->id);
1688 self::validate_context($context);
1689
1690 $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1691
1692 $result = array();
1693 $result['status'] = $status;
581e75bf
JL
1694 $result['canpindiscussions'] = has_capability('mod/forum:pindiscussions', $context);
1695 $result['cancreateattachment'] = forum_can_create_attachment($forum, $context);
04cd8ae3
JL
1696 $result['warnings'] = $warnings;
1697 return $result;
1698 }
1699
1700 /**
1701 * Returns description of method result value
1702 *
1703 * @return external_description
1704 * @since Moodle 3.1
1705 */
1706 public static function can_add_discussion_returns() {
1707 return new external_single_structure(
1708 array(
1709 'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
581e75bf
JL
1710 'canpindiscussions' => new external_value(PARAM_BOOL, 'True if the user can pin discussions, false otherwise.',
1711 VALUE_OPTIONAL),
1712 'cancreateattachment' => new external_value(PARAM_BOOL, 'True if the user can add attachments, false otherwise.',
1713 VALUE_OPTIONAL),
04cd8ae3
JL
1714 'warnings' => new external_warnings()
1715 )
1716 );
1717 }
1718
4daa0d08
JL
1719 /**
1720 * Describes the parameters for get_forum_access_information.
1721 *
1722 * @return external_external_function_parameters
1723 * @since Moodle 3.7
1724 */
1725 public static function get_forum_access_information_parameters() {
1726 return new external_function_parameters (
1727 array(
1728 'forumid' => new external_value(PARAM_INT, 'Forum instance id.')
1729 )
1730 );
1731 }
1732
1733 /**
1734 * Return access information for a given forum.
1735 *
1736 * @param int $forumid forum instance id
1737 * @return array of warnings and the access information
1738 * @since Moodle 3.7
1739 * @throws moodle_exception
1740 */
1741 public static function get_forum_access_information($forumid) {
1742 global $DB;
1743
1744 $params = self::validate_parameters(self::get_forum_access_information_parameters(), array('forumid' => $forumid));
1745
1746 // Request and permission validation.
1747 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1748 $cm = get_coursemodule_from_instance('forum', $forum->id);
1749
1750 $context = context_module::instance($cm->id);
1751 self::validate_context($context);
1752
1753 $result = array();
1754 // Return all the available capabilities.
1755 $capabilities = load_capability_def('mod_forum');
1756 foreach ($capabilities as $capname => $capdata) {
1757 // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1758 $field = 'can' . str_replace('mod/forum:', '', $capname);
1759 $result[$field] = has_capability($capname, $context);
1760 }
1761
1762 $result['warnings'] = array();
1763 return $result;
1764 }
1765
1766 /**
1767 * Describes the get_forum_access_information return value.
1768 *
1769 * @return external_single_structure
1770 * @since Moodle 3.7
1771 */
1772 public static function get_forum_access_information_returns() {
1773
1774 $structure = array(
1775 'warnings' => new external_warnings()
1776 );
1777
1778 $capabilities = load_capability_def('mod_forum');
1779 foreach ($capabilities as $capname => $capdata) {
1780 // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1781 $field = 'can' . str_replace('mod/forum:', '', $capname);
1782 $structure[$field] = new external_value(PARAM_BOOL, 'Whether the user has the capability ' . $capname . ' allowed.',
1783 VALUE_OPTIONAL);
1784 }
1785
1786 return new external_single_structure($structure);
1787 }
2646e9d6
RW
1788
1789 /**
1790 * Set the subscription state.
1791 *
1792 * @param int $forumid
1793 * @param int $discussionid
1794 * @param bool $targetstate
1795 * @return \stdClass
1796 */
1797 public static function set_subscription_state($forumid, $discussionid, $targetstate) {
f30f46db 1798 global $PAGE, $USER;
2646e9d6
RW
1799
1800 $params = self::validate_parameters(self::set_subscription_state_parameters(), [
1801 'forumid' => $forumid,
1802 'discussionid' => $discussionid,
1803 'targetstate' => $targetstate
1804 ]);
1805
1806 $vaultfactory = mod_forum\local\container::get_vault_factory();
1807 $forumvault = $vaultfactory->get_forum_vault();
1808 $forum = $forumvault->get_from_id($params['forumid']);
2646e9d6 1809 $coursemodule = $forum->get_course_module_record();
96a49734 1810 $context = $forum->get_context();
2646e9d6 1811
96a49734 1812 self::validate_context($context);
2646e9d6 1813
2646e9d6
RW
1814 $discussionvault = $vaultfactory->get_discussion_vault();
1815 $discussion = $discussionvault->get_from_id($params['discussionid']);
1816 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1817
1818 $forumrecord = $legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
1819 $discussionrecord = $legacydatamapperfactory->get_discussion_data_mapper()->to_legacy_object($discussion);
1820
1821 if (!\mod_forum\subscriptions::is_subscribable($forumrecord)) {
1822 // Nothing to do. We won't actually output any content here though.
1823 throw new \moodle_exception('cannotsubscribe', 'mod_forum');
1824 }
1825
1826 $issubscribed = \mod_forum\subscriptions::is_subscribed(
1827 $USER->id,
1828 $forumrecord,
1829 $discussion->get_id(),
1830 $coursemodule
1831 );
1832
1833 // If the current state doesn't equal the desired state then update the current
1834 // state to the desired state.
1835 if ($issubscribed != (bool) $params['targetstate']) {
1836 if ($params['targetstate']) {
1837 \mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussionrecord, $context);
1838 } else {
1839 \mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussionrecord, $context);
1840 }
1841 }
1842
1843 $exporterfactory = mod_forum\local\container::get_exporter_factory();
1844 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1845 return $exporter->export($PAGE->get_renderer('mod_forum'));
1846 }
1847
1848 /**
1849 * Returns description of method parameters.
1850 *
1851 * @return external_function_parameters
1852 */
1853 public static function set_subscription_state_parameters() {
1854 return new external_function_parameters(
1855 [
1856 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1857 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1858 'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1859 ]
1860 );
1861 }
1862
1863 /**
1864 * Returns description of method result value.
1865 *
1866 * @return external_description
1867 */
1868 public static function set_subscription_state_returns() {
8885cd57 1869 return discussion_exporter::get_read_structure();
2646e9d6 1870 }
2893812e 1871
2893812e
P
1872 /**
1873 * Set the lock state.
1874 *
1875 * @param int $forumid
1876 * @param int $discussionid
1877 * @param string $targetstate
1878 * @return \stdClass
1879 */
1880 public static function set_lock_state($forumid, $discussionid, $targetstate) {
1881 global $DB, $PAGE, $USER;
1882
1883 $params = self::validate_parameters(self::set_lock_state_parameters(), [
1884 'forumid' => $forumid,
1885 'discussionid' => $discussionid,
1886 'targetstate' => $targetstate
1887 ]);
1888
1889 $vaultfactory = mod_forum\local\container::get_vault_factory();
1890 $forumvault = $vaultfactory->get_forum_vault();
1891 $forum = $forumvault->get_from_id($params['forumid']);
2893812e
P
1892
1893 $managerfactory = mod_forum\local\container::get_manager_factory();
1894 $capabilitymanager = $managerfactory->get_capability_manager($forum);
bdb4a87d
P
1895 if (!$capabilitymanager->can_manage_forum($USER)) {
1896 throw new moodle_exception('errorcannotlock', 'forum');
1897 }
1898
1899 // If the targetstate(currentstate) is not 0 then it should be set to the current time.
1900 $lockedvalue = $targetstate ? 0 : time();
1901 self::validate_context($forum->get_context());
1902
2893812e
P
1903 $discussionvault = $vaultfactory->get_discussion_vault();
1904 $discussion = $discussionvault->get_from_id($params['discussionid']);
2893812e 1905
c475fe41 1906 // If the current state doesn't equal the desired state then update the current.
2893812e 1907 // state to the desired state.
bdb4a87d
P
1908 $discussion->toggle_locked_state($lockedvalue);
1909 $response = $discussionvault->update_discussion($discussion);
1910 $discussion = !$response ? $response : $discussion;
a7260536 1911 $exporterfactory = mod_forum\local\container::get_exporter_factory();
1912 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1913 return $exporter->export($PAGE->get_renderer('mod_forum'));
1914 }
1915
cda9da99
P
1916 /**
1917 * Returns description of method parameters.
1918 *
1919 * @return external_function_parameters
1920 */
1921 public static function set_lock_state_parameters() {
1922 return new external_function_parameters(
1923 [
1924 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1925 'discussionid' => new external_value(PARAM_INT, 'The discussion to lock / unlock'),
1926 'targetstate' => new external_value(PARAM_INT, 'The timestamp for the lock state')
1927 ]
1928 );
1929 }
1930
1931 /**
1932 * Returns description of method result value.
1933 *
1934 * @return external_description
1935 */
1936 public static function set_lock_state_returns() {
1937 return new external_single_structure([
1938 'id' => new external_value(PARAM_INT, 'The discussion we are locking.'),
1939 'locked' => new external_value(PARAM_BOOL, 'The locked state of the discussion.'),
1940 'times' => new external_single_structure([
1941 'locked' => new external_value(PARAM_INT, 'The locked time of the discussion.'),
1942 ])
1943 ]);
1944 }
1945
a7260536 1946 /**
1947 * Set the pin state.
1948 *
a7260536 1949 * @param int $discussionid
1950 * @param bool $targetstate
1951 * @return \stdClass
1952 */
8885cd57 1953 public static function set_pin_state($discussionid, $targetstate) {
a7260536 1954 global $PAGE, $USER;
1955 $params = self::validate_parameters(self::set_pin_state_parameters(), [
a7260536 1956 'discussionid' => $discussionid,
1957 'targetstate' => $targetstate,
1958 ]);
1959 $vaultfactory = mod_forum\local\container::get_vault_factory();
309e8698 1960 $managerfactory = mod_forum\local\container::get_manager_factory();
a7260536 1961 $forumvault = $vaultfactory->get_forum_vault();
1962 $discussionvault = $vaultfactory->get_discussion_vault();
8885cd57
P
1963 $discussion = $discussionvault->get_from_id($params['discussionid']);
1964 $forum = $forumvault->get_from_id($discussion->get_forum_id());
309e8698 1965 $capabilitymanager = $managerfactory->get_capability_manager($forum);
a7260536 1966
1967 self::validate_context($forum->get_context());
24962ee1
P
1968
1969 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
309e8698 1970 if (!$capabilitymanager->can_pin_discussions($USER)) {
a7260536 1971 // Nothing to do. We won't actually output any content here though.
309e8698 1972 throw new \moodle_exception('cannotpindiscussions', 'mod_forum');
a7260536 1973 }
1974
a7260536 1975 $discussion->set_pinned($targetstate);
cda9da99 1976 $discussionvault->update_discussion($discussion);
2893812e
P
1977
1978 $exporterfactory = mod_forum\local\container::get_exporter_factory();
1979 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1980 return $exporter->export($PAGE->get_renderer('mod_forum'));
1981 }
1982
a7260536 1983 /**
1984 * Returns description of method parameters.
1985 *
1986 * @return external_function_parameters
1987 */
1988 public static function set_pin_state_parameters() {
1989 return new external_function_parameters(
1990 [
309e8698
P
1991 'discussionid' => new external_value(PARAM_INT, 'The discussion to pin or unpin', VALUE_REQUIRED,
1992 null, NULL_NOT_ALLOWED),
1993 'targetstate' => new external_value(PARAM_INT, 'The target state', VALUE_REQUIRED,
1994 null, NULL_NOT_ALLOWED),
a7260536 1995 ]
1996 );
1997 }
1998
a7260536 1999 /**
2000 * Returns description of method result value.
2001 *
2002 * @return external_single_structure
2003 */
2004 public static function set_pin_state_returns() {
8885cd57 2005 return discussion_exporter::get_read_structure();
a7260536 2006 }
2b9fe87d 2007}