MDL-69521 core: Move all comments in code from 4.1 to 3.11
[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 95 // Format the intro before being returning using the format setting.
dfd48815
JL
96 $options = array('noclean' => true);
97 list($forum->intro, $forum->introformat) =
98 external_format_text($forum->intro, $forum->introformat, $context->id, 'mod_forum', 'intro', null, $options);
7ef49bd3 99 $forum->introfiles = external_util::get_area_files($context->id, 'mod_forum', 'intro', false, false);
ea5b910b
JL
100 // Discussions count. This function does static request cache.
101 $forum->numdiscussions = forum_count_discussions($forum, $cm, $course);
102 $forum->cmid = $forum->coursemodule;
103 $forum->cancreatediscussions = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
4669389d 104 $forum->istracked = forum_tp_is_tracked($forum);
2256bb74
JL
105 if ($forum->istracked) {
106 $forum->unreadpostscount = forum_tp_count_forum_unread_posts($cm, $course);
107 }
ea5b910b
JL
108
109 // Add the forum to the array to return.
110 $arrforums[$forum->id] = $forum;
2b9fe87d
MN
111 }
112 }
113
114 return $arrforums;
115 }
116
117 /**
a9a0cb69 118 * Describes the get_forum return value.
2b9fe87d
MN
119 *
120 * @return external_single_structure
121 * @since Moodle 2.5
122 */
0f3bbfd4 123 public static function get_forums_by_courses_returns() {
2b9fe87d
MN
124 return new external_multiple_structure(
125 new external_single_structure(
126 array(
127 'id' => new external_value(PARAM_INT, 'Forum id'),
5b587c75 128 'course' => new external_value(PARAM_INT, 'Course id'),
2b9fe87d 129 'type' => new external_value(PARAM_TEXT, 'The forum type'),
5b587c75 130 'name' => new external_value(PARAM_RAW, 'Forum name'),
2b9fe87d
MN
131 'intro' => new external_value(PARAM_RAW, 'The forum intro'),
132 'introformat' => new external_format_value('intro'),
7ef49bd3 133 'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
99bcb318
SR
134 'duedate' => new external_value(PARAM_INT, 'duedate for the user', VALUE_OPTIONAL),
135 'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user', VALUE_OPTIONAL),
2b9fe87d
MN
136 'assessed' => new external_value(PARAM_INT, 'Aggregate type'),
137 'assesstimestart' => new external_value(PARAM_INT, 'Assess start time'),
138 'assesstimefinish' => new external_value(PARAM_INT, 'Assess finish time'),
139 'scale' => new external_value(PARAM_INT, 'Scale'),
92b229c5 140 'grade_forum' => new external_value(PARAM_INT, 'Whole forum grade'),
254fc008 141 'grade_forum_notify' => new external_value(PARAM_INT, 'Whether to send notifications to students upon grading by default'),
2b9fe87d
MN
142 'maxbytes' => new external_value(PARAM_INT, 'Maximum attachment size'),
143 'maxattachments' => new external_value(PARAM_INT, 'Maximum number of attachments'),
144 'forcesubscribe' => new external_value(PARAM_INT, 'Force users to subscribe'),
145 'trackingtype' => new external_value(PARAM_INT, 'Subscription mode'),
146 'rsstype' => new external_value(PARAM_INT, 'RSS feed for this activity'),
147 'rssarticles' => new external_value(PARAM_INT, 'Number of RSS recent articles'),
148 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
149 'warnafter' => new external_value(PARAM_INT, 'Post threshold for warning'),
150 'blockafter' => new external_value(PARAM_INT, 'Post threshold for blocking'),
151 'blockperiod' => new external_value(PARAM_INT, 'Time period for blocking'),
152 'completiondiscussions' => new external_value(PARAM_INT, 'Student must create discussions'),
153 'completionreplies' => new external_value(PARAM_INT, 'Student must post replies'),
154 'completionposts' => new external_value(PARAM_INT, 'Student must post discussions or replies'),
7ea6ada3 155 'cmid' => new external_value(PARAM_INT, 'Course module id'),
ea5b910b
JL
156 'numdiscussions' => new external_value(PARAM_INT, 'Number of discussions in the forum', VALUE_OPTIONAL),
157 'cancreatediscussions' => new external_value(PARAM_BOOL, 'If the user can create discussions', VALUE_OPTIONAL),
0f3bbfd4 158 'lockdiscussionafter' => new external_value(PARAM_INT, 'After what period a discussion is locked', VALUE_OPTIONAL),
4669389d 159 'istracked' => new external_value(PARAM_BOOL, 'If the user is tracking the forum', VALUE_OPTIONAL),
2256bb74
JL
160 'unreadpostscount' => new external_value(PARAM_INT, 'The number of unread posts for tracked forums',
161 VALUE_OPTIONAL),
2b9fe87d
MN
162 ), 'forum'
163 )
164 );
165 }
a9a0cb69 166
bc4c7337
AN
167 /**
168 * Get the forum posts in the specified discussion.
169 *
170 * @param int $discussionid
171 * @param string $sortby
172 * @param string $sortdirection
173 * @return array
174 */
175 public static function get_discussion_posts(int $discussionid, ?string $sortby, ?string $sortdirection) {
176 global $USER;
177 // Validate the parameter.
178 $params = self::validate_parameters(self::get_discussion_posts_parameters(), [
179 'discussionid' => $discussionid,
180 'sortby' => $sortby,
181 'sortdirection' => $sortdirection,
182 ]);
183 $warnings = [];
184
185 $vaultfactory = mod_forum\local\container::get_vault_factory();
186
187 $discussionvault = $vaultfactory->get_discussion_vault();
188 $discussion = $discussionvault->get_from_id($params['discussionid']);
189
190 $forumvault = $vaultfactory->get_forum_vault();
191 $forum = $forumvault->get_from_id($discussion->get_forum_id());
3d58c1be
MM
192 $context = $forum->get_context();
193 self::validate_context($context);
bc4c7337
AN
194
195 $sortby = $params['sortby'];
196 $sortdirection = $params['sortdirection'];
197 $sortallowedvalues = ['id', 'created', 'modified'];
198 $directionallowedvalues = ['ASC', 'DESC'];
199
200 if (!in_array(strtolower($sortby), $sortallowedvalues)) {
201 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
202 'allowed values are: ' . implode(', ', $sortallowedvalues));
203 }
204
205 $sortdirection = strtoupper($sortdirection);
206 if (!in_array($sortdirection, $directionallowedvalues)) {
207 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
208 'allowed values are: ' . implode(',', $directionallowedvalues));
209 }
210
211 $managerfactory = mod_forum\local\container::get_manager_factory();
212 $capabilitymanager = $managerfactory->get_capability_manager($forum);
213
214 $postvault = $vaultfactory->get_post_vault();
215 $posts = $postvault->get_from_discussion_id(
216 $USER,
217 $discussion->get_id(),
218 $capabilitymanager->can_view_any_private_reply($USER),
219 "{$sortby} {$sortdirection}"
220 );
221
222 $builderfactory = mod_forum\local\container::get_builder_factory();
223 $postbuilder = $builderfactory->get_exported_posts_builder();
224
225 $legacydatamapper = mod_forum\local\container::get_legacy_data_mapper_factory();
226
227 return [
228 'posts' => $postbuilder->build($USER, [$forum], [$discussion], $posts),
229 'ratinginfo' => \core_rating\external\util::get_rating_info(
230 $legacydatamapper->get_forum_data_mapper()->to_legacy_object($forum),
231 $forum->get_context(),
232 'mod_forum',
233 'post',
234 $legacydatamapper->get_post_data_mapper()->to_legacy_objects($posts)
235 ),
236 'warnings' => $warnings,
237 ];
238 }
239
240 /**
241 * Describe the post parameters.
242 *
243 * @return external_function_parameters
244 */
245 public static function get_discussion_posts_parameters() {
246 return new external_function_parameters ([
247 'discussionid' => new external_value(PARAM_INT, 'The ID of the discussion from which to fetch posts.', VALUE_REQUIRED),
248 'sortby' => new external_value(PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
249 'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
250 ]);
251 }
252
253 /**
254 * Describe the post return format.
255 *
256 * @return external_single_structure
257 */
258 public static function get_discussion_posts_returns() {
259 return new external_single_structure([
260 'posts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
261 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
262 'warnings' => new external_warnings()
263 ]);
264 }
265
e2ede426
JL
266 /**
267 * Describes the parameters for get_forum_discussion_posts.
268 *
9db43c73 269 * @return external_function_parameters
e2ede426
JL
270 * @since Moodle 2.7
271 */
272 public static function get_forum_discussion_posts_parameters() {
273 return new external_function_parameters (
274 array(
275 'discussionid' => new external_value(PARAM_INT, 'discussion ID', VALUE_REQUIRED),
276 'sortby' => new external_value(PARAM_ALPHA,
277 'sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
278 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
279 )
280 );
281 }
282
283 /**
284 * Returns a list of forum posts for a discussion
285 *
286 * @param int $discussionid the post ids
287 * @param string $sortby sort by this element (id, created or modified)
288 * @param string $sortdirection sort direction: ASC or DESC
289 *
290 * @return array the forum post details
291 * @since Moodle 2.7
74ee34fd 292 * @todo MDL-65252 This will be removed in Moodle 3.11
e2ede426 293 */
fb8840d2 294 public static function get_forum_discussion_posts($discussionid, $sortby = "created", $sortdirection = "DESC") {
d85bedf7 295 global $CFG, $DB, $USER, $PAGE;
e2ede426 296
b1aa7dfa 297 $posts = array();
e2ede426
JL
298 $warnings = array();
299
300 // Validate the parameter.
301 $params = self::validate_parameters(self::get_forum_discussion_posts_parameters(),
302 array(
303 'discussionid' => $discussionid,
304 'sortby' => $sortby,
305 'sortdirection' => $sortdirection));
306
307 // Compact/extract functions are not recommended.
308 $discussionid = $params['discussionid'];
309 $sortby = $params['sortby'];
310 $sortdirection = $params['sortdirection'];
311
312 $sortallowedvalues = array('id', 'created', 'modified');
313 if (!in_array($sortby, $sortallowedvalues)) {
314 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
315 'allowed values are: ' . implode(',', $sortallowedvalues));
316 }
317
318 $sortdirection = strtoupper($sortdirection);
319 $directionallowedvalues = array('ASC', 'DESC');
320 if (!in_array($sortdirection, $directionallowedvalues)) {
321 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
322 'allowed values are: ' . implode(',', $directionallowedvalues));
323 }
324
325 $discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
326 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
327 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
328 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
329
330 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
331 $modcontext = context_module::instance($cm->id);
332 self::validate_context($modcontext);
333
334 // This require must be here, see mod/forum/discuss.php.
335 require_once($CFG->dirroot . "/mod/forum/lib.php");
336
337 // Check they have the view forum capability.
338 require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
339
340 if (! $post = forum_get_post_full($discussion->firstpost)) {
341 throw new moodle_exception('notexists', 'forum');
342 }
343
344 // This function check groups, qanda, timed discussions, etc.
345 if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
346 throw new moodle_exception('noviewdiscussionspermission', 'forum');
347 }
348
349 $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
350
351 // We will add this field in the response.
352 $canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
353
354 $forumtracked = forum_tp_is_tracked($forum);
355
356 $sort = 'p.' . $sortby . ' ' . $sortdirection;
b1aa7dfa 357 $allposts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
e2ede426 358
b1aa7dfa 359 foreach ($allposts as $post) {
3e95e09b 360 if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
e2ede426
JL
361 $warning = array();
362 $warning['item'] = 'post';
363 $warning['itemid'] = $post->id;
364 $warning['warningcode'] = '1';
365 $warning['message'] = 'You can\'t see this post';
366 $warnings[] = $warning;
367 continue;
368 }
369
370 // Function forum_get_all_discussion_posts adds postread field.
f47eeafd
JL
371 // Note that the value returned can be a boolean or an integer. The WS expects a boolean.
372 if (empty($post->postread)) {
b1aa7dfa 373 $post->postread = false;
f47eeafd 374 } else {
b1aa7dfa 375 $post->postread = true;
e2ede426 376 }
f47eeafd 377
bc4c7337
AN
378 $post->isprivatereply = !empty($post->privatereplyto);
379
b1aa7dfa
JL
380 $post->canreply = $canreply;
381 if (!empty($post->children)) {
382 $post->children = array_keys($post->children);
fb8840d2 383 } else {
b1aa7dfa 384 $post->children = array();
e2ede426
JL
385 }
386
3e95e09b
AN
387 if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
388 // The post is available, but has been marked as deleted.
389 // It will still be available but filled with a placeholder.
390 $post->userid = null;
391 $post->userfullname = null;
392 $post->userpictureurl = null;
393
394 $post->subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
395 $post->message = get_string('privacy:request:delete:post:message', 'mod_forum');
396
397 $post->deleted = true;
398 $posts[] = $post;
399
400 continue;
401 }
402 $post->deleted = false;
403
30861fbd
JL
404 if (forum_is_author_hidden($post, $forum)) {
405 $post->userid = null;
406 $post->userfullname = null;
407 $post->userpictureurl = null;
408 } else {
409 $user = new stdclass();
410 $user->id = $post->userid;
411 $user = username_load_fields_from_object($user, $post, null, array('picture', 'imagealt', 'email'));
412 $post->userfullname = fullname($user, $canviewfullname);
81f810dc 413
30861fbd
JL
414 $userpicture = new user_picture($user);
415 $userpicture->size = 1; // Size f1.
416 $post->userpictureurl = $userpicture->get_url($PAGE)->out(false);
417 }
14ebc396 418
55bb8189 419 $post->subject = external_format_string($post->subject, $modcontext->id);
48fb0250 420 // Rewrite embedded images URLs.
29cc69f3 421 $options = array('trusted' => $post->messagetrust);
48fb0250 422 list($post->message, $post->messageformat) =
29cc69f3
JL
423 external_format_text($post->message, $post->messageformat, $modcontext->id, 'mod_forum', 'post', $post->id,
424 $options);
48fb0250
JL
425
426 // List attachments.
427 if (!empty($post->attachment)) {
14590070 428 $post->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment', $post->id);
48fb0250 429 }
c8743f62
JL
430 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $post->id);
431 if (!empty($messageinlinefiles)) {
432 $post->messageinlinefiles = $messageinlinefiles;
433 }
6c344ff2
JL
434 // Post tags.
435 $post->tags = \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $post->id);
e2ede426 436
b1aa7dfa 437 $posts[] = $post;
e2ede426
JL
438 }
439
440 $result = array();
441 $result['posts'] = $posts;
b7ce46df 442 $result['ratinginfo'] = \core_rating\external\util::get_rating_info($forum, $modcontext, 'mod_forum', 'post', $posts);
e2ede426
JL
443 $result['warnings'] = $warnings;
444 return $result;
445 }
446
447 /**
448 * Describes the get_forum_discussion_posts return value.
449 *
450 * @return external_single_structure
451 * @since Moodle 2.7
452 */
453 public static function get_forum_discussion_posts_returns() {
454 return new external_single_structure(
455 array(
456 'posts' => new external_multiple_structure(
457 new external_single_structure(
458 array(
459 'id' => new external_value(PARAM_INT, 'Post id'),
460 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
461 'parent' => new external_value(PARAM_INT, 'Parent id'),
462 'userid' => new external_value(PARAM_INT, 'User id'),
463 'created' => new external_value(PARAM_INT, 'Creation time'),
464 'modified' => new external_value(PARAM_INT, 'Time modified'),
465 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
466 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
467 'message' => new external_value(PARAM_RAW, 'The post message'),
48fb0250 468 'messageformat' => new external_format_value('message'),
e2ede426 469 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
c8743f62 470 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
48fb0250 471 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
14590070 472 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
e2ede426
JL
473 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
474 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
475 'children' => new external_multiple_structure(new external_value(PARAM_INT, 'children post id')),
476 'canreply' => new external_value(PARAM_BOOL, 'The user can reply to posts?'),
477 'postread' => new external_value(PARAM_BOOL, 'The post was read'),
694bf0c7 478 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
3e95e09b
AN
479 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL),
480 'deleted' => new external_value(PARAM_BOOL, 'This post has been removed.'),
bc4c7337 481 'isprivatereply' => new external_value(PARAM_BOOL, 'The post is a private reply'),
6c344ff2
JL
482 'tags' => new external_multiple_structure(
483 \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
484 ),
e2ede426
JL
485 ), 'post'
486 )
487 ),
b7ce46df 488 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
e2ede426
JL
489 'warnings' => new external_warnings()
490 )
491 );
492 }
493
bc4c7337
AN
494 /**
495 * Mark the get_forum_discussion_posts web service as deprecated.
496 *
497 * @return bool
498 */
499 public static function get_forum_discussion_posts_is_deprecated() {
500 return true;
501 }
502
7c51b40a
JL
503 /**
504 * Describes the parameters for get_forum_discussions_paginated.
505 *
1a9c60e9 506 * @deprecated since 3.7
9db43c73 507 * @return external_function_parameters
7c51b40a
JL
508 * @since Moodle 2.8
509 */
510 public static function get_forum_discussions_paginated_parameters() {
511 return new external_function_parameters (
512 array(
513 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
514 'sortby' => new external_value(PARAM_ALPHA,
515 'sort by this element: id, timemodified, timestart or timeend', VALUE_DEFAULT, 'timemodified'),
516 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
517 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
518 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
519 )
520 );
521 }
522
523 /**
524 * Returns a list of forum discussions optionally sorted and paginated.
525 *
1a9c60e9 526 * @deprecated since 3.7
7c51b40a
JL
527 * @param int $forumid the forum instance id
528 * @param string $sortby sort by this element (id, timemodified, timestart or timeend)
529 * @param string $sortdirection sort direction: ASC or DESC
530 * @param int $page page number
531 * @param int $perpage items per page
532 *
533 * @return array the forum discussion details including warnings
534 * @since Moodle 2.8
535 */
536 public static function get_forum_discussions_paginated($forumid, $sortby = 'timemodified', $sortdirection = 'DESC',
1a9c60e9 537 $page = -1, $perpage = 0) {
d85bedf7 538 global $CFG, $DB, $USER, $PAGE;
7c51b40a
JL
539
540 require_once($CFG->dirroot . "/mod/forum/lib.php");
541
542 $warnings = array();
039c81f0 543 $discussions = array();
7c51b40a
JL
544
545 $params = self::validate_parameters(self::get_forum_discussions_paginated_parameters(),
546 array(
547 'forumid' => $forumid,
548 'sortby' => $sortby,
549 'sortdirection' => $sortdirection,
550 'page' => $page,
551 'perpage' => $perpage
552 )
553 );
554
555 // Compact/extract functions are not recommended.
556 $forumid = $params['forumid'];
557 $sortby = $params['sortby'];
558 $sortdirection = $params['sortdirection'];
559 $page = $params['page'];
560 $perpage = $params['perpage'];
561
562 $sortallowedvalues = array('id', 'timemodified', 'timestart', 'timeend');
563 if (!in_array($sortby, $sortallowedvalues)) {
564 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
565 'allowed values are: ' . implode(',', $sortallowedvalues));
566 }
567
568 $sortdirection = strtoupper($sortdirection);
569 $directionallowedvalues = array('ASC', 'DESC');
570 if (!in_array($sortdirection, $directionallowedvalues)) {
571 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
572 'allowed values are: ' . implode(',', $directionallowedvalues));
573 }
574
575 $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
576 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
577 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
578
579 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
580 $modcontext = context_module::instance($cm->id);
581 self::validate_context($modcontext);
582
583 // Check they have the view forum capability.
584 require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
585
5f219cf1 586 $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
4f3a2d21 587 $alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
7c51b40a 588
039c81f0 589 if ($alldiscussions) {
7c51b40a
JL
590 $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
591
592 // Get the unreads array, this takes a forum id and returns data for all discussions.
593 $unreads = array();
594 if ($cantrack = forum_tp_can_track_forums($forum)) {
595 if ($forumtracked = forum_tp_is_tracked($forum)) {
596 $unreads = forum_get_discussions_unread($cm);
597 }
598 }
599 // The forum function returns the replies for all the discussions in a given forum.
bc4c7337 600 $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
565cccfa 601 $canlock = has_capability('moodle/course:manageactivities', $modcontext, $USER);
bc4c7337 602 $replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage, $canseeprivatereplies);
7c51b40a 603
039c81f0
JL
604 foreach ($alldiscussions as $discussion) {
605
7c51b40a 606 // This function checks for qanda forums.
039c81f0
JL
607 // Note that the forum_get_discussions returns as id the post id, not the discussion id so we need to do this.
608 $discussionrec = clone $discussion;
609 $discussionrec->id = $discussion->discussion;
610 if (!forum_user_can_see_discussion($forum, $discussionrec, $modcontext)) {
7c51b40a
JL
611 $warning = array();
612 // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
613 $warning['item'] = 'post';
614 $warning['itemid'] = $discussion->id;
615 $warning['warningcode'] = '1';
616 $warning['message'] = 'You can\'t see this discussion';
617 $warnings[] = $warning;
618 continue;
619 }
620
621 $discussion->numunread = 0;
622 if ($cantrack && $forumtracked) {
623 if (isset($unreads[$discussion->discussion])) {
624 $discussion->numunread = (int) $unreads[$discussion->discussion];
625 }
626 }
627
628 $discussion->numreplies = 0;
629 if (!empty($replies[$discussion->discussion])) {
630 $discussion->numreplies = (int) $replies[$discussion->discussion]->replies;
631 }
632
55bb8189
JL
633 $discussion->name = external_format_string($discussion->name, $modcontext->id);
634 $discussion->subject = external_format_string($discussion->subject, $modcontext->id);
7c51b40a 635 // Rewrite embedded images URLs.
29cc69f3 636 $options = array('trusted' => $discussion->messagetrust);
7c51b40a
JL
637 list($discussion->message, $discussion->messageformat) =
638 external_format_text($discussion->message, $discussion->messageformat,
29cc69f3 639 $modcontext->id, 'mod_forum', 'post', $discussion->id, $options);
7c51b40a
JL
640
641 // List attachments.
642 if (!empty($discussion->attachment)) {
14590070
JL
643 $discussion->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment',
644 $discussion->id);
7c51b40a 645 }
c8743f62
JL
646 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $discussion->id);
647 if (!empty($messageinlinefiles)) {
648 $discussion->messageinlinefiles = $messageinlinefiles;
649 }
7c51b40a 650
565cccfa
P
651 $discussion->locked = forum_discussion_is_locked($forum, $discussion);
652 $discussion->canlock = $canlock;
0f3bbfd4
AN
653 $discussion->canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
654
30861fbd
JL
655 if (forum_is_author_hidden($discussion, $forum)) {
656 $discussion->userid = null;
657 $discussion->userfullname = null;
658 $discussion->userpictureurl = null;
659
660 $discussion->usermodified = null;
661 $discussion->usermodifiedfullname = null;
662 $discussion->usermodifiedpictureurl = null;
663 } else {
664 $picturefields = explode(',', user_picture::fields());
665
666 // Load user objects from the results of the query.
667 $user = new stdclass();
668 $user->id = $discussion->userid;
669 $user = username_load_fields_from_object($user, $discussion, null, $picturefields);
670 // Preserve the id, it can be modified by username_load_fields_from_object.
671 $user->id = $discussion->userid;
672 $discussion->userfullname = fullname($user, $canviewfullname);
673
674 $userpicture = new user_picture($user);
675 $userpicture->size = 1; // Size f1.
676 $discussion->userpictureurl = $userpicture->get_url($PAGE)->out(false);
677
678 $usermodified = new stdclass();
679 $usermodified->id = $discussion->usermodified;
680 $usermodified = username_load_fields_from_object($usermodified, $discussion, 'um', $picturefields);
681 // Preserve the id (it can be overwritten due to the prefixed $picturefields).
682 $usermodified->id = $discussion->usermodified;
683 $discussion->usermodifiedfullname = fullname($usermodified, $canviewfullname);
684
685 $userpicture = new user_picture($usermodified);
686 $userpicture->size = 1; // Size f1.
687 $discussion->usermodifiedpictureurl = $userpicture->get_url($PAGE)->out(false);
688 }
689
039c81f0 690 $discussions[] = $discussion;
7c51b40a
JL
691 }
692 }
693
694 $result = array();
695 $result['discussions'] = $discussions;
696 $result['warnings'] = $warnings;
697 return $result;
698
699 }
700
701 /**
702 * Describes the get_forum_discussions_paginated return value.
703 *
1a9c60e9 704 * @deprecated since 3.7
7c51b40a
JL
705 * @return external_single_structure
706 * @since Moodle 2.8
707 */
708 public static function get_forum_discussions_paginated_returns() {
709 return new external_single_structure(
710 array(
711 'discussions' => new external_multiple_structure(
712 new external_single_structure(
713 array(
714 'id' => new external_value(PARAM_INT, 'Post id'),
715 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
716 'groupid' => new external_value(PARAM_INT, 'Group id'),
717 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
718 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
719 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
720 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
721 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
722 'parent' => new external_value(PARAM_INT, 'Parent id'),
723 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
724 'created' => new external_value(PARAM_INT, 'Creation time'),
725 'modified' => new external_value(PARAM_INT, 'Time modified'),
726 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
727 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
728 'message' => new external_value(PARAM_RAW, 'The post message'),
729 'messageformat' => new external_format_value('message'),
730 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
c8743f62 731 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
7c51b40a 732 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
14590070 733 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
7c51b40a
JL
734 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
735 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
736 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
737 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
738 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
739 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
28c2f2b2 740 'numreplies' => new external_value(PARAM_INT, 'The number of replies in the discussion'),
5f219cf1 741 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
0f3bbfd4
AN
742 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
743 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
744 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
565cccfa 745 'canlock' => new external_value(PARAM_BOOL, 'Can the user lock the discussion'),
7c51b40a
JL
746 ), 'post'
747 )
748 ),
749 'warnings' => new external_warnings()
750 )
751 );
752 }
753
1a9c60e9
MG
754 /**
755 * Describes the parameters for get_forum_discussions.
756 *
757 * @return external_function_parameters
758 * @since Moodle 3.7
759 */
760 public static function get_forum_discussions_parameters() {
761 return new external_function_parameters (
762 array(
763 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
764 'sortorder' => new external_value(PARAM_INT,
765 'sort by this element: numreplies, , created or timemodified', VALUE_DEFAULT, -1),
766 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
767 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
768 'groupid' => new external_value(PARAM_INT, 'group id', VALUE_DEFAULT, 0),
769 )
770 );
771 }
772
773 /**
774 * Returns a list of forum discussions optionally sorted and paginated.
775 *
776 * @param int $forumid the forum instance id
777 * @param int $sortorder The sort order
778 * @param int $page page number
779 * @param int $perpage items per page
780 * @param int $groupid the user course group
781 *
782 *
783 * @return array the forum discussion details including warnings
784 * @since Moodle 3.7
785 */
786 public static function get_forum_discussions(int $forumid, ?int $sortorder = -1, ?int $page = -1,
787 ?int $perpage = 0, ?int $groupid = 0) {
788
789 global $CFG, $DB, $USER;
790
791 require_once($CFG->dirroot . "/mod/forum/lib.php");
792
793 $warnings = array();
794 $discussions = array();
795
796 $params = self::validate_parameters(self::get_forum_discussions_parameters(),
797 array(
798 'forumid' => $forumid,
799 'sortorder' => $sortorder,
800 'page' => $page,
801 'perpage' => $perpage,
802 'groupid' => $groupid
803 )
804 );
805
806 // Compact/extract functions are not recommended.
807 $forumid = $params['forumid'];
808 $sortorder = $params['sortorder'];
809 $page = $params['page'];
810 $perpage = $params['perpage'];
811 $groupid = $params['groupid'];
812
813 $vaultfactory = \mod_forum\local\container::get_vault_factory();
814 $discussionlistvault = $vaultfactory->get_discussions_in_forum_vault();
815
816 $sortallowedvalues = array(
817 $discussionlistvault::SORTORDER_LASTPOST_DESC,
818 $discussionlistvault::SORTORDER_LASTPOST_ASC,
819 $discussionlistvault::SORTORDER_CREATED_DESC,
820 $discussionlistvault::SORTORDER_CREATED_ASC,
821 $discussionlistvault::SORTORDER_REPLIES_DESC,
822 $discussionlistvault::SORTORDER_REPLIES_ASC
823 );
824
825 // If sortorder not defined set a default one.
826 if ($sortorder == -1) {
827 $sortorder = $discussionlistvault::SORTORDER_LASTPOST_DESC;
828 }
829
830 if (!in_array($sortorder, $sortallowedvalues)) {
831 throw new invalid_parameter_exception('Invalid value for sortorder parameter (value: ' . $sortorder . '),' .
832 ' allowed values are: ' . implode(',', $sortallowedvalues));
833 }
834
835 $managerfactory = \mod_forum\local\container::get_manager_factory();
836 $urlfactory = \mod_forum\local\container::get_url_factory();
837 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
838
839 $forumvault = $vaultfactory->get_forum_vault();
840 $forum = $forumvault->get_from_id($forumid);
2a80ecb5
MG
841 if (!$forum) {
842 throw new \moodle_exception("Unable to find forum with id {$forumid}");
843 }
1a9c60e9
MG
844 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
845 $forumrecord = $forumdatamapper->to_legacy_object($forum);
846
847 $capabilitymanager = $managerfactory->get_capability_manager($forum);
848
849 $course = $DB->get_record('course', array('id' => $forum->get_course_id()), '*', MUST_EXIST);
850 $cm = get_coursemodule_from_instance('forum', $forum->get_id(), $course->id, false, MUST_EXIST);
851
852 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
853 $modcontext = context_module::instance($cm->id);
854 self::validate_context($modcontext);
855
856 $canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($USER);
857
858 // Check they have the view forum capability.
859 if (!$capabilitymanager->can_view_discussions($USER)) {
860 throw new moodle_exception('noviewdiscussionspermission', 'forum');
861 }
862
b84f79ec 863 $alldiscussions = mod_forum_get_discussion_summaries($forum, $USER, $groupid, $sortorder, $page, $perpage);
1a9c60e9
MG
864
865 if ($alldiscussions) {
866 $discussionids = array_keys($alldiscussions);
867
868 $postvault = $vaultfactory->get_post_vault();
f31c531c 869 $postdatamapper = $legacydatamapperfactory->get_post_data_mapper();
1a9c60e9
MG
870 // Return the reply count for each discussion in a given forum.
871 $replies = $postvault->get_reply_count_for_discussion_ids($USER, $discussionids, $canseeanyprivatereply);
872 // Return the first post for each discussion in a given forum.
873 $firstposts = $postvault->get_first_post_for_discussion_ids($discussionids);
874
875 // Get the unreads array, this takes a forum id and returns data for all discussions.
876 $unreads = array();
877 if ($cantrack = forum_tp_can_track_forums($forumrecord)) {
878 if ($forumtracked = forum_tp_is_tracked($forumrecord)) {
879 $unreads = $postvault->get_unread_count_for_discussion_ids($USER, $discussionids, $canseeanyprivatereply);
880 }
881 }
882
883 $canlock = $capabilitymanager->can_manage_forum($USER);
884
121ccf15
MG
885 $usercontext = context_user::instance($USER->id);
886 $ufservice = core_favourites\service_factory::get_service_for_user_context($usercontext);
887
888 $canfavourite = has_capability('mod/forum:cantogglefavourite', $modcontext, $USER);
889
1a9c60e9
MG
890 foreach ($alldiscussions as $discussionsummary) {
891 $discussion = $discussionsummary->get_discussion();
892 $firstpostauthor = $discussionsummary->get_first_post_author();
893 $latestpostauthor = $discussionsummary->get_latest_post_author();
894
895 // This function checks for qanda forums.
896 $canviewdiscussion = $capabilitymanager->can_view_discussion($USER, $discussion);
897 if (!$canviewdiscussion) {
898 $warning = array();
899 // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
900 $warning['item'] = 'post';
901 $warning['itemid'] = $discussion->get_id();
902 $warning['warningcode'] = '1';
903 $warning['message'] = 'You can\'t see this discussion';
904 $warnings[] = $warning;
905 continue;
906 }
907
f31c531c
RW
908 $firstpost = $firstposts[$discussion->get_first_post_id()];
909 $discussionobject = $postdatamapper->to_legacy_object($firstpost);
910 // Fix up the types for these properties.
911 $discussionobject->mailed = $discussionobject->mailed ? 1 : 0;
912 $discussionobject->messagetrust = $discussionobject->messagetrust ? 1 : 0;
913 $discussionobject->mailnow = $discussionobject->mailnow ? 1 : 0;
1a9c60e9
MG
914 $discussionobject->groupid = $discussion->get_group_id();
915 $discussionobject->timemodified = $discussion->get_time_modified();
916 $discussionobject->usermodified = $discussion->get_user_modified();
917 $discussionobject->timestart = $discussion->get_time_start();
918 $discussionobject->timeend = $discussion->get_time_end();
919 $discussionobject->pinned = $discussion->is_pinned();
920
921 $discussionobject->numunread = 0;
922 if ($cantrack && $forumtracked) {
923 if (isset($unreads[$discussion->get_id()])) {
924 $discussionobject->numunread = (int) $unreads[$discussion->get_id()];
925 }
926 }
927
928 $discussionobject->numreplies = 0;
929 if (!empty($replies[$discussion->get_id()])) {
930 $discussionobject->numreplies = (int) $replies[$discussion->get_id()];
931 }
932
933 $discussionobject->name = external_format_string($discussion->get_name(), $modcontext->id);
934 $discussionobject->subject = external_format_string($discussionobject->subject, $modcontext->id);
935 // Rewrite embedded images URLs.
29cc69f3 936 $options = array('trusted' => $discussionobject->messagetrust);
1a9c60e9
MG
937 list($discussionobject->message, $discussionobject->messageformat) =
938 external_format_text($discussionobject->message, $discussionobject->messageformat,
29cc69f3 939 $modcontext->id, 'mod_forum', 'post', $discussionobject->id, $options);
1a9c60e9
MG
940
941 // List attachments.
942 if (!empty($discussionobject->attachment)) {
943 $discussionobject->attachments = external_util::get_area_files($modcontext->id, 'mod_forum',
944 'attachment', $discussionobject->id);
945 }
946 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post',
947 $discussionobject->id);
948 if (!empty($messageinlinefiles)) {
949 $discussionobject->messageinlinefiles = $messageinlinefiles;
950 }
951
952 $discussionobject->locked = $forum->is_discussion_locked($discussion);
953 $discussionobject->canlock = $canlock;
121ccf15
MG
954 $discussionobject->starred = !empty($ufservice) ? $ufservice->favourite_exists('mod_forum', 'discussions',
955 $discussion->get_id(), $modcontext) : false;
1a9c60e9 956 $discussionobject->canreply = $capabilitymanager->can_post_in_discussion($USER, $discussion);
121ccf15 957 $discussionobject->canfavourite = $canfavourite;
1a9c60e9
MG
958
959 if (forum_is_author_hidden($discussionobject, $forumrecord)) {
960 $discussionobject->userid = null;
961 $discussionobject->userfullname = null;
962 $discussionobject->userpictureurl = null;
963
964 $discussionobject->usermodified = null;
965 $discussionobject->usermodifiedfullname = null;
966 $discussionobject->usermodifiedpictureurl = null;
967
968 } else {
969 $discussionobject->userfullname = $firstpostauthor->get_full_name();
6489aa2a 970 $discussionobject->userpictureurl = $urlfactory->get_author_profile_image_url($firstpostauthor, null, 2)
1a9c60e9
MG
971 ->out(false);
972
973 $discussionobject->usermodifiedfullname = $latestpostauthor->get_full_name();
6489aa2a
RW
974 $discussionobject->usermodifiedpictureurl = $urlfactory->get_author_profile_image_url(
975 $latestpostauthor, null, 2)->out(false);
1a9c60e9
MG
976 }
977
978 $discussions[] = (array) $discussionobject;
979 }
980 }
981 $result = array();
982 $result['discussions'] = $discussions;
983 $result['warnings'] = $warnings;
984
985 return $result;
986 }
987
988 /**
989 * Describes the get_forum_discussions return value.
990 *
991 * @return external_single_structure
992 * @since Moodle 3.7
993 */
994 public static function get_forum_discussions_returns() {
995 return new external_single_structure(
996 array(
997 'discussions' => new external_multiple_structure(
998 new external_single_structure(
999 array(
1000 'id' => new external_value(PARAM_INT, 'Post id'),
1001 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
1002 'groupid' => new external_value(PARAM_INT, 'Group id'),
1003 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
1004 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
1005 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
1006 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
1007 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
1008 'parent' => new external_value(PARAM_INT, 'Parent id'),
1009 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
1010 'created' => new external_value(PARAM_INT, 'Creation time'),
1011 'modified' => new external_value(PARAM_INT, 'Time modified'),
1012 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
1013 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
1014 'message' => new external_value(PARAM_RAW, 'The post message'),
1015 'messageformat' => new external_format_value('message'),
1016 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
1017 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
1018 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
1019 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
1020 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
1021 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
1022 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
1023 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
1024 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
1025 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
1026 'numreplies' => new external_value(PARAM_INT, 'The number of replies in the discussion'),
1027 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
1028 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
1029 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
121ccf15 1030 'starred' => new external_value(PARAM_BOOL, 'Is the discussion starred'),
1a9c60e9
MG
1031 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
1032 'canlock' => new external_value(PARAM_BOOL, 'Can the user lock the discussion'),
121ccf15 1033 'canfavourite' => new external_value(PARAM_BOOL, 'Can the user star the discussion'),
1a9c60e9
MG
1034 ), 'post'
1035 )
1036 ),
1037 'warnings' => new external_warnings()
1038 )
1039 );
1040 }
1041
4a1e44a1
JL
1042 /**
1043 * Returns description of method parameters
1044 *
1045 * @return external_function_parameters
1046 * @since Moodle 2.9
1047 */
1048 public static function view_forum_parameters() {
1049 return new external_function_parameters(
1050 array(
1051 'forumid' => new external_value(PARAM_INT, 'forum instance id')
1052 )
1053 );
1054 }
1055
1056 /**
1c2b7882 1057 * Trigger the course module viewed event and update the module completion status.
4a1e44a1
JL
1058 *
1059 * @param int $forumid the forum instance id
1060 * @return array of warnings and status result
1061 * @since Moodle 2.9
1062 * @throws moodle_exception
1063 */
1064 public static function view_forum($forumid) {
1065 global $DB, $CFG;
1066 require_once($CFG->dirroot . "/mod/forum/lib.php");
1067
1068 $params = self::validate_parameters(self::view_forum_parameters(),
1069 array(
1070 'forumid' => $forumid
1071 ));
1072 $warnings = array();
1073
1074 // Request and permission validation.
ca7e2fc2 1075 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
4a1e44a1
JL
1076 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1077
1078 $context = context_module::instance($cm->id);
1079 self::validate_context($context);
1080
ea2fa324
JL
1081 require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
1082
4a1e44a1
JL
1083 // Call the forum/lib API.
1084 forum_view($forum, $course, $cm, $context);
1085
1086 $result = array();
1087 $result['status'] = true;
1088 $result['warnings'] = $warnings;
1089 return $result;
1090 }
1091
1092 /**
1093 * Returns description of method result value
1094 *
1095 * @return external_description
1096 * @since Moodle 2.9
1097 */
1098 public static function view_forum_returns() {
1099 return new external_single_structure(
1100 array(
1101 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1102 'warnings' => new external_warnings()
1103 )
1104 );
1105 }
1106
a3c315dd
JL
1107 /**
1108 * Returns description of method parameters
1109 *
1110 * @return external_function_parameters
1111 * @since Moodle 2.9
1112 */
1113 public static function view_forum_discussion_parameters() {
1114 return new external_function_parameters(
1115 array(
1116 'discussionid' => new external_value(PARAM_INT, 'discussion id')
1117 )
1118 );
1119 }
1120
1121 /**
1c2b7882 1122 * Trigger the discussion viewed event.
a3c315dd
JL
1123 *
1124 * @param int $discussionid the discussion id
1125 * @return array of warnings and status result
1126 * @since Moodle 2.9
1127 * @throws moodle_exception
1128 */
1129 public static function view_forum_discussion($discussionid) {
db3c9ff8 1130 global $DB, $CFG, $USER;
a3c315dd
JL
1131 require_once($CFG->dirroot . "/mod/forum/lib.php");
1132
1133 $params = self::validate_parameters(self::view_forum_discussion_parameters(),
1134 array(
1135 'discussionid' => $discussionid
1136 ));
1137 $warnings = array();
1138
1139 $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
1140 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
1141 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1142
1143 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
1144 $modcontext = context_module::instance($cm->id);
1145 self::validate_context($modcontext);
1146
ea2fa324
JL
1147 require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
1148
a3c315dd
JL
1149 // Call the forum/lib API.
1150 forum_discussion_view($modcontext, $forum, $discussion);
1151
db3c9ff8
PFO
1152 // Mark as read if required.
1153 if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
1154 forum_tp_mark_discussion_read($USER, $discussion->id);
1155 }
1156
a3c315dd
JL
1157 $result = array();
1158 $result['status'] = true;
1159 $result['warnings'] = $warnings;
1160 return $result;
1161 }
1162
1163 /**
1164 * Returns description of method result value
1165 *
1166 * @return external_description
1167 * @since Moodle 2.9
1168 */
1169 public static function view_forum_discussion_returns() {
1170 return new external_single_structure(
1171 array(
1172 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
1173 'warnings' => new external_warnings()
1174 )
1175 );
1176 }
1177
50a20317
JL
1178 /**
1179 * Returns description of method parameters
1180 *
1181 * @return external_function_parameters
1182 * @since Moodle 3.0
1183 */
1184 public static function add_discussion_post_parameters() {
1185 return new external_function_parameters(
1186 array(
1187 'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
1188 (can be the initial discussion post'),
1189 'subject' => new external_value(PARAM_TEXT, 'new post subject'),
f2eb04c1 1190 'message' => new external_value(PARAM_RAW, 'new post message (html assumed if messageformat is not provided)'),
50a20317
JL
1191 'options' => new external_multiple_structure (
1192 new external_single_structure(
1193 array(
1194 'name' => new external_value(PARAM_ALPHANUM,
1195 'The allowed keys (value format) are:
1196 discussionsubscribe (bool); subscribe to the discussion?, default to true
bc4c7337 1197 private (bool); make this reply private to the author of the parent post, default to false.
48143990 1198 inlineattachmentsid (int); the draft file area id for inline attachments
e881c4f5 1199 attachmentsid (int); the draft file area id for attachments
117e4bd4 1200 topreferredformat (bool); convert the message & messageformat to FORMAT_HTML, defaults to false
50a20317
JL
1201 '),
1202 'value' => new external_value(PARAM_RAW, 'the value of the option,
1203 this param is validated in the external function.'
1204 )
1205 )
f2eb04c1
EL
1206 ), 'Options', VALUE_DEFAULT, array()),
1207 'messageformat' => new external_format_value('message', VALUE_DEFAULT)
50a20317
JL
1208 )
1209 );
1210 }
1211
1212 /**
1213 * Create new posts into an existing discussion.
1214 *
1215 * @param int $postid the post id we are going to reply to
1216 * @param string $subject new post subject
f2eb04c1 1217 * @param string $message new post message (html assumed if messageformat is not provided)
50a20317 1218 * @param array $options optional settings
f2eb04c1 1219 * @param string $messageformat The format of the message, defaults to FORMAT_HTML for BC
50a20317
JL
1220 * @return array of warnings and the new post id
1221 * @since Moodle 3.0
1222 * @throws moodle_exception
1223 */
f2eb04c1 1224 public static function add_discussion_post($postid, $subject, $message, $options = array(), $messageformat = FORMAT_HTML) {
a0c9b6af 1225 global $CFG, $USER;
50a20317
JL
1226 require_once($CFG->dirroot . "/mod/forum/lib.php");
1227
9b4f09ba
P
1228 // Get all the factories that are required.
1229 $vaultfactory = mod_forum\local\container::get_vault_factory();
1230 $entityfactory = mod_forum\local\container::get_entity_factory();
1231 $datamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1232 $managerfactory = mod_forum\local\container::get_manager_factory();
1233 $discussionvault = $vaultfactory->get_discussion_vault();
1234 $forumvault = $vaultfactory->get_forum_vault();
1235 $discussiondatamapper = $datamapperfactory->get_discussion_data_mapper();
1236 $forumdatamapper = $datamapperfactory->get_forum_data_mapper();
1237
50a20317 1238 $params = self::validate_parameters(self::add_discussion_post_parameters(),
609a1073
JL
1239 array(
1240 'postid' => $postid,
1241 'subject' => $subject,
1242 'message' => $message,
f2eb04c1
EL
1243 'options' => $options,
1244 'messageformat' => $messageformat,
609a1073
JL
1245 )
1246 );
bc4c7337 1247
609a1073
JL
1248 $warnings = array();
1249
1250 if (!$parent = forum_get_post_full($params['postid'])) {
1251 throw new moodle_exception('invalidparentpostid', 'forum');
1252 }
1253
9b4f09ba 1254 if (!$discussion = $discussionvault->get_from_id($parent->discussion)) {
609a1073
JL
1255 throw new moodle_exception('notpartofdiscussion', 'forum');
1256 }
1257
1258 // Request and permission validation.
9b4f09ba
P
1259 $forum = $forumvault->get_from_id($discussion->get_forum_id());
1260 $capabilitymanager = $managerfactory->get_capability_manager($forum);
1261 $course = $forum->get_course_record();
1262 $cm = $forum->get_course_module_record();
609a1073 1263
9b4f09ba
P
1264 $discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
1265 $forumrecord = $forumdatamapper->to_legacy_object($forum);
609a1073
JL
1266 $context = context_module::instance($cm->id);
1267 self::validate_context($context);
1268
d411a1c1
MG
1269 $coursecontext = \context_course::instance($forum->get_course_id());
1270 $discussionsubscribe = \mod_forum\subscriptions::get_user_default_subscription($forumrecord, $coursecontext,
1271 $cm, null);
1272
50a20317
JL
1273 // Validate options.
1274 $options = array(
d411a1c1 1275 'discussionsubscribe' => $discussionsubscribe,
bc4c7337 1276 'private' => false,
48143990 1277 'inlineattachmentsid' => 0,
f2eb04c1 1278 'attachmentsid' => null,
117e4bd4 1279 'topreferredformat' => false
50a20317
JL
1280 );
1281 foreach ($params['options'] as $option) {
1282 $name = trim($option['name']);
1283 switch ($name) {
1284 case 'discussionsubscribe':
1285 $value = clean_param($option['value'], PARAM_BOOL);
1286 break;
bc4c7337
AN
1287 case 'private':
1288 $value = clean_param($option['value'], PARAM_BOOL);
1289 break;
48143990 1290 case 'inlineattachmentsid':
41182118
BK
1291 $value = clean_param($option['value'], PARAM_INT);
1292 break;
e881c4f5
BK
1293 case 'attachmentsid':
1294 $value = clean_param($option['value'], PARAM_INT);
609a1073
JL
1295 // Ensure that the user has permissions to create attachments.
1296 if (!has_capability('mod/forum:createattachment', $context)) {
1297 $value = 0;
1298 }
e881c4f5 1299 break;
117e4bd4 1300 case 'topreferredformat':
f2eb04c1
EL
1301 $value = clean_param($option['value'], PARAM_BOOL);
1302 break;
50a20317
JL
1303 default:
1304 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1305 }
1306 $options[$name] = $value;
1307 }
1308
9b4f09ba 1309 if (!$capabilitymanager->can_post_in_discussion($USER, $discussion)) {
50a20317
JL
1310 throw new moodle_exception('nopostforum', 'forum');
1311 }
1312
9b4f09ba 1313 $thresholdwarning = forum_check_throttling($forumrecord, $cm);
50a20317
JL
1314 forum_check_blocking_threshold($thresholdwarning);
1315
117e4bd4
EL
1316 // If we want to force a conversion to the preferred format, let's do it now.
1317 if ($options['topreferredformat']) {
1318 // We always are going to honor the preferred format. We are creating a new post.
1319 $preferredformat = editors_get_preferred_format();
1320 // If the post is not HTML and the preferred format is HTML, convert to it.
1321 if ($params['messageformat'] != FORMAT_HTML and $preferredformat == FORMAT_HTML) {
e9049ac8 1322 $params['message'] = format_text($params['message'], $params['messageformat'], ['filter' => false]);
117e4bd4
EL
1323 }
1324 $params['messageformat'] = $preferredformat;
f2eb04c1
EL
1325 }
1326
50a20317
JL
1327 // Create the post.
1328 $post = new stdClass();
9b4f09ba 1329 $post->discussion = $discussion->get_id();
50a20317
JL
1330 $post->parent = $parent->id;
1331 $post->subject = $params['subject'];
1332 $post->message = $params['message'];
f2eb04c1 1333 $post->messageformat = $params['messageformat'];
50a20317 1334 $post->messagetrust = trusttext_trusted($context);
48143990 1335 $post->itemid = $options['inlineattachmentsid'];
3e95e09b 1336 $post->attachments = $options['attachmentsid'];
bc4c7337 1337 $post->isprivatereply = $options['private'];
3e95e09b 1338 $post->deleted = 0;
e881c4f5
BK
1339 $fakemform = $post->attachments;
1340 if ($postid = forum_add_new_post($post, $fakemform)) {
50a20317
JL
1341
1342 $post->id = $postid;
1343
1344 // Trigger events and completion.
1345 $params = array(
1346 'context' => $context,
1347 'objectid' => $post->id,
1348 'other' => array(
9b4f09ba
P
1349 'discussionid' => $discussion->get_id(),
1350 'forumid' => $forum->get_id(),
1351 'forumtype' => $forum->get_type(),
50a20317
JL
1352 )
1353 );
1354 $event = \mod_forum\event\post_created::create($params);
1355 $event->add_record_snapshot('forum_posts', $post);
9b4f09ba 1356 $event->add_record_snapshot('forum_discussions', $discussionrecord);
50a20317
JL
1357 $event->trigger();
1358
1359 // Update completion state.
1360 $completion = new completion_info($course);
1361 if ($completion->is_enabled($cm) &&
9b4f09ba 1362 ($forum->get_completion_replies() || $forum->get_completion_posts())) {
50a20317
JL
1363 $completion->update_state($cm, COMPLETION_COMPLETE);
1364 }
1365
d411a1c1
MG
1366 if ($options['discussionsubscribe']) {
1367 $settings = new stdClass();
1368 $settings->discussionsubscribe = $options['discussionsubscribe'];
1369 forum_post_subscription($settings, $forumrecord, $discussionrecord);
1370 }
50a20317
JL
1371 } else {
1372 throw new moodle_exception('couldnotadd', 'forum');
1373 }
1374
9b4f09ba
P
1375 $builderfactory = \mod_forum\local\container::get_builder_factory();
1376 $exportedpostsbuilder = $builderfactory->get_exported_posts_builder();
1377 $postentity = $entityfactory->get_post_from_stdClass($post);
1378 $exportedposts = $exportedpostsbuilder->build($USER, [$forum], [$discussion], [$postentity]);
1379 $exportedpost = $exportedposts[0];
1380
a0c9b6af
P
1381 $message = [];
1382 $message[] = [
1383 'type' => 'success',
1384 'message' => get_string("postaddedsuccess", "forum")
1385 ];
1386
1387 $message[] = [
1388 'type' => 'success',
1389 'message' => get_string("postaddedtimeleft", "forum", format_time($CFG->maxeditingtime))
1390 ];
1391
50a20317
JL
1392 $result = array();
1393 $result['postid'] = $postid;
1394 $result['warnings'] = $warnings;
9b4f09ba 1395 $result['post'] = $exportedpost;
a0c9b6af 1396 $result['messages'] = $message;
50a20317
JL
1397 return $result;
1398 }
1399
1400 /**
1401 * Returns description of method result value
1402 *
1403 * @return external_description
1404 * @since Moodle 3.0
1405 */
1406 public static function add_discussion_post_returns() {
1407 return new external_single_structure(
1408 array(
1409 'postid' => new external_value(PARAM_INT, 'new post id'),
9b4f09ba 1410 'warnings' => new external_warnings(),
a0c9b6af
P
1411 'post' => post_exporter::get_read_structure(),
1412 'messages' => new external_multiple_structure(
1413 new external_single_structure(
1414 array(
1415 'type' => new external_value(PARAM_TEXT, "The classification to be used in the client side", VALUE_REQUIRED),
1416 'message' => new external_value(PARAM_TEXT,'untranslated english message to explain the warning', VALUE_REQUIRED)
1417 ), 'Messages'), 'list of warnings', VALUE_OPTIONAL
1418 ),
1419 //'alertmessage' => new external_value(PARAM_RAW, 'Success message to be displayed to the user.'),
50a20317
JL
1420 )
1421 );
1422 }
1423
309e8698
P
1424 /**
1425 * Toggle the favouriting value for the discussion provided
1426 *
309e8698
P
1427 * @param int $discussionid The discussion we need to favourite
1428 * @param bool $targetstate The state of the favourite value
1429 * @return array The exported discussion
1430 */
8885cd57 1431 public static function toggle_favourite_state($discussionid, $targetstate) {
99bda8a7
P
1432 global $DB, $PAGE, $USER;
1433
1434 $params = self::validate_parameters(self::toggle_favourite_state_parameters(), [
99bda8a7
P
1435 'discussionid' => $discussionid,
1436 'targetstate' => $targetstate
1437 ]);
1438
1439 $vaultfactory = mod_forum\local\container::get_vault_factory();
8885cd57
P
1440 // Get the discussion vault and the corresponding discussion entity.
1441 $discussionvault = $vaultfactory->get_discussion_vault();
1442 $discussion = $discussionvault->get_from_id($params['discussionid']);
1443
99bda8a7 1444 $forumvault = $vaultfactory->get_forum_vault();
8885cd57 1445 $forum = $forumvault->get_from_id($discussion->get_forum_id());
99bda8a7 1446 $forumcontext = $forum->get_context();
99bda8a7
P
1447 self::validate_context($forumcontext);
1448
1449 $managerfactory = mod_forum\local\container::get_manager_factory();
1450 $capabilitymanager = $managerfactory->get_capability_manager($forum);
1451
99bda8a7 1452 // Does the user have the ability to favourite the discussion?
b4a1bbbb 1453 if (!$capabilitymanager->can_favourite_discussion($USER)) {
99bda8a7
P
1454 throw new moodle_exception('cannotfavourite', 'forum');
1455 }
d3cac88d 1456 $usercontext = context_user::instance($USER->id);
99bda8a7
P
1457 $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
1458 $isfavourited = $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
1459
1460 $favouritefunction = $targetstate ? 'create_favourite' : 'delete_favourite';
1461 if ($isfavourited != (bool) $params['targetstate']) {
1462 $ufservice->{$favouritefunction}('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
1463 }
1464
1465 $exporterfactory = mod_forum\local\container::get_exporter_factory();
13cd05ac
P
1466 $builder = mod_forum\local\container::get_builder_factory()->get_exported_discussion_builder();
1467 $favourited = ($builder->is_favourited($discussion, $forumcontext, $USER) ? [$discussion->get_id()] : []);
1468 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion, [], $favourited);
99bda8a7
P
1469 return $exporter->export($PAGE->get_renderer('mod_forum'));
1470 }
1471
1472 /**
1473 * Returns description of method result value
1474 *
1475 * @return external_description
1476 * @since Moodle 3.0
1477 */
1478 public static function toggle_favourite_state_returns() {
1479 return discussion_exporter::get_read_structure();
1480 }
1481
309e8698
P
1482 /**
1483 * Defines the parameters for the toggle_favourite_state method
1484 *
1485 * @return external_function_parameters
1486 */
897ac0de 1487 public static function toggle_favourite_state_parameters() {
99bda8a7
P
1488 return new external_function_parameters(
1489 [
99bda8a7
P
1490 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1491 'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1492 ]
1493 );
1494 }
1495
7ab43ac8
JL
1496 /**
1497 * Returns description of method parameters
1498 *
1499 * @return external_function_parameters
1500 * @since Moodle 3.0
1501 */
1502 public static function add_discussion_parameters() {
1503 return new external_function_parameters(
1504 array(
7267daa8
AN
1505 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1506 'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
1507 'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
aa9059b9 1508 'groupid' => new external_value(PARAM_INT, 'The group, default to 0', VALUE_DEFAULT, 0),
7ab43ac8
JL
1509 'options' => new external_multiple_structure (
1510 new external_single_structure(
1511 array(
1512 'name' => new external_value(PARAM_ALPHANUM,
1513 'The allowed keys (value format) are:
1514 discussionsubscribe (bool); subscribe to the discussion?, default to true
5f219cf1 1515 discussionpinned (bool); is the discussion pinned, default to false
48143990 1516 inlineattachmentsid (int); the draft file area id for inline attachments
e881c4f5 1517 attachmentsid (int); the draft file area id for attachments
7ab43ac8 1518 '),
7267daa8
AN
1519 'value' => new external_value(PARAM_RAW, 'The value of the option,
1520 This param is validated in the external function.'
7ab43ac8
JL
1521 )
1522 )
1523 ), 'Options', VALUE_DEFAULT, array())
1524 )
1525 );
1526 }
1527
1528 /**
1529 * Add a new discussion into an existing forum.
1530 *
1531 * @param int $forumid the forum instance id
1532 * @param string $subject new discussion subject
1533 * @param string $message new discussion message (only html format allowed)
1534 * @param int $groupid the user course group
1535 * @param array $options optional settings
1536 * @return array of warnings and the new discussion id
1537 * @since Moodle 3.0
1538 * @throws moodle_exception
1539 */
aa9059b9 1540 public static function add_discussion($forumid, $subject, $message, $groupid = 0, $options = array()) {
7ab43ac8
JL
1541 global $DB, $CFG;
1542 require_once($CFG->dirroot . "/mod/forum/lib.php");
1543
1544 $params = self::validate_parameters(self::add_discussion_parameters(),
1545 array(
1546 'forumid' => $forumid,
1547 'subject' => $subject,
1548 'message' => $message,
1549 'groupid' => $groupid,
1550 'options' => $options
1551 ));
609a1073
JL
1552
1553 $warnings = array();
1554
1555 // Request and permission validation.
1556 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1557 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1558
1559 $context = context_module::instance($cm->id);
1560 self::validate_context($context);
1561
7ab43ac8
JL
1562 // Validate options.
1563 $options = array(
5f219cf1 1564 'discussionsubscribe' => true,
41182118 1565 'discussionpinned' => false,
48143990 1566 'inlineattachmentsid' => 0,
e881c4f5 1567 'attachmentsid' => null
7ab43ac8
JL
1568 );
1569 foreach ($params['options'] as $option) {
1570 $name = trim($option['name']);
1571 switch ($name) {
1572 case 'discussionsubscribe':
1573 $value = clean_param($option['value'], PARAM_BOOL);
1574 break;
5f219cf1
BK
1575 case 'discussionpinned':
1576 $value = clean_param($option['value'], PARAM_BOOL);
1577 break;
48143990 1578 case 'inlineattachmentsid':
41182118
BK
1579 $value = clean_param($option['value'], PARAM_INT);
1580 break;
e881c4f5
BK
1581 case 'attachmentsid':
1582 $value = clean_param($option['value'], PARAM_INT);
609a1073
JL
1583 // Ensure that the user has permissions to create attachments.
1584 if (!has_capability('mod/forum:createattachment', $context)) {
1585 $value = 0;
1586 }
e881c4f5 1587 break;
7ab43ac8
JL
1588 default:
1589 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1590 }
1591 $options[$name] = $value;
1592 }
1593
7ab43ac8
JL
1594 // Normalize group.
1595 if (!groups_get_activity_groupmode($cm)) {
1596 // Groups not supported, force to -1.
1597 $groupid = -1;
1598 } else {
1599 // Check if we receive the default or and empty value for groupid,
1600 // in this case, get the group for the user in the activity.
aa9059b9 1601 if (empty($params['groupid'])) {
7ab43ac8
JL
1602 $groupid = groups_get_activity_group($cm);
1603 } else {
1604 // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
1605 $groupid = $params['groupid'];
1606 }
1607 }
1608
1609 if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
1610 throw new moodle_exception('cannotcreatediscussion', 'forum');
1611 }
1612
1613 $thresholdwarning = forum_check_throttling($forum, $cm);
1614 forum_check_blocking_threshold($thresholdwarning);
1615
1616 // Create the discussion.
1617 $discussion = new stdClass();
1618 $discussion->course = $course->id;
1619 $discussion->forum = $forum->id;
1620 $discussion->message = $params['message'];
1621 $discussion->messageformat = FORMAT_HTML; // Force formatting for now.
1622 $discussion->messagetrust = trusttext_trusted($context);
48143990 1623 $discussion->itemid = $options['inlineattachmentsid'];
7ab43ac8
JL
1624 $discussion->groupid = $groupid;
1625 $discussion->mailnow = 0;
1626 $discussion->subject = $params['subject'];
1627 $discussion->name = $discussion->subject;
1628 $discussion->timestart = 0;
1629 $discussion->timeend = 0;
2893812e 1630 $discussion->timelocked = 0;
e881c4f5
BK
1631 $discussion->attachments = $options['attachmentsid'];
1632
5f219cf1
BK
1633 if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
1634 $discussion->pinned = FORUM_DISCUSSION_PINNED;
1635 } else {
1636 $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
1637 }
e881c4f5
BK
1638 $fakemform = $options['attachmentsid'];
1639 if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
7ab43ac8
JL
1640
1641 $discussion->id = $discussionid;
1642
1643 // Trigger events and completion.
1644
1645 $params = array(
1646 'context' => $context,
1647 'objectid' => $discussion->id,
1648 'other' => array(
1649 'forumid' => $forum->id,
1650 )
1651 );
1652 $event = \mod_forum\event\discussion_created::create($params);
1653 $event->add_record_snapshot('forum_discussions', $discussion);
1654 $event->trigger();
1655
1656 $completion = new completion_info($course);
1657 if ($completion->is_enabled($cm) &&
1658 ($forum->completiondiscussions || $forum->completionposts)) {
1659 $completion->update_state($cm, COMPLETION_COMPLETE);
1660 }
1661
1662 $settings = new stdClass();
1663 $settings->discussionsubscribe = $options['discussionsubscribe'];
1664 forum_post_subscription($settings, $forum, $discussion);
1665 } else {
1666 throw new moodle_exception('couldnotadd', 'forum');
1667 }
1668
1669 $result = array();
1670 $result['discussionid'] = $discussionid;
1671 $result['warnings'] = $warnings;
1672 return $result;
1673 }
1674
1675 /**
1676 * Returns description of method result value
1677 *
1678 * @return external_description
1679 * @since Moodle 3.0
1680 */
1681 public static function add_discussion_returns() {
1682 return new external_single_structure(
1683 array(
7267daa8 1684 'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
7ab43ac8
JL
1685 'warnings' => new external_warnings()
1686 )
1687 );
1688 }
1689
04cd8ae3
JL
1690 /**
1691 * Returns description of method parameters
1692 *
1693 * @return external_function_parameters
1694 * @since Moodle 3.1
1695 */
1696 public static function can_add_discussion_parameters() {
1697 return new external_function_parameters(
1698 array(
1699 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1700 'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1701 Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1702 )
1703 );
1704 }
1705
1706 /**
1707 * Check if the current user can add discussions in the given forum (and optionally for the given group).
1708 *
1709 * @param int $forumid the forum instance id
1710 * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1711 * @return array of warnings and the status (true if the user can add discussions)
1712 * @since Moodle 3.1
1713 * @throws moodle_exception
1714 */
1715 public static function can_add_discussion($forumid, $groupid = null) {
1716 global $DB, $CFG;
1717 require_once($CFG->dirroot . "/mod/forum/lib.php");
1718
1719 $params = self::validate_parameters(self::can_add_discussion_parameters(),
1720 array(
1721 'forumid' => $forumid,
1722 'groupid' => $groupid,
1723 ));
1724 $warnings = array();
1725
1726 // Request and permission validation.
1727 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1728 list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1729
1730 $context = context_module::instance($cm->id);
1731 self::validate_context($context);
1732
1733 $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1734
1735 $result = array();
1736 $result['status'] = $status;
581e75bf
JL
1737 $result['canpindiscussions'] = has_capability('mod/forum:pindiscussions', $context);
1738 $result['cancreateattachment'] = forum_can_create_attachment($forum, $context);
04cd8ae3
JL
1739 $result['warnings'] = $warnings;
1740 return $result;
1741 }
1742
1743 /**
1744 * Returns description of method result value
1745 *
1746 * @return external_description
1747 * @since Moodle 3.1
1748 */
1749 public static function can_add_discussion_returns() {
1750 return new external_single_structure(
1751 array(
1752 'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
581e75bf
JL
1753 'canpindiscussions' => new external_value(PARAM_BOOL, 'True if the user can pin discussions, false otherwise.',
1754 VALUE_OPTIONAL),
1755 'cancreateattachment' => new external_value(PARAM_BOOL, 'True if the user can add attachments, false otherwise.',
1756 VALUE_OPTIONAL),
04cd8ae3
JL
1757 'warnings' => new external_warnings()
1758 )
1759 );
1760 }
1761
4daa0d08
JL
1762 /**
1763 * Describes the parameters for get_forum_access_information.
1764 *
1765 * @return external_external_function_parameters
1766 * @since Moodle 3.7
1767 */
1768 public static function get_forum_access_information_parameters() {
1769 return new external_function_parameters (
1770 array(
1771 'forumid' => new external_value(PARAM_INT, 'Forum instance id.')
1772 )
1773 );
1774 }
1775
1776 /**
1777 * Return access information for a given forum.
1778 *
1779 * @param int $forumid forum instance id
1780 * @return array of warnings and the access information
1781 * @since Moodle 3.7
1782 * @throws moodle_exception
1783 */
1784 public static function get_forum_access_information($forumid) {
1785 global $DB;
1786
1787 $params = self::validate_parameters(self::get_forum_access_information_parameters(), array('forumid' => $forumid));
1788
1789 // Request and permission validation.
1790 $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1791 $cm = get_coursemodule_from_instance('forum', $forum->id);
1792
1793 $context = context_module::instance($cm->id);
1794 self::validate_context($context);
1795
1796 $result = array();
1797 // Return all the available capabilities.
1798 $capabilities = load_capability_def('mod_forum');
1799 foreach ($capabilities as $capname => $capdata) {
1800 // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1801 $field = 'can' . str_replace('mod/forum:', '', $capname);
1802 $result[$field] = has_capability($capname, $context);
1803 }
1804
1805 $result['warnings'] = array();
1806 return $result;
1807 }
1808
1809 /**
1810 * Describes the get_forum_access_information return value.
1811 *
1812 * @return external_single_structure
1813 * @since Moodle 3.7
1814 */
1815 public static function get_forum_access_information_returns() {
1816
1817 $structure = array(
1818 'warnings' => new external_warnings()
1819 );
1820
1821 $capabilities = load_capability_def('mod_forum');
1822 foreach ($capabilities as $capname => $capdata) {
1823 // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1824 $field = 'can' . str_replace('mod/forum:', '', $capname);
1825 $structure[$field] = new external_value(PARAM_BOOL, 'Whether the user has the capability ' . $capname . ' allowed.',
1826 VALUE_OPTIONAL);
1827 }
1828
1829 return new external_single_structure($structure);
1830 }
2646e9d6
RW
1831
1832 /**
1833 * Set the subscription state.
1834 *
1835 * @param int $forumid
1836 * @param int $discussionid
1837 * @param bool $targetstate
1838 * @return \stdClass
1839 */
1840 public static function set_subscription_state($forumid, $discussionid, $targetstate) {
f30f46db 1841 global $PAGE, $USER;
2646e9d6
RW
1842
1843 $params = self::validate_parameters(self::set_subscription_state_parameters(), [
1844 'forumid' => $forumid,
1845 'discussionid' => $discussionid,
1846 'targetstate' => $targetstate
1847 ]);
1848
1849 $vaultfactory = mod_forum\local\container::get_vault_factory();
1850 $forumvault = $vaultfactory->get_forum_vault();
1851 $forum = $forumvault->get_from_id($params['forumid']);
2646e9d6 1852 $coursemodule = $forum->get_course_module_record();
96a49734 1853 $context = $forum->get_context();
2646e9d6 1854
96a49734 1855 self::validate_context($context);
2646e9d6 1856
2646e9d6
RW
1857 $discussionvault = $vaultfactory->get_discussion_vault();
1858 $discussion = $discussionvault->get_from_id($params['discussionid']);
1859 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1860
1861 $forumrecord = $legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
1862 $discussionrecord = $legacydatamapperfactory->get_discussion_data_mapper()->to_legacy_object($discussion);
1863
1864 if (!\mod_forum\subscriptions::is_subscribable($forumrecord)) {
1865 // Nothing to do. We won't actually output any content here though.
1866 throw new \moodle_exception('cannotsubscribe', 'mod_forum');
1867 }
1868
1869 $issubscribed = \mod_forum\subscriptions::is_subscribed(
1870 $USER->id,
1871 $forumrecord,
1872 $discussion->get_id(),
1873 $coursemodule
1874 );
1875
1876 // If the current state doesn't equal the desired state then update the current
1877 // state to the desired state.
1878 if ($issubscribed != (bool) $params['targetstate']) {
1879 if ($params['targetstate']) {
1880 \mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussionrecord, $context);
1881 } else {
1882 \mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussionrecord, $context);
1883 }
1884 }
1885
1886 $exporterfactory = mod_forum\local\container::get_exporter_factory();
1887 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1888 return $exporter->export($PAGE->get_renderer('mod_forum'));
1889 }
1890
1891 /**
1892 * Returns description of method parameters.
1893 *
1894 * @return external_function_parameters
1895 */
1896 public static function set_subscription_state_parameters() {
1897 return new external_function_parameters(
1898 [
1899 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1900 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1901 'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1902 ]
1903 );
1904 }
1905
1906 /**
1907 * Returns description of method result value.
1908 *
1909 * @return external_description
1910 */
1911 public static function set_subscription_state_returns() {
8885cd57 1912 return discussion_exporter::get_read_structure();
2646e9d6 1913 }
2893812e 1914
2893812e
P
1915 /**
1916 * Set the lock state.
1917 *
1918 * @param int $forumid
1919 * @param int $discussionid
1920 * @param string $targetstate
1921 * @return \stdClass
1922 */
1923 public static function set_lock_state($forumid, $discussionid, $targetstate) {
1924 global $DB, $PAGE, $USER;
1925
1926 $params = self::validate_parameters(self::set_lock_state_parameters(), [
1927 'forumid' => $forumid,
1928 'discussionid' => $discussionid,
1929 'targetstate' => $targetstate
1930 ]);
1931
1932 $vaultfactory = mod_forum\local\container::get_vault_factory();
1933 $forumvault = $vaultfactory->get_forum_vault();
1934 $forum = $forumvault->get_from_id($params['forumid']);
2893812e
P
1935
1936 $managerfactory = mod_forum\local\container::get_manager_factory();
1937 $capabilitymanager = $managerfactory->get_capability_manager($forum);
bdb4a87d
P
1938 if (!$capabilitymanager->can_manage_forum($USER)) {
1939 throw new moodle_exception('errorcannotlock', 'forum');
1940 }
1941
1942 // If the targetstate(currentstate) is not 0 then it should be set to the current time.
1943 $lockedvalue = $targetstate ? 0 : time();
1944 self::validate_context($forum->get_context());
1945
2893812e
P
1946 $discussionvault = $vaultfactory->get_discussion_vault();
1947 $discussion = $discussionvault->get_from_id($params['discussionid']);
2893812e 1948
c475fe41 1949 // If the current state doesn't equal the desired state then update the current.
2893812e 1950 // state to the desired state.
bdb4a87d
P
1951 $discussion->toggle_locked_state($lockedvalue);
1952 $response = $discussionvault->update_discussion($discussion);
1953 $discussion = !$response ? $response : $discussion;
a7260536 1954 $exporterfactory = mod_forum\local\container::get_exporter_factory();
1955 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1956 return $exporter->export($PAGE->get_renderer('mod_forum'));
1957 }
1958
cda9da99
P
1959 /**
1960 * Returns description of method parameters.
1961 *
1962 * @return external_function_parameters
1963 */
1964 public static function set_lock_state_parameters() {
1965 return new external_function_parameters(
1966 [
1967 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1968 'discussionid' => new external_value(PARAM_INT, 'The discussion to lock / unlock'),
1969 'targetstate' => new external_value(PARAM_INT, 'The timestamp for the lock state')
1970 ]
1971 );
1972 }
1973
1974 /**
1975 * Returns description of method result value.
1976 *
1977 * @return external_description
1978 */
1979 public static function set_lock_state_returns() {
1980 return new external_single_structure([
1981 'id' => new external_value(PARAM_INT, 'The discussion we are locking.'),
1982 'locked' => new external_value(PARAM_BOOL, 'The locked state of the discussion.'),
1983 'times' => new external_single_structure([
1984 'locked' => new external_value(PARAM_INT, 'The locked time of the discussion.'),
1985 ])
1986 ]);
1987 }
1988
a7260536 1989 /**
1990 * Set the pin state.
1991 *
a7260536 1992 * @param int $discussionid
1993 * @param bool $targetstate
1994 * @return \stdClass
1995 */
8885cd57 1996 public static function set_pin_state($discussionid, $targetstate) {
a7260536 1997 global $PAGE, $USER;
1998 $params = self::validate_parameters(self::set_pin_state_parameters(), [
a7260536 1999 'discussionid' => $discussionid,
2000 'targetstate' => $targetstate,
2001 ]);
2002 $vaultfactory = mod_forum\local\container::get_vault_factory();
309e8698 2003 $managerfactory = mod_forum\local\container::get_manager_factory();
a7260536 2004 $forumvault = $vaultfactory->get_forum_vault();
2005 $discussionvault = $vaultfactory->get_discussion_vault();
8885cd57
P
2006 $discussion = $discussionvault->get_from_id($params['discussionid']);
2007 $forum = $forumvault->get_from_id($discussion->get_forum_id());
309e8698 2008 $capabilitymanager = $managerfactory->get_capability_manager($forum);
a7260536 2009
2010 self::validate_context($forum->get_context());
24962ee1
P
2011
2012 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
309e8698 2013 if (!$capabilitymanager->can_pin_discussions($USER)) {
a7260536 2014 // Nothing to do. We won't actually output any content here though.
309e8698 2015 throw new \moodle_exception('cannotpindiscussions', 'mod_forum');
a7260536 2016 }
2017
a7260536 2018 $discussion->set_pinned($targetstate);
cda9da99 2019 $discussionvault->update_discussion($discussion);
2893812e
P
2020
2021 $exporterfactory = mod_forum\local\container::get_exporter_factory();
2022 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
2023 return $exporter->export($PAGE->get_renderer('mod_forum'));
2024 }
2025
a7260536 2026 /**
2027 * Returns description of method parameters.
2028 *
2029 * @return external_function_parameters
2030 */
2031 public static function set_pin_state_parameters() {
2032 return new external_function_parameters(
2033 [
309e8698
P
2034 'discussionid' => new external_value(PARAM_INT, 'The discussion to pin or unpin', VALUE_REQUIRED,
2035 null, NULL_NOT_ALLOWED),
2036 'targetstate' => new external_value(PARAM_INT, 'The target state', VALUE_REQUIRED,
2037 null, NULL_NOT_ALLOWED),
a7260536 2038 ]
2039 );
2040 }
2041
a7260536 2042 /**
2043 * Returns description of method result value.
2044 *
2045 * @return external_single_structure
2046 */
2047 public static function set_pin_state_returns() {
8885cd57 2048 return discussion_exporter::get_read_structure();
a7260536 2049 }
56444a60
JL
2050
2051 /**
2052 * Returns description of method parameters
2053 *
2054 * @return external_function_parameters
2055 * @since Moodle 3.8
2056 */
2057 public static function delete_post_parameters() {
2058 return new external_function_parameters(
2059 array(
2060 'postid' => new external_value(PARAM_INT, 'Post to be deleted. It can be a discussion topic post.'),
2061 )
2062 );
2063 }
2064
2065 /**
2066 * Deletes a post or a discussion completely when the post is the discussion topic.
2067 *
2068 * @param int $postid post to be deleted, it can be a discussion topic post.
2069 * @return array of warnings and the status (true if the post/discussion was deleted)
2070 * @since Moodle 3.8
2071 * @throws moodle_exception
2072 */
2073 public static function delete_post($postid) {
2074 global $USER, $CFG;
2075 require_once($CFG->dirroot . "/mod/forum/lib.php");
2076
2077 $params = self::validate_parameters(self::delete_post_parameters(),
2078 array(
2079 'postid' => $postid,
2080 )
2081 );
2082 $warnings = array();
2083 $vaultfactory = mod_forum\local\container::get_vault_factory();
2084 $forumvault = $vaultfactory->get_forum_vault();
2085 $discussionvault = $vaultfactory->get_discussion_vault();
2086 $postvault = $vaultfactory->get_post_vault();
2087 $postentity = $postvault->get_from_id($params['postid']);
2088
2089 if (empty($postentity)) {
2090 throw new moodle_exception('invalidpostid', 'forum');
2091 }
2092
2093 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
2094
2095 if (empty($discussionentity)) {
2096 throw new moodle_exception('notpartofdiscussion', 'forum');
2097 }
2098
2099 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
2100 if (empty($forumentity)) {
2101 throw new moodle_exception('invalidforumid', 'forum');
2102 }
2103
2104 $context = $forumentity->get_context();
2105
2106 self::validate_context($context);
2107
2108 $managerfactory = mod_forum\local\container::get_manager_factory();
2109 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
2110 $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
2111 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
2112 $discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper();
2113 $postdatamapper = $legacydatamapperfactory->get_post_data_mapper();
2114
2115 $replycount = $postvault->get_reply_count_for_post_id_in_discussion_id($USER, $postentity->get_id(),
2116 $discussionentity->get_id(), true);
2117 $hasreplies = $replycount > 0;
2118
2119 $capabilitymanager->validate_delete_post($USER, $discussionentity, $postentity, $hasreplies);
2120
2121 if (!$postentity->has_parent()) {
2122 $status = forum_delete_discussion(
2123 $discussiondatamapper->to_legacy_object($discussionentity),
2124 false,
2125 $forumentity->get_course_record(),
2126 $forumentity->get_course_module_record(),
2127 $forumdatamapper->to_legacy_object($forumentity)
2128 );
2129 } else {
2130 $status = forum_delete_post(
2131 $postdatamapper->to_legacy_object($postentity),
2132 has_capability('mod/forum:deleteanypost', $context),
2133 $forumentity->get_course_record(),
2134 $forumentity->get_course_module_record(),
2135 $forumdatamapper->to_legacy_object($forumentity)
2136 );
2137 }
2138
2139 $result = array();
2140 $result['status'] = $status;
2141 $result['warnings'] = $warnings;
2142 return $result;
2143 }
2144
2145 /**
2146 * Returns description of method result value
2147 *
2148 * @return external_description
2149 * @since Moodle 3.8
2150 */
2151 public static function delete_post_returns() {
2152 return new external_single_structure(
2153 array(
2154 'status' => new external_value(PARAM_BOOL, 'True if the post/discussion was deleted, false otherwise.'),
2155 'warnings' => new external_warnings()
2156 )
2157 );
2158 }
34e6dd24
MM
2159
2160 /**
2161 * Get the forum posts in the specified forum instance.
2162 *
2163 * @param int $userid
2164 * @param int $cmid
2165 * @param string $sortby
2166 * @param string $sortdirection
2167 * @return array
2168 */
2169 public static function get_discussion_posts_by_userid(int $userid = 0, int $cmid, ?string $sortby, ?string $sortdirection) {
2170 global $USER, $DB;
2171 // Validate the parameter.
2172 $params = self::validate_parameters(self::get_discussion_posts_by_userid_parameters(), [
2173 'userid' => $userid,
2174 'cmid' => $cmid,
2175 'sortby' => $sortby,
2176 'sortdirection' => $sortdirection,
2177 ]);
2178 $warnings = [];
2179
a62c68fb 2180 $user = core_user::get_user($params['userid']);
34e6dd24
MM
2181
2182 $vaultfactory = mod_forum\local\container::get_vault_factory();
2183
2184 $forumvault = $vaultfactory->get_forum_vault();
2185 $forum = $forumvault->get_from_course_module_id($params['cmid']);
2186
2187 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
2188 self::validate_context($forum->get_context());
2189
2190 $sortby = $params['sortby'];
2191 $sortdirection = $params['sortdirection'];
2192 $sortallowedvalues = ['id', 'created', 'modified'];
2193 $directionallowedvalues = ['ASC', 'DESC'];
2194
2195 if (!in_array(strtolower($sortby), $sortallowedvalues)) {
2196 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
2197 'allowed values are: ' . implode(', ', $sortallowedvalues));
2198 }
2199
2200 $sortdirection = strtoupper($sortdirection);
2201 if (!in_array($sortdirection, $directionallowedvalues)) {
2202 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
2203 'allowed values are: ' . implode(',', $directionallowedvalues));
2204 }
2205
2206 $managerfactory = mod_forum\local\container::get_manager_factory();
2207 $capabilitymanager = $managerfactory->get_capability_manager($forum);
2208
d9fedf3d
RW
2209 $discussionsummariesvault = $vaultfactory->get_discussions_in_forum_vault();
2210 $discussionsummaries = $discussionsummariesvault->get_from_forum_id(
2211 $forum->get_id(),
2212 true,
2213 null,
2214 $discussionsummariesvault::SORTORDER_CREATED_ASC,
2215 0,
2216 0
2217 );
34e6dd24
MM
2218
2219 $postvault = $vaultfactory->get_post_vault();
2220
2221 $builderfactory = mod_forum\local\container::get_builder_factory();
2222 $postbuilder = $builderfactory->get_exported_posts_builder();
2223
2224 $builtdiscussions = [];
d9fedf3d
RW
2225 foreach ($discussionsummaries as $discussionsummary) {
2226 $discussion = $discussionsummary->get_discussion();
34e6dd24
MM
2227 $posts = $postvault->get_posts_in_discussion_for_user_id(
2228 $discussion->get_id(),
2229 $user->id,
2230 $capabilitymanager->can_view_any_private_reply($USER),
2231 "{$sortby} {$sortdirection}"
2232 );
2233 if (empty($posts)) {
2234 continue;
2235 }
2236
2237 $parentids = array_filter(array_map(function($post) {
2238 return $post->has_parent() ? $post->get_parent_id() : null;
2239 }, $posts));
2240
2241 $parentposts = [];
2242 if ($parentids) {
2243 $parentposts = $postbuilder->build(
2244 $user,
2245 [$forum],
2246 [$discussion],
2247 $postvault->get_from_ids(array_values($parentids))
2248 );
2249 }
2250
d9fedf3d
RW
2251 $discussionauthor = $discussionsummary->get_first_post_author();
2252 $firstpost = $discussionsummary->get_first_post();
2253
34e6dd24
MM
2254 $builtdiscussions[] = [
2255 'name' => $discussion->get_name(),
2256 'id' => $discussion->get_id(),
d9fedf3d
RW
2257 'timecreated' => $firstpost->get_time_created(),
2258 'authorfullname' => $discussionauthor->get_full_name(),
34e6dd24
MM
2259 'posts' => [
2260 'userposts' => $postbuilder->build($user, [$forum], [$discussion], $posts),
2261 'parentposts' => $parentposts,
2262 ],
2263 ];
2264 }
2265
2266 return [
2267 'discussions' => $builtdiscussions,
2268 'warnings' => $warnings,
2269 ];
2270 }
2271
2272 /**
2273 * Describe the post parameters.
2274 *
2275 * @return external_function_parameters
2276 */
2277 public static function get_discussion_posts_by_userid_parameters() {
2278 return new external_function_parameters ([
2279 'userid' => new external_value(
2280 PARAM_INT, 'The ID of the user of whom to fetch posts.', VALUE_REQUIRED),
2281 'cmid' => new external_value(
2282 PARAM_INT, 'The ID of the module of which to fetch items.', VALUE_REQUIRED),
2283 'sortby' => new external_value(
2284 PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
2285 'sortdirection' => new external_value(
2286 PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
2287 ]);
2288 }
2289
2290 /**
2291 * Describe the post return format.
2292 *
2293 * @return external_single_structure
2294 */
2295 public static function get_discussion_posts_by_userid_returns() {
2296 return new external_single_structure([
2297 'discussions' => new external_multiple_structure(
2298 new external_single_structure([
2299 'name' => new external_value(PARAM_RAW, 'Name of the discussion'),
2300 'id' => new external_value(PARAM_INT, 'ID of the discussion'),
d9fedf3d
RW
2301 'timecreated' => new external_value(PARAM_INT, 'Timestamp of the discussion start'),
2302 'authorfullname' => new external_value(PARAM_RAW, 'Full name of the user that started the discussion'),
34e6dd24
MM
2303 'posts' => new external_single_structure([
2304 'userposts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
2305 'parentposts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
2306 ]),
2307 ])),
2308 'warnings' => new external_warnings(),
2309 ]);
2310 }
f3b08d99 2311
9a023ef3
JL
2312 /**
2313 * Returns description of method parameters
2314 *
2315 * @return external_function_parameters
2316 * @since Moodle 3.8
2317 */
2318 public static function get_discussion_post_parameters() {
2319 return new external_function_parameters(
2320 array(
2321 'postid' => new external_value(PARAM_INT, 'Post to fetch.'),
2322 )
2323 );
2324 }
f3b08d99 2325
9a023ef3
JL
2326 /**
2327 * Get a particular discussion post.
2328 *
2329 * @param int $postid post to fetch
2330 * @return array of post and warnings (if any)
2331 * @since Moodle 3.8
2332 * @throws moodle_exception
2333 */
2334 public static function get_discussion_post($postid) {
2335 global $USER, $CFG;
2336
2337 $params = self::validate_parameters(self::get_discussion_post_parameters(),
2338 array(
2339 'postid' => $postid,
2340 ));
2341 $warnings = array();
2342 $vaultfactory = mod_forum\local\container::get_vault_factory();
2343 $forumvault = $vaultfactory->get_forum_vault();
2344 $discussionvault = $vaultfactory->get_discussion_vault();
2345 $postvault = $vaultfactory->get_post_vault();
2346
2347 $postentity = $postvault->get_from_id($params['postid']);
2348 if (empty($postentity)) {
2349 throw new moodle_exception('invalidpostid', 'forum');
2350 }
2351 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
2352 if (empty($discussionentity)) {
2353 throw new moodle_exception('notpartofdiscussion', 'forum');
2354 }
2355 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
2356 if (empty($forumentity)) {
2357 throw new moodle_exception('invalidforumid', 'forum');
2358 }
2359 self::validate_context($forumentity->get_context());
2360
2361 $managerfactory = mod_forum\local\container::get_manager_factory();
2362 $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
2363
2364 if (!$capabilitymanager->can_view_post($USER, $discussionentity, $postentity)) {
2365 throw new moodle_exception('noviewdiscussionspermission', 'forum');
2366 }
2367
2368 $builderfactory = mod_forum\local\container::get_builder_factory();
2369 $postbuilder = $builderfactory->get_exported_posts_builder();
2370 $posts = $postbuilder->build($USER, [$forumentity], [$discussionentity], [$postentity]);
2371 $post = empty($posts) ? array() : reset($posts);
2372
2373 $result = array();
2374 $result['post'] = $post;
2375 $result['warnings'] = $warnings;
2376 return $result;
2377 }
f3b08d99 2378
9a023ef3
JL
2379 /**
2380 * Returns description of method result value
2381 *
2382 * @return external_description
2383 * @since Moodle 3.8
2384 */
2385 public static function get_discussion_post_returns() {
2386 return new external_single_structure(
2387 array(
2388 'post' => \mod_forum\local\exporters\post::get_read_structure(),
2389 'warnings' => new external_warnings(),
2390 )
2391 );
2392 }
131bfaa8
JL
2393
2394 /**
2395 * Returns description of method parameters
2396 *
2397 * @return external_function_parameters
2398 * @since Moodle 3.8
2399 */
2400 public static function prepare_draft_area_for_post_parameters() {
2401 return new external_function_parameters(
2402 array(
2403 'postid' => new external_value(PARAM_INT, 'Post to prepare the draft area for.'),
2404 'area' => new external_value(PARAM_ALPHA, 'Area to prepare: attachment or post.'),
2405 'draftitemid' => new external_value(PARAM_INT, 'The draft item id to use. 0 to generate one.',
2406 VALUE_DEFAULT, 0),
2407 'filestokeep' => new external_multiple_structure(
2408 new external_single_structure(
2409 array(
2410 'filename' => new external_value(PARAM_FILE, 'File name.'),
2411 'filepath' => new external_value(PARAM_PATH, 'File path.'),
2412 )
2413 ), 'Only keep these files in the draft file area. Empty for keeping all.', VALUE_DEFAULT, []
2414 ),
2415 )
2416 );
2417 }
f3b08d99 2418
131bfaa8
JL
2419 /**
2420 * Prepares a draft area for editing a post.
2421 *
2422 * @param int $postid post to prepare the draft area for
2423 * @param string $area area to prepare attachment or post
2424 * @param int $draftitemid the draft item id to use. 0 to generate a new one.
2425 * @param array $filestokeep only keep these files in the draft file area. Empty for keeping all.
2426 * @return array of files in the area, the area options and the draft item id
2427 * @since Moodle 3.8
2428 * @throws moodle_exception
2429 */
2430 public static function prepare_draft_area_for_post($postid, $area, $draftitemid = 0, $filestokeep = []) {
2431 global $USER;
2432
2433 $params = self::validate_parameters(
2434 self::prepare_draft_area_for_post_parameters(),
2435 array(
2436 'postid' => $postid,
2437 'area' => $area,
2438 'draftitemid' => $draftitemid,
2439 'filestokeep' => $filestokeep,
2440 )
2441 );
2442 $directionallowedvalues = ['ASC', 'DESC'];
2443
2444 $allowedareas = ['attachment', 'post'];
2445 if (!in_array($params['area'], $allowedareas)) {
2446 throw new invalid_parameter_exception('Invalid value for area parameter
2447 (value: ' . $params['area'] . '),' . 'allowed values are: ' . implode(', ', $allowedareas));
2448 }
2449
2450 $warnings = array();
2451 $vaultfactory = mod_forum\local\container::get_vault_factory();
2452 $forumvault = $vaultfactory->get_forum_vault();
2453 $discussionvault = $vaultfactory->get_discussion_vault();
2454 $postvault = $vaultfactory->get_post_vault();
2455
2456 $postentity = $postvault->get_from_id($params['postid']);
2457 if (empty($postentity)) {
2458 throw new moodle_exception('invalidpostid', 'forum');
2459 }
2460 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
2461 if (empty($discussionentity)) {
2462 throw new moodle_exception('notpartofdiscussion', 'forum');
2463 }
2464 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
2465 if (empty($forumentity)) {
2466 throw new moodle_exception('invalidforumid', 'forum');
2467 }
2468
2469 $context = $forumentity->get_context();
2470 self::validate_context($context);
2471
2472 $managerfactory = mod_forum\local\container::get_manager_factory();
2473 $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
2474
2475 if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) {
2476 throw new moodle_exception('noviewdiscussionspermission', 'forum');
2477 }
2478
2479 if ($params['area'] == 'attachment') {
2480 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
2481 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
2482 $forum = $forumdatamapper->to_legacy_object($forumentity);
2483
2484 $areaoptions = mod_forum_post_form::attachment_options($forum);
2485 $messagetext = null;
2486 } else {
2487 $areaoptions = mod_forum_post_form::editor_options($context, $postentity->get_id());
2488 $messagetext = $postentity->get_message();
2489 }
2490
2491 $draftitemid = empty($params['draftitemid']) ? 0 : $params['draftitemid'];
2492 $messagetext = file_prepare_draft_area($draftitemid, $context->id, 'mod_forum', $params['area'],
2493 $postentity->get_id(), $areaoptions, $messagetext);
2494
2495 // Just get a structure compatible with external API.
2496 array_walk($areaoptions, function(&$item, $key) {
2497 $item = ['name' => $key, 'value' => $item];
2498 });
2499
2500 // Do we need to keep only the given files?
2501 $usercontext = context_user::instance($USER->id);
2502 if (!empty($params['filestokeep'])) {
2503 $fs = get_file_storage();
2504
2505 if ($areafiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid)) {
2506 $filestokeep = [];
2507 foreach ($params['filestokeep'] as $ftokeep) {
2508 $filestokeep[$ftokeep['filepath']][$ftokeep['filename']] = $ftokeep;
2509 }
2510
2511 foreach ($areafiles as $file) {
2512 if ($file->is_directory()) {
2513 continue;
2514 }
2515 if (!isset($filestokeep[$file->get_filepath()][$file->get_filename()])) {
2516 $file->delete(); // Not in the list to be kept.
2517 }
2518 }
2519 }
2520 }
2521
2522 $result = array(
2523 'draftitemid' => $draftitemid,
2524 'files' => external_util::get_area_files($usercontext->id, 'user', 'draft',
2525 $draftitemid),
2526 'areaoptions' => $areaoptions,
2527 'messagetext' => $messagetext,
2528 'warnings' => $warnings,
2529 );
2530 return $result;
2531 }
f3b08d99 2532
131bfaa8
JL
2533 /**
2534 * Returns description of method result value
2535 *
2536 * @return external_description
2537 * @since Moodle 3.8
2538 */
2539 public static function prepare_draft_area_for_post_returns() {
2540 return new external_single_structure(
2541 array(
2542 'draftitemid' => new external_value(PARAM_INT, 'Draft item id for the file area.'),
2543 'files' => new external_files('Draft area files.', VALUE_OPTIONAL),
2544 'areaoptions' => new external_multiple_structure(
2545 new external_single_structure(
2546 array(
2547 'name' => new external_value(PARAM_RAW, 'Name of option.'),
2548 'value' => new external_value(PARAM_RAW, 'Value of option.'),
2549 )
2550 ), 'Draft file area options.'
2551 ),
2552 'messagetext' => new external_value(PARAM_RAW, 'Message text with URLs rewritten.'),
2553 'warnings' => new external_warnings(),
2554 )
2555 );
2556 }
0a063e6d
JL
2557
2558 /**
2559 * Returns description of method parameters
2560 *
2561 * @return external_function_parameters
2562 * @since Moodle 3.8
2563 */
2564 public static function update_discussion_post_parameters() {
2565 return new external_function_parameters(
2566 [
2567 'postid' => new external_value(PARAM_INT, 'Post to be updated. It can be a discussion topic post.'),
2568 'subject' => new external_value(PARAM_TEXT, 'Updated post subject', VALUE_DEFAULT, ''),
2569 'message' => new external_value(PARAM_RAW, 'Updated post message (HTML assumed if messageformat is not provided)',
2570 VALUE_DEFAULT, ''),
2571 'messageformat' => new external_format_value('message', VALUE_DEFAULT),
2572 'options' => new external_multiple_structure (
2573 new external_single_structure(
2574 [
2575 'name' => new external_value(
2576 PARAM_ALPHANUM,
2577 'The allowed keys (value format) are:
2578 pinned (bool); (only for discussions) whether to pin this discussion or not
2579 discussionsubscribe (bool); whether to subscribe to the post or not
2580 inlineattachmentsid (int); the draft file area id for inline attachments in the text
2581 attachmentsid (int); the draft file area id for attachments'
2582 ),
2583 'value' => new external_value(PARAM_RAW, 'The value of the option.')
2584 ]
2585 ),
2586 'Configuration options for the post.',
2587 VALUE_DEFAULT,
2588 []
2589 ),
2590 ]
2591 );
2592 }
2593
2594 /**
2595 * Updates a post or a discussion post topic.
2596 *
2597 * @param int $postid post to be updated, it can be a discussion topic post.
2598 * @param string $subject updated post subject
2599 * @param string $message updated post message (HTML assumed if messageformat is not provided)
2600 * @param int $messageformat The format of the message, defaults to FORMAT_HTML
2601 * @param array $options different configuration options for the post to be updated.
2602 * @return array of warnings and the status (true if the post/discussion was deleted)
2603 * @since Moodle 3.8
2604 * @throws moodle_exception
2605 * @todo support more options: timed posts, groups change and tags.
2606 */
2607 public static function update_discussion_post($postid, $subject = '', $message = '', $messageformat = FORMAT_HTML,
2608 $options = []) {
2609 global $CFG, $USER;
2610 require_once($CFG->dirroot . "/mod/forum/lib.php");
2611
2612 $params = self::validate_parameters(self::add_discussion_post_parameters(),
2613 [
2614 'postid' => $postid,
2615 'subject' => $subject,
2616 'message' => $message,
2617 'options' => $options,
2618 'messageformat' => $messageformat,
2619 ]
2620 );
2621 $warnings = [];
2622
2623 // Validate options.
2624 $options = [];
2625 foreach ($params['options'] as $option) {
2626 $name = trim($option['name']);
2627 switch ($name) {
2628 case 'pinned':
2629 $value = clean_param($option['value'], PARAM_BOOL);
2630 break;
2631 case 'discussionsubscribe':
2632 $value = clean_param($option['value'], PARAM_BOOL);
2633 break;
2634 case 'inlineattachmentsid':
2635 $value = clean_param($option['value'], PARAM_INT);
2636 break;
2637 case 'attachmentsid':
2638 $value = clean_param($option['value'], PARAM_INT);
2639 break;
2640 default:
2641 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
2642 }
2643 $options[$name] = $value;
2644 }
2645
2646 $managerfactory = mod_forum\local\container::get_manager_factory();
2647 $vaultfactory = mod_forum\local\container::get_vault_factory();
2648 $forumvault = $vaultfactory->get_forum_vault();
2649 $discussionvault = $vaultfactory->get_discussion_vault();
2650 $postvault = $vaultfactory->get_post_vault();
2651 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
2652 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
2653 $discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper();
2654 $postdatamapper = $legacydatamapperfactory->get_post_data_mapper();
2655
2656 $postentity = $postvault->get_from_id($params['postid']);
2657 if (empty($postentity)) {
2658 throw new moodle_exception('invalidpostid', 'forum');
2659 }
2660 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
2661 if (empty($discussionentity)) {
2662 throw new moodle_exception('notpartofdiscussion', 'forum');
2663 }
2664 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
2665 if (empty($forumentity)) {
2666 throw new moodle_exception('invalidforumid', 'forum');
2667 }
2668 $forum = $forumdatamapper->to_legacy_object($forumentity);
2669 $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
2670
2671 $modcontext = $forumentity->get_context();
2672 self::validate_context($modcontext);
2673
2674 if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) {
2675 throw new moodle_exception('cannotupdatepost', 'forum');
2676 }
2677
2678 // Get the original post.
2679 $updatepost = $postdatamapper->to_legacy_object($postentity);
2680 $updatepost->itemid = IGNORE_FILE_MERGE;
2681 $updatepost->attachments = IGNORE_FILE_MERGE;
2682
2683 // Prepare the post to be updated.
2684 if (!empty($params['subject'])) {
2685 $updatepost->subject = $params['subject'];
2686 }
2687
2688 if (!empty($params['message']) && !empty($params['messageformat'])) {
2689 $updatepost->message = $params['message'];
2690 $updatepost->messageformat = $params['messageformat'];
2691 $updatepost->messagetrust = trusttext_trusted($modcontext);
2692 // Clean message text.
2693 $updatepost = trusttext_pre_edit($updatepost, 'message', $modcontext);
2694 }
2695
2696 if (isset($options['discussionsubscribe'])) {
2697 // No need to validate anything here, forum_post_subscription will do.
2698 $updatepost->discussionsubscribe = $options['discussionsubscribe'];
2699 }
2700
2701 // When editing first post/discussion.
2702 if (!$postentity->has_parent()) {
2703 // Defaults for discussion topic posts.
2704 $updatepost->name = $discussionentity->get_name();
2705 $updatepost->timestart = $discussionentity->get_time_start();
2706 $updatepost->timeend = $discussionentity->get_time_end();
2707
2708 if (isset($options['pinned'])) {
2709 if ($capabilitymanager->can_pin_discussions($USER)) {
2710 // Can change pinned if we have capability.
2711 $updatepost->pinned = !empty($options['pinned']) ? FORUM_DISCUSSION_PINNED : FORUM_DISCUSSION_UNPINNED;
2712 }
2713 }
2714 }
2715
2716 if (isset($options['inlineattachmentsid'])) {
2717 $updatepost->itemid = $options['inlineattachmentsid'];
2718 }
2719
2720 if (isset($options['attachmentsid']) && forum_can_create_attachment($forum, $modcontext)) {
2721 $updatepost->attachments = $options['attachmentsid'];
2722 }
2723
2724 // Update the post.
2725 $fakemform = $updatepost->id;
2726 if (forum_update_post($updatepost, $fakemform)) {
2727 $discussion = $discussiondatamapper->to_legacy_object($discussionentity);
2728
2729 forum_trigger_post_updated_event($updatepost, $discussion, $modcontext, $forum);
2730
2731 forum_post_subscription(
2732 $updatepost,
2733 $forum,
2734 $discussion
2735 );
2736 $status = true;
2737 } else {
2738 $status = false;
2739 }
2740
2741 return [
2742 'status' => $status,
2743 'warnings' => $warnings,
2744 ];
2745 }
2746
2747 /**
2748 * Returns description of method result value
2749 *
2750 * @return external_description
2751 * @since Moodle 3.8
2752 */
2753 public static function update_discussion_post_returns() {
2754 return new external_single_structure(
2755 [
2756 'status' => new external_value(PARAM_BOOL, 'True if the post/discussion was updated, false otherwise.'),
2757 'warnings' => new external_warnings()
2758 ]
2759 );
2760 }
2b9fe87d 2761}