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