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