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