MDL-65032 mod_forum: Updates based on Jun's feedback
[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;
32 class mod_forum_external extends external_api {
34     /**
35      * Describes the parameters for get_forum.
36      *
37      * @return external_function_parameters
38      * @since Moodle 2.5
39      */
40     public static function get_forums_by_courses_parameters() {
41         return new external_function_parameters (
42             array(
43                 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID',
44                         VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of Course IDs', VALUE_DEFAULT, array()),
45             )
46         );
47     }
49     /**
50      * Returns a list of forums in a provided list of courses,
51      * if no list is provided all forums that the user can view
52      * will be returned.
53      *
54      * @param array $courseids the course ids
55      * @return array the forum details
56      * @since Moodle 2.5
57      */
58     public static function get_forums_by_courses($courseids = array()) {
59         global $CFG;
61         require_once($CFG->dirroot . "/mod/forum/lib.php");
63         $params = self::validate_parameters(self::get_forums_by_courses_parameters(), array('courseids' => $courseids));
65         $courses = array();
66         if (empty($params['courseids'])) {
67             $courses = enrol_get_my_courses();
68             $params['courseids'] = array_keys($courses);
69         }
71         // Array to store the forums to return.
72         $arrforums = array();
73         $warnings = array();
75         // Ensure there are courseids to loop through.
76         if (!empty($params['courseids'])) {
78             list($courses, $warnings) = external_util::validate_courses($params['courseids'], $courses);
80             // Get the forums in this course. This function checks users visibility permissions.
81             $forums = get_all_instances_in_courses("forum", $courses);
82             foreach ($forums as $forum) {
84                 $course = $courses[$forum->course];
85                 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
86                 $context = context_module::instance($cm->id);
88                 // Skip forums we are not allowed to see discussions.
89                 if (!has_capability('mod/forum:viewdiscussion', $context)) {
90                     continue;
91                 }
93                 $forum->name = external_format_string($forum->name, $context->id);
94                 // Format the intro before being returning using the format setting.
95                 list($forum->intro, $forum->introformat) = external_format_text($forum->intro, $forum->introformat,
96                                                                                 $context->id, 'mod_forum', 'intro', null);
97                 $forum->introfiles = external_util::get_area_files($context->id, 'mod_forum', 'intro', false, false);
98                 // Discussions count. This function does static request cache.
99                 $forum->numdiscussions = forum_count_discussions($forum, $cm, $course);
100                 $forum->cmid = $forum->coursemodule;
101                 $forum->cancreatediscussions = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
102                 $forum->istracked = forum_tp_is_tracked($forum);
103                 if ($forum->istracked) {
104                     $forum->unreadpostscount = forum_tp_count_forum_unread_posts($cm, $course);
105                 }
107                 // Add the forum to the array to return.
108                 $arrforums[$forum->id] = $forum;
109             }
110         }
112         return $arrforums;
113     }
115     /**
116      * Describes the get_forum return value.
117      *
118      * @return external_single_structure
119      * @since Moodle 2.5
120      */
121     public static function get_forums_by_courses_returns() {
122         return new external_multiple_structure(
123             new external_single_structure(
124                 array(
125                     'id' => new external_value(PARAM_INT, 'Forum id'),
126                     'course' => new external_value(PARAM_INT, 'Course id'),
127                     'type' => new external_value(PARAM_TEXT, 'The forum type'),
128                     'name' => new external_value(PARAM_RAW, 'Forum name'),
129                     'intro' => new external_value(PARAM_RAW, 'The forum intro'),
130                     'introformat' => new external_format_value('intro'),
131                     'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
132                     'duedate' => new external_value(PARAM_INT, 'duedate for the user', VALUE_OPTIONAL),
133                     'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user', VALUE_OPTIONAL),
134                     'assessed' => new external_value(PARAM_INT, 'Aggregate type'),
135                     'assesstimestart' => new external_value(PARAM_INT, 'Assess start time'),
136                     'assesstimefinish' => new external_value(PARAM_INT, 'Assess finish time'),
137                     'scale' => new external_value(PARAM_INT, 'Scale'),
138                     'maxbytes' => new external_value(PARAM_INT, 'Maximum attachment size'),
139                     'maxattachments' => new external_value(PARAM_INT, 'Maximum number of attachments'),
140                     'forcesubscribe' => new external_value(PARAM_INT, 'Force users to subscribe'),
141                     'trackingtype' => new external_value(PARAM_INT, 'Subscription mode'),
142                     'rsstype' => new external_value(PARAM_INT, 'RSS feed for this activity'),
143                     'rssarticles' => new external_value(PARAM_INT, 'Number of RSS recent articles'),
144                     'timemodified' => new external_value(PARAM_INT, 'Time modified'),
145                     'warnafter' => new external_value(PARAM_INT, 'Post threshold for warning'),
146                     'blockafter' => new external_value(PARAM_INT, 'Post threshold for blocking'),
147                     'blockperiod' => new external_value(PARAM_INT, 'Time period for blocking'),
148                     'completiondiscussions' => new external_value(PARAM_INT, 'Student must create discussions'),
149                     'completionreplies' => new external_value(PARAM_INT, 'Student must post replies'),
150                     'completionposts' => new external_value(PARAM_INT, 'Student must post discussions or replies'),
151                     'cmid' => new external_value(PARAM_INT, 'Course module id'),
152                     'numdiscussions' => new external_value(PARAM_INT, 'Number of discussions in the forum', VALUE_OPTIONAL),
153                     'cancreatediscussions' => new external_value(PARAM_BOOL, 'If the user can create discussions', VALUE_OPTIONAL),
154                     'lockdiscussionafter' => new external_value(PARAM_INT, 'After what period a discussion is locked', VALUE_OPTIONAL),
155                     'istracked' => new external_value(PARAM_BOOL, 'If the user is tracking the forum', VALUE_OPTIONAL),
156                     'unreadpostscount' => new external_value(PARAM_INT, 'The number of unread posts for tracked forums',
157                         VALUE_OPTIONAL),
158                 ), 'forum'
159             )
160         );
161     }
163     /**
164      * Get the forum posts in the specified discussion.
165      *
166      * @param   int $discussionid
167      * @param   string $sortby
168      * @param   string $sortdirection
169      * @return  array
170      */
171     public static function get_discussion_posts(int $discussionid, ?string $sortby, ?string $sortdirection) {
172         global $USER;
173         // Validate the parameter.
174         $params = self::validate_parameters(self::get_discussion_posts_parameters(), [
175                 'discussionid' => $discussionid,
176                 'sortby' => $sortby,
177                 'sortdirection' => $sortdirection,
178             ]);
179         $warnings = [];
181         $vaultfactory = mod_forum\local\container::get_vault_factory();
183         $discussionvault = $vaultfactory->get_discussion_vault();
184         $discussion = $discussionvault->get_from_id($params['discussionid']);
186         $forumvault = $vaultfactory->get_forum_vault();
187         $forum = $forumvault->get_from_id($discussion->get_forum_id());
189         $sortby = $params['sortby'];
190         $sortdirection = $params['sortdirection'];
191         $sortallowedvalues = ['id', 'created', 'modified'];
192         $directionallowedvalues = ['ASC', 'DESC'];
194         if (!in_array(strtolower($sortby), $sortallowedvalues)) {
195             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
196                 'allowed values are: ' . implode(', ', $sortallowedvalues));
197         }
199         $sortdirection = strtoupper($sortdirection);
200         if (!in_array($sortdirection, $directionallowedvalues)) {
201             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
202                 'allowed values are: ' . implode(',', $directionallowedvalues));
203         }
205         $managerfactory = mod_forum\local\container::get_manager_factory();
206         $capabilitymanager = $managerfactory->get_capability_manager($forum);
208         $postvault = $vaultfactory->get_post_vault();
209         $posts = $postvault->get_from_discussion_id(
210                 $USER,
211                 $discussion->get_id(),
212                 $capabilitymanager->can_view_any_private_reply($USER),
213                 "{$sortby} {$sortdirection}"
214             );
216         $builderfactory = mod_forum\local\container::get_builder_factory();
217         $postbuilder = $builderfactory->get_exported_posts_builder();
219         $legacydatamapper = mod_forum\local\container::get_legacy_data_mapper_factory();
221         return [
222             'posts' => $postbuilder->build($USER, [$forum], [$discussion], $posts),
223             'ratinginfo' => \core_rating\external\util::get_rating_info(
224                 $legacydatamapper->get_forum_data_mapper()->to_legacy_object($forum),
225                 $forum->get_context(),
226                 'mod_forum',
227                 'post',
228                 $legacydatamapper->get_post_data_mapper()->to_legacy_objects($posts)
229             ),
230             'warnings' => $warnings,
231         ];
232     }
234     /**
235      * Describe the post parameters.
236      *
237      * @return external_function_parameters
238      */
239     public static function get_discussion_posts_parameters() {
240         return new external_function_parameters ([
241             'discussionid' => new external_value(PARAM_INT, 'The ID of the discussion from which to fetch posts.', VALUE_REQUIRED),
242             'sortby' => new external_value(PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
243             'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
244         ]);
245     }
247     /**
248      * Describe the post return format.
249      *
250      * @return external_single_structure
251      */
252     public static function get_discussion_posts_returns() {
253         return new external_single_structure([
254             'posts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
255             'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
256             'warnings' => new external_warnings()
257         ]);
258     }
260     /**
261      * Describes the parameters for get_forum_discussion_posts.
262      *
263      * @return external_function_parameters
264      * @since Moodle 2.7
265      */
266     public static function get_forum_discussion_posts_parameters() {
267         return new external_function_parameters (
268             array(
269                 'discussionid' => new external_value(PARAM_INT, 'discussion ID', VALUE_REQUIRED),
270                 'sortby' => new external_value(PARAM_ALPHA,
271                     'sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
272                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
273             )
274         );
275     }
277     /**
278      * Returns a list of forum posts for a discussion
279      *
280      * @param int $discussionid the post ids
281      * @param string $sortby sort by this element (id, created or modified)
282      * @param string $sortdirection sort direction: ASC or DESC
283      *
284      * @return array the forum post details
285      * @since Moodle 2.7
286      * @todo MDL-65252 This will be removed in Moodle 4.1
287      */
288     public static function get_forum_discussion_posts($discussionid, $sortby = "created", $sortdirection = "DESC") {
289         global $CFG, $DB, $USER, $PAGE;
291         $posts = array();
292         $warnings = array();
294         // Validate the parameter.
295         $params = self::validate_parameters(self::get_forum_discussion_posts_parameters(),
296             array(
297                 'discussionid' => $discussionid,
298                 'sortby' => $sortby,
299                 'sortdirection' => $sortdirection));
301         // Compact/extract functions are not recommended.
302         $discussionid   = $params['discussionid'];
303         $sortby         = $params['sortby'];
304         $sortdirection  = $params['sortdirection'];
306         $sortallowedvalues = array('id', 'created', 'modified');
307         if (!in_array($sortby, $sortallowedvalues)) {
308             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
309                 'allowed values are: ' . implode(',', $sortallowedvalues));
310         }
312         $sortdirection = strtoupper($sortdirection);
313         $directionallowedvalues = array('ASC', 'DESC');
314         if (!in_array($sortdirection, $directionallowedvalues)) {
315             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
316                 'allowed values are: ' . implode(',', $directionallowedvalues));
317         }
319         $discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
320         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
321         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
322         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
324         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
325         $modcontext = context_module::instance($cm->id);
326         self::validate_context($modcontext);
328         // This require must be here, see mod/forum/discuss.php.
329         require_once($CFG->dirroot . "/mod/forum/lib.php");
331         // Check they have the view forum capability.
332         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
334         if (! $post = forum_get_post_full($discussion->firstpost)) {
335             throw new moodle_exception('notexists', 'forum');
336         }
338         // This function check groups, qanda, timed discussions, etc.
339         if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
340             throw new moodle_exception('noviewdiscussionspermission', 'forum');
341         }
343         $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
345         // We will add this field in the response.
346         $canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
348         $forumtracked = forum_tp_is_tracked($forum);
350         $sort = 'p.' . $sortby . ' ' . $sortdirection;
351         $allposts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
353         foreach ($allposts as $post) {
354             if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
355                 $warning = array();
356                 $warning['item'] = 'post';
357                 $warning['itemid'] = $post->id;
358                 $warning['warningcode'] = '1';
359                 $warning['message'] = 'You can\'t see this post';
360                 $warnings[] = $warning;
361                 continue;
362             }
364             // Function forum_get_all_discussion_posts adds postread field.
365             // Note that the value returned can be a boolean or an integer. The WS expects a boolean.
366             if (empty($post->postread)) {
367                 $post->postread = false;
368             } else {
369                 $post->postread = true;
370             }
372             $post->isprivatereply = !empty($post->privatereplyto);
374             $post->canreply = $canreply;
375             if (!empty($post->children)) {
376                 $post->children = array_keys($post->children);
377             } else {
378                 $post->children = array();
379             }
381             if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
382                 // The post is available, but has been marked as deleted.
383                 // It will still be available but filled with a placeholder.
384                 $post->userid = null;
385                 $post->userfullname = null;
386                 $post->userpictureurl = null;
388                 $post->subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
389                 $post->message = get_string('privacy:request:delete:post:message', 'mod_forum');
391                 $post->deleted = true;
392                 $posts[] = $post;
394                 continue;
395             }
396             $post->deleted = false;
398             if (forum_is_author_hidden($post, $forum)) {
399                 $post->userid = null;
400                 $post->userfullname = null;
401                 $post->userpictureurl = null;
402             } else {
403                 $user = new stdclass();
404                 $user->id = $post->userid;
405                 $user = username_load_fields_from_object($user, $post, null, array('picture', 'imagealt', 'email'));
406                 $post->userfullname = fullname($user, $canviewfullname);
408                 $userpicture = new user_picture($user);
409                 $userpicture->size = 1; // Size f1.
410                 $post->userpictureurl = $userpicture->get_url($PAGE)->out(false);
411             }
413             $post->subject = external_format_string($post->subject, $modcontext->id);
414             // Rewrite embedded images URLs.
415             list($post->message, $post->messageformat) =
416                 external_format_text($post->message, $post->messageformat, $modcontext->id, 'mod_forum', 'post', $post->id);
418             // List attachments.
419             if (!empty($post->attachment)) {
420                 $post->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment', $post->id);
421             }
422             $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $post->id);
423             if (!empty($messageinlinefiles)) {
424                 $post->messageinlinefiles = $messageinlinefiles;
425             }
426             // Post tags.
427             $post->tags = \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $post->id);
429             $posts[] = $post;
430         }
432         $result = array();
433         $result['posts'] = $posts;
434         $result['ratinginfo'] = \core_rating\external\util::get_rating_info($forum, $modcontext, 'mod_forum', 'post', $posts);
435         $result['warnings'] = $warnings;
436         return $result;
437     }
439     /**
440      * Describes the get_forum_discussion_posts return value.
441      *
442      * @return external_single_structure
443      * @since Moodle 2.7
444      */
445     public static function get_forum_discussion_posts_returns() {
446         return new external_single_structure(
447             array(
448                 'posts' => new external_multiple_structure(
449                         new external_single_structure(
450                             array(
451                                 'id' => new external_value(PARAM_INT, 'Post id'),
452                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
453                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
454                                 'userid' => new external_value(PARAM_INT, 'User id'),
455                                 'created' => new external_value(PARAM_INT, 'Creation time'),
456                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
457                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
458                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
459                                 'message' => new external_value(PARAM_RAW, 'The post message'),
460                                 'messageformat' => new external_format_value('message'),
461                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
462                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
463                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
464                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
465                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
466                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
467                                 'children' => new external_multiple_structure(new external_value(PARAM_INT, 'children post id')),
468                                 'canreply' => new external_value(PARAM_BOOL, 'The user can reply to posts?'),
469                                 'postread' => new external_value(PARAM_BOOL, 'The post was read'),
470                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
471                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL),
472                                 'deleted' => new external_value(PARAM_BOOL, 'This post has been removed.'),
473                                 'isprivatereply' => new external_value(PARAM_BOOL, 'The post is a private reply'),
474                                 'tags' => new external_multiple_structure(
475                                     \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
476                                 ),
477                             ), 'post'
478                         )
479                     ),
480                 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
481                 'warnings' => new external_warnings()
482             )
483         );
484     }
486     /**
487      * Mark the get_forum_discussion_posts web service as deprecated.
488      *
489      * @return  bool
490      */
491     public static function get_forum_discussion_posts_is_deprecated() {
492         return true;
493     }
495     /**
496      * Describes the parameters for get_forum_discussions_paginated.
497      *
498      * @return external_function_parameters
499      * @since Moodle 2.8
500      */
501     public static function get_forum_discussions_paginated_parameters() {
502         return new external_function_parameters (
503             array(
504                 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
505                 'sortby' => new external_value(PARAM_ALPHA,
506                     'sort by this element: id, timemodified, timestart or timeend', VALUE_DEFAULT, 'timemodified'),
507                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
508                 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
509                 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
510             )
511         );
512     }
514     /**
515      * Returns a list of forum discussions optionally sorted and paginated.
516      *
517      * @param int $forumid the forum instance id
518      * @param string $sortby sort by this element (id, timemodified, timestart or timeend)
519      * @param string $sortdirection sort direction: ASC or DESC
520      * @param int $page page number
521      * @param int $perpage items per page
522      *
523      * @return array the forum discussion details including warnings
524      * @since Moodle 2.8
525      */
526     public static function get_forum_discussions_paginated($forumid, $sortby = 'timemodified', $sortdirection = 'DESC',
527                                                     $page = -1, $perpage = 0) {
528         global $CFG, $DB, $USER, $PAGE;
530         require_once($CFG->dirroot . "/mod/forum/lib.php");
532         $warnings = array();
533         $discussions = array();
535         $params = self::validate_parameters(self::get_forum_discussions_paginated_parameters(),
536             array(
537                 'forumid' => $forumid,
538                 'sortby' => $sortby,
539                 'sortdirection' => $sortdirection,
540                 'page' => $page,
541                 'perpage' => $perpage
542             )
543         );
545         // Compact/extract functions are not recommended.
546         $forumid        = $params['forumid'];
547         $sortby         = $params['sortby'];
548         $sortdirection  = $params['sortdirection'];
549         $page           = $params['page'];
550         $perpage        = $params['perpage'];
552         $sortallowedvalues = array('id', 'timemodified', 'timestart', 'timeend');
553         if (!in_array($sortby, $sortallowedvalues)) {
554             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
555                 'allowed values are: ' . implode(',', $sortallowedvalues));
556         }
558         $sortdirection = strtoupper($sortdirection);
559         $directionallowedvalues = array('ASC', 'DESC');
560         if (!in_array($sortdirection, $directionallowedvalues)) {
561             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
562                 'allowed values are: ' . implode(',', $directionallowedvalues));
563         }
565         $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
566         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
567         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
569         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
570         $modcontext = context_module::instance($cm->id);
571         self::validate_context($modcontext);
573         // Check they have the view forum capability.
574         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
576         $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
577         $alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
579         if ($alldiscussions) {
580             $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
582             // Get the unreads array, this takes a forum id and returns data for all discussions.
583             $unreads = array();
584             if ($cantrack = forum_tp_can_track_forums($forum)) {
585                 if ($forumtracked = forum_tp_is_tracked($forum)) {
586                     $unreads = forum_get_discussions_unread($cm);
587                 }
588             }
589             // The forum function returns the replies for all the discussions in a given forum.
590             $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
591             $replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage, $canseeprivatereplies);
593             foreach ($alldiscussions as $discussion) {
595                 // This function checks for qanda forums.
596                 // Note that the forum_get_discussions returns as id the post id, not the discussion id so we need to do this.
597                 $discussionrec = clone $discussion;
598                 $discussionrec->id = $discussion->discussion;
599                 if (!forum_user_can_see_discussion($forum, $discussionrec, $modcontext)) {
600                     $warning = array();
601                     // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
602                     $warning['item'] = 'post';
603                     $warning['itemid'] = $discussion->id;
604                     $warning['warningcode'] = '1';
605                     $warning['message'] = 'You can\'t see this discussion';
606                     $warnings[] = $warning;
607                     continue;
608                 }
610                 $discussion->numunread = 0;
611                 if ($cantrack && $forumtracked) {
612                     if (isset($unreads[$discussion->discussion])) {
613                         $discussion->numunread = (int) $unreads[$discussion->discussion];
614                     }
615                 }
617                 $discussion->numreplies = 0;
618                 if (!empty($replies[$discussion->discussion])) {
619                     $discussion->numreplies = (int) $replies[$discussion->discussion]->replies;
620                 }
622                 $discussion->name = external_format_string($discussion->name, $modcontext->id);
623                 $discussion->subject = external_format_string($discussion->subject, $modcontext->id);
624                 // Rewrite embedded images URLs.
625                 list($discussion->message, $discussion->messageformat) =
626                     external_format_text($discussion->message, $discussion->messageformat,
627                                             $modcontext->id, 'mod_forum', 'post', $discussion->id);
629                 // List attachments.
630                 if (!empty($discussion->attachment)) {
631                     $discussion->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment',
632                                                                                 $discussion->id);
633                 }
634                 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $discussion->id);
635                 if (!empty($messageinlinefiles)) {
636                     $discussion->messageinlinefiles = $messageinlinefiles;
637                 }
639                 $discussion->timelocked = forum_discussion_is_locked($forum, $discussion);
640                 $discussion->canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
642                 if (forum_is_author_hidden($discussion, $forum)) {
643                     $discussion->userid = null;
644                     $discussion->userfullname = null;
645                     $discussion->userpictureurl = null;
647                     $discussion->usermodified = null;
648                     $discussion->usermodifiedfullname = null;
649                     $discussion->usermodifiedpictureurl = null;
650                 } else {
651                     $picturefields = explode(',', user_picture::fields());
653                     // Load user objects from the results of the query.
654                     $user = new stdclass();
655                     $user->id = $discussion->userid;
656                     $user = username_load_fields_from_object($user, $discussion, null, $picturefields);
657                     // Preserve the id, it can be modified by username_load_fields_from_object.
658                     $user->id = $discussion->userid;
659                     $discussion->userfullname = fullname($user, $canviewfullname);
661                     $userpicture = new user_picture($user);
662                     $userpicture->size = 1; // Size f1.
663                     $discussion->userpictureurl = $userpicture->get_url($PAGE)->out(false);
665                     $usermodified = new stdclass();
666                     $usermodified->id = $discussion->usermodified;
667                     $usermodified = username_load_fields_from_object($usermodified, $discussion, 'um', $picturefields);
668                     // Preserve the id (it can be overwritten due to the prefixed $picturefields).
669                     $usermodified->id = $discussion->usermodified;
670                     $discussion->usermodifiedfullname = fullname($usermodified, $canviewfullname);
672                     $userpicture = new user_picture($usermodified);
673                     $userpicture->size = 1; // Size f1.
674                     $discussion->usermodifiedpictureurl = $userpicture->get_url($PAGE)->out(false);
675                 }
677                 $discussions[] = $discussion;
678             }
679         }
681         $result = array();
682         $result['discussions'] = $discussions;
683         $result['warnings'] = $warnings;
684         return $result;
686     }
688     /**
689      * Describes the get_forum_discussions_paginated return value.
690      *
691      * @return external_single_structure
692      * @since Moodle 2.8
693      */
694     public static function get_forum_discussions_paginated_returns() {
695         return new external_single_structure(
696             array(
697                 'discussions' => new external_multiple_structure(
698                         new external_single_structure(
699                             array(
700                                 'id' => new external_value(PARAM_INT, 'Post id'),
701                                 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
702                                 'groupid' => new external_value(PARAM_INT, 'Group id'),
703                                 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
704                                 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
705                                 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
706                                 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
707                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
708                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
709                                 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
710                                 'created' => new external_value(PARAM_INT, 'Creation time'),
711                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
712                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
713                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
714                                 'message' => new external_value(PARAM_RAW, 'The post message'),
715                                 'messageformat' => new external_format_value('message'),
716                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
717                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
718                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
719                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
720                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
721                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
722                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
723                                 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
724                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
725                                 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
726                                 'numreplies' => new external_value(PARAM_TEXT, 'The number of replies in the discussion'),
727                                 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
728                                 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
729                                 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
730                                 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
731                             ), 'post'
732                         )
733                     ),
734                 'warnings' => new external_warnings()
735             )
736         );
737     }
739     /**
740      * Returns description of method parameters
741      *
742      * @return external_function_parameters
743      * @since Moodle 2.9
744      */
745     public static function view_forum_parameters() {
746         return new external_function_parameters(
747             array(
748                 'forumid' => new external_value(PARAM_INT, 'forum instance id')
749             )
750         );
751     }
753     /**
754      * Trigger the course module viewed event and update the module completion status.
755      *
756      * @param int $forumid the forum instance id
757      * @return array of warnings and status result
758      * @since Moodle 2.9
759      * @throws moodle_exception
760      */
761     public static function view_forum($forumid) {
762         global $DB, $CFG;
763         require_once($CFG->dirroot . "/mod/forum/lib.php");
765         $params = self::validate_parameters(self::view_forum_parameters(),
766                                             array(
767                                                 'forumid' => $forumid
768                                             ));
769         $warnings = array();
771         // Request and permission validation.
772         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
773         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
775         $context = context_module::instance($cm->id);
776         self::validate_context($context);
778         require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
780         // Call the forum/lib API.
781         forum_view($forum, $course, $cm, $context);
783         $result = array();
784         $result['status'] = true;
785         $result['warnings'] = $warnings;
786         return $result;
787     }
789     /**
790      * Returns description of method result value
791      *
792      * @return external_description
793      * @since Moodle 2.9
794      */
795     public static function view_forum_returns() {
796         return new external_single_structure(
797             array(
798                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
799                 'warnings' => new external_warnings()
800             )
801         );
802     }
804     /**
805      * Returns description of method parameters
806      *
807      * @return external_function_parameters
808      * @since Moodle 2.9
809      */
810     public static function view_forum_discussion_parameters() {
811         return new external_function_parameters(
812             array(
813                 'discussionid' => new external_value(PARAM_INT, 'discussion id')
814             )
815         );
816     }
818     /**
819      * Trigger the discussion viewed event.
820      *
821      * @param int $discussionid the discussion id
822      * @return array of warnings and status result
823      * @since Moodle 2.9
824      * @throws moodle_exception
825      */
826     public static function view_forum_discussion($discussionid) {
827         global $DB, $CFG, $USER;
828         require_once($CFG->dirroot . "/mod/forum/lib.php");
830         $params = self::validate_parameters(self::view_forum_discussion_parameters(),
831                                             array(
832                                                 'discussionid' => $discussionid
833                                             ));
834         $warnings = array();
836         $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
837         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
838         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
840         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
841         $modcontext = context_module::instance($cm->id);
842         self::validate_context($modcontext);
844         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
846         // Call the forum/lib API.
847         forum_discussion_view($modcontext, $forum, $discussion);
849         // Mark as read if required.
850         if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
851             forum_tp_mark_discussion_read($USER, $discussion->id);
852         }
854         $result = array();
855         $result['status'] = true;
856         $result['warnings'] = $warnings;
857         return $result;
858     }
860     /**
861      * Returns description of method result value
862      *
863      * @return external_description
864      * @since Moodle 2.9
865      */
866     public static function view_forum_discussion_returns() {
867         return new external_single_structure(
868             array(
869                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
870                 'warnings' => new external_warnings()
871             )
872         );
873     }
875     /**
876      * Returns description of method parameters
877      *
878      * @return external_function_parameters
879      * @since Moodle 3.0
880      */
881     public static function add_discussion_post_parameters() {
882         return new external_function_parameters(
883             array(
884                 'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
885                                                 (can be the initial discussion post'),
886                 'subject' => new external_value(PARAM_TEXT, 'new post subject'),
887                 'message' => new external_value(PARAM_RAW, 'new post message (only html format allowed)'),
888                 'options' => new external_multiple_structure (
889                     new external_single_structure(
890                         array(
891                             'name' => new external_value(PARAM_ALPHANUM,
892                                         'The allowed keys (value format) are:
893                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
894                                         private (bool); make this reply private to the author of the parent post, default to false.
895                                         inlineattachmentsid              (int); the draft file area id for inline attachments
896                                         attachmentsid       (int); the draft file area id for attachments
897                             '),
898                             'value' => new external_value(PARAM_RAW, 'the value of the option,
899                                                             this param is validated in the external function.'
900                         )
901                     )
902                 ), 'Options', VALUE_DEFAULT, array())
903             )
904         );
905     }
907     /**
908      * Create new posts into an existing discussion.
909      *
910      * @param int $postid the post id we are going to reply to
911      * @param string $subject new post subject
912      * @param string $message new post message (only html format allowed)
913      * @param array $options optional settings
914      * @return array of warnings and the new post id
915      * @since Moodle 3.0
916      * @throws moodle_exception
917      */
918     public static function add_discussion_post($postid, $subject, $message, $options = array()) {
919         global $CFG, $USER;
920         require_once($CFG->dirroot . "/mod/forum/lib.php");
922         // Get all the factories that are required.
923         $vaultfactory = mod_forum\local\container::get_vault_factory();
924         $entityfactory = mod_forum\local\container::get_entity_factory();
925         $datamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
926         $managerfactory = mod_forum\local\container::get_manager_factory();
927         $discussionvault = $vaultfactory->get_discussion_vault();
928         $forumvault = $vaultfactory->get_forum_vault();
929         $discussiondatamapper = $datamapperfactory->get_discussion_data_mapper();
930         $forumdatamapper = $datamapperfactory->get_forum_data_mapper();
932         $params = self::validate_parameters(self::add_discussion_post_parameters(),
933             array(
934                 'postid' => $postid,
935                 'subject' => $subject,
936                 'message' => $message,
937                 'options' => $options
938             )
939         );
941         $warnings = array();
943         if (!$parent = forum_get_post_full($params['postid'])) {
944             throw new moodle_exception('invalidparentpostid', 'forum');
945         }
947         if (!$discussion = $discussionvault->get_from_id($parent->discussion)) {
948             throw new moodle_exception('notpartofdiscussion', 'forum');
949         }
951         // Request and permission validation.
952         $forum = $forumvault->get_from_id($discussion->get_forum_id());
953         $capabilitymanager = $managerfactory->get_capability_manager($forum);
954         $course = $forum->get_course_record();
955         $cm = $forum->get_course_module_record();
957         $discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
958         $forumrecord = $forumdatamapper->to_legacy_object($forum);
959         $context = context_module::instance($cm->id);
960         self::validate_context($context);
962         // Validate options.
963         $options = array(
964             'discussionsubscribe' => true,
965             'private'             => false,
966             'inlineattachmentsid' => 0,
967             'attachmentsid' => null
968         );
969         foreach ($params['options'] as $option) {
970             $name = trim($option['name']);
971             switch ($name) {
972                 case 'discussionsubscribe':
973                     $value = clean_param($option['value'], PARAM_BOOL);
974                     break;
975                 case 'private':
976                     $value = clean_param($option['value'], PARAM_BOOL);
977                     break;
978                 case 'inlineattachmentsid':
979                     $value = clean_param($option['value'], PARAM_INT);
980                     break;
981                 case 'attachmentsid':
982                     $value = clean_param($option['value'], PARAM_INT);
983                     // Ensure that the user has permissions to create attachments.
984                     if (!has_capability('mod/forum:createattachment', $context)) {
985                         $value = 0;
986                     }
987                     break;
988                 default:
989                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
990             }
991             $options[$name] = $value;
992         }
994         if (!$capabilitymanager->can_post_in_discussion($USER, $discussion)) {
995             throw new moodle_exception('nopostforum', 'forum');
996         }
998         $thresholdwarning = forum_check_throttling($forumrecord, $cm);
999         forum_check_blocking_threshold($thresholdwarning);
1001         // Create the post.
1002         $post = new stdClass();
1003         $post->discussion = $discussion->get_id();
1004         $post->parent = $parent->id;
1005         $post->subject = $params['subject'];
1006         $post->message = $params['message'];
1007         $post->messageformat = FORMAT_HTML;   // Force formatting for now.
1008         $post->messagetrust = trusttext_trusted($context);
1009         $post->itemid = $options['inlineattachmentsid'];
1010         $post->attachments = $options['attachmentsid'];
1011         $post->isprivatereply = $options['private'];
1012         $post->deleted = 0;
1013         $fakemform = $post->attachments;
1014         if ($postid = forum_add_new_post($post, $fakemform)) {
1016             $post->id = $postid;
1018             // Trigger events and completion.
1019             $params = array(
1020                 'context' => $context,
1021                 'objectid' => $post->id,
1022                 'other' => array(
1023                     'discussionid' => $discussion->get_id(),
1024                     'forumid' => $forum->get_id(),
1025                     'forumtype' => $forum->get_type(),
1026                 )
1027             );
1028             $event = \mod_forum\event\post_created::create($params);
1029             $event->add_record_snapshot('forum_posts', $post);
1030             $event->add_record_snapshot('forum_discussions', $discussionrecord);
1031             $event->trigger();
1033             // Update completion state.
1034             $completion = new completion_info($course);
1035             if ($completion->is_enabled($cm) &&
1036                     ($forum->get_completion_replies() || $forum->get_completion_posts())) {
1037                 $completion->update_state($cm, COMPLETION_COMPLETE);
1038             }
1040             $settings = new stdClass();
1041             $settings->discussionsubscribe = $options['discussionsubscribe'];
1042             forum_post_subscription($settings, $forumrecord, $discussionrecord);
1043         } else {
1044             throw new moodle_exception('couldnotadd', 'forum');
1045         }
1047         $builderfactory = \mod_forum\local\container::get_builder_factory();
1048         $exportedpostsbuilder = $builderfactory->get_exported_posts_builder();
1049         $postentity = $entityfactory->get_post_from_stdClass($post);
1050         $exportedposts = $exportedpostsbuilder->build($USER, [$forum], [$discussion], [$postentity]);
1051         $exportedpost = $exportedposts[0];
1053         $message = [];
1054         $message[] = [
1055             'type' => 'success',
1056             'message' => get_string("postaddedsuccess", "forum")
1057         ];
1059         $message[] = [
1060             'type' => 'success',
1061             'message' => get_string("postaddedtimeleft", "forum", format_time($CFG->maxeditingtime))
1062         ];
1064         $result = array();
1065         $result['postid'] = $postid;
1066         $result['warnings'] = $warnings;
1067         $result['post'] = $exportedpost;
1068         $result['messages'] = $message;
1069         return $result;
1070     }
1072     /**
1073      * Returns description of method result value
1074      *
1075      * @return external_description
1076      * @since Moodle 3.0
1077      */
1078     public static function add_discussion_post_returns() {
1079         return new external_single_structure(
1080             array(
1081                 'postid' => new external_value(PARAM_INT, 'new post id'),
1082                 'warnings' => new external_warnings(),
1083                 'post' => post_exporter::get_read_structure(),
1084                 'messages' => new external_multiple_structure(
1085                     new external_single_structure(
1086                         array(
1087                             'type' => new external_value(PARAM_TEXT, "The classification to be used in the client side", VALUE_REQUIRED),
1088                             'message' => new external_value(PARAM_TEXT,'untranslated english message to explain the warning', VALUE_REQUIRED)
1089                         ), 'Messages'), 'list of warnings', VALUE_OPTIONAL
1090                 ),
1091                 //'alertmessage' => new external_value(PARAM_RAW, 'Success message to be displayed to the user.'),
1092             )
1093         );
1094     }
1096     /**
1097      * Returns description of method parameters
1098      *
1099      * @return external_function_parameters
1100      * @since Moodle 3.0
1101      */
1102     public static function add_discussion_parameters() {
1103         return new external_function_parameters(
1104             array(
1105                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1106                 'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
1107                 'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
1108                 'groupid' => new external_value(PARAM_INT, 'The group, default to 0', VALUE_DEFAULT, 0),
1109                 'options' => new external_multiple_structure (
1110                     new external_single_structure(
1111                         array(
1112                             'name' => new external_value(PARAM_ALPHANUM,
1113                                         'The allowed keys (value format) are:
1114                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
1115                                         discussionpinned    (bool); is the discussion pinned, default to false
1116                                         inlineattachmentsid              (int); the draft file area id for inline attachments
1117                                         attachmentsid       (int); the draft file area id for attachments
1118                             '),
1119                             'value' => new external_value(PARAM_RAW, 'The value of the option,
1120                                                             This param is validated in the external function.'
1121                         )
1122                     )
1123                 ), 'Options', VALUE_DEFAULT, array())
1124             )
1125         );
1126     }
1128     /**
1129      * Add a new discussion into an existing forum.
1130      *
1131      * @param int $forumid the forum instance id
1132      * @param string $subject new discussion subject
1133      * @param string $message new discussion message (only html format allowed)
1134      * @param int $groupid the user course group
1135      * @param array $options optional settings
1136      * @return array of warnings and the new discussion id
1137      * @since Moodle 3.0
1138      * @throws moodle_exception
1139      */
1140     public static function add_discussion($forumid, $subject, $message, $groupid = 0, $options = array()) {
1141         global $DB, $CFG;
1142         require_once($CFG->dirroot . "/mod/forum/lib.php");
1144         $params = self::validate_parameters(self::add_discussion_parameters(),
1145                                             array(
1146                                                 'forumid' => $forumid,
1147                                                 'subject' => $subject,
1148                                                 'message' => $message,
1149                                                 'groupid' => $groupid,
1150                                                 'options' => $options
1151                                             ));
1153         $warnings = array();
1155         // Request and permission validation.
1156         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1157         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1159         $context = context_module::instance($cm->id);
1160         self::validate_context($context);
1162         // Validate options.
1163         $options = array(
1164             'discussionsubscribe' => true,
1165             'discussionpinned' => false,
1166             'inlineattachmentsid' => 0,
1167             'attachmentsid' => null
1168         );
1169         foreach ($params['options'] as $option) {
1170             $name = trim($option['name']);
1171             switch ($name) {
1172                 case 'discussionsubscribe':
1173                     $value = clean_param($option['value'], PARAM_BOOL);
1174                     break;
1175                 case 'discussionpinned':
1176                     $value = clean_param($option['value'], PARAM_BOOL);
1177                     break;
1178                 case 'inlineattachmentsid':
1179                     $value = clean_param($option['value'], PARAM_INT);
1180                     break;
1181                 case 'attachmentsid':
1182                     $value = clean_param($option['value'], PARAM_INT);
1183                     // Ensure that the user has permissions to create attachments.
1184                     if (!has_capability('mod/forum:createattachment', $context)) {
1185                         $value = 0;
1186                     }
1187                     break;
1188                 default:
1189                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1190             }
1191             $options[$name] = $value;
1192         }
1194         // Normalize group.
1195         if (!groups_get_activity_groupmode($cm)) {
1196             // Groups not supported, force to -1.
1197             $groupid = -1;
1198         } else {
1199             // Check if we receive the default or and empty value for groupid,
1200             // in this case, get the group for the user in the activity.
1201             if (empty($params['groupid'])) {
1202                 $groupid = groups_get_activity_group($cm);
1203             } else {
1204                 // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
1205                 $groupid = $params['groupid'];
1206             }
1207         }
1209         if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
1210             throw new moodle_exception('cannotcreatediscussion', 'forum');
1211         }
1213         $thresholdwarning = forum_check_throttling($forum, $cm);
1214         forum_check_blocking_threshold($thresholdwarning);
1216         // Create the discussion.
1217         $discussion = new stdClass();
1218         $discussion->course = $course->id;
1219         $discussion->forum = $forum->id;
1220         $discussion->message = $params['message'];
1221         $discussion->messageformat = FORMAT_HTML;   // Force formatting for now.
1222         $discussion->messagetrust = trusttext_trusted($context);
1223         $discussion->itemid = $options['inlineattachmentsid'];
1224         $discussion->groupid = $groupid;
1225         $discussion->mailnow = 0;
1226         $discussion->subject = $params['subject'];
1227         $discussion->name = $discussion->subject;
1228         $discussion->timestart = 0;
1229         $discussion->timeend = 0;
1230         $discussion->timelocked = 0;
1231         $discussion->attachments = $options['attachmentsid'];
1233         if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
1234             $discussion->pinned = FORUM_DISCUSSION_PINNED;
1235         } else {
1236             $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
1237         }
1238         $fakemform = $options['attachmentsid'];
1239         if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
1241             $discussion->id = $discussionid;
1243             // Trigger events and completion.
1245             $params = array(
1246                 'context' => $context,
1247                 'objectid' => $discussion->id,
1248                 'other' => array(
1249                     'forumid' => $forum->id,
1250                 )
1251             );
1252             $event = \mod_forum\event\discussion_created::create($params);
1253             $event->add_record_snapshot('forum_discussions', $discussion);
1254             $event->trigger();
1256             $completion = new completion_info($course);
1257             if ($completion->is_enabled($cm) &&
1258                     ($forum->completiondiscussions || $forum->completionposts)) {
1259                 $completion->update_state($cm, COMPLETION_COMPLETE);
1260             }
1262             $settings = new stdClass();
1263             $settings->discussionsubscribe = $options['discussionsubscribe'];
1264             forum_post_subscription($settings, $forum, $discussion);
1265         } else {
1266             throw new moodle_exception('couldnotadd', 'forum');
1267         }
1269         $result = array();
1270         $result['discussionid'] = $discussionid;
1271         $result['warnings'] = $warnings;
1272         return $result;
1273     }
1275     /**
1276      * Returns description of method result value
1277      *
1278      * @return external_description
1279      * @since Moodle 3.0
1280      */
1281     public static function add_discussion_returns() {
1282         return new external_single_structure(
1283             array(
1284                 'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
1285                 'warnings' => new external_warnings()
1286             )
1287         );
1288     }
1290     /**
1291      * Returns description of method parameters
1292      *
1293      * @return external_function_parameters
1294      * @since Moodle 3.1
1295      */
1296     public static function can_add_discussion_parameters() {
1297         return new external_function_parameters(
1298             array(
1299                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1300                 'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1301                                                 Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1302             )
1303         );
1304     }
1306     /**
1307      * Check if the current user can add discussions in the given forum (and optionally for the given group).
1308      *
1309      * @param int $forumid the forum instance id
1310      * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1311      * @return array of warnings and the status (true if the user can add discussions)
1312      * @since Moodle 3.1
1313      * @throws moodle_exception
1314      */
1315     public static function can_add_discussion($forumid, $groupid = null) {
1316         global $DB, $CFG;
1317         require_once($CFG->dirroot . "/mod/forum/lib.php");
1319         $params = self::validate_parameters(self::can_add_discussion_parameters(),
1320                                             array(
1321                                                 'forumid' => $forumid,
1322                                                 'groupid' => $groupid,
1323                                             ));
1324         $warnings = array();
1326         // Request and permission validation.
1327         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1328         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1330         $context = context_module::instance($cm->id);
1331         self::validate_context($context);
1333         $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1335         $result = array();
1336         $result['status'] = $status;
1337         $result['canpindiscussions'] = has_capability('mod/forum:pindiscussions', $context);
1338         $result['cancreateattachment'] = forum_can_create_attachment($forum, $context);
1339         $result['warnings'] = $warnings;
1340         return $result;
1341     }
1343     /**
1344      * Returns description of method result value
1345      *
1346      * @return external_description
1347      * @since Moodle 3.1
1348      */
1349     public static function can_add_discussion_returns() {
1350         return new external_single_structure(
1351             array(
1352                 'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
1353                 'canpindiscussions' => new external_value(PARAM_BOOL, 'True if the user can pin discussions, false otherwise.',
1354                     VALUE_OPTIONAL),
1355                 'cancreateattachment' => new external_value(PARAM_BOOL, 'True if the user can add attachments, false otherwise.',
1356                     VALUE_OPTIONAL),
1357                 'warnings' => new external_warnings()
1358             )
1359         );
1360     }
1362     /**
1363      * Describes the parameters for get_forum_access_information.
1364      *
1365      * @return external_external_function_parameters
1366      * @since Moodle 3.7
1367      */
1368     public static function get_forum_access_information_parameters() {
1369         return new external_function_parameters (
1370             array(
1371                 'forumid' => new external_value(PARAM_INT, 'Forum instance id.')
1372             )
1373         );
1374     }
1376     /**
1377      * Return access information for a given forum.
1378      *
1379      * @param int $forumid forum instance id
1380      * @return array of warnings and the access information
1381      * @since Moodle 3.7
1382      * @throws  moodle_exception
1383      */
1384     public static function get_forum_access_information($forumid) {
1385         global $DB;
1387         $params = self::validate_parameters(self::get_forum_access_information_parameters(), array('forumid' => $forumid));
1389         // Request and permission validation.
1390         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1391         $cm = get_coursemodule_from_instance('forum', $forum->id);
1393         $context = context_module::instance($cm->id);
1394         self::validate_context($context);
1396         $result = array();
1397         // Return all the available capabilities.
1398         $capabilities = load_capability_def('mod_forum');
1399         foreach ($capabilities as $capname => $capdata) {
1400             // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1401             $field = 'can' . str_replace('mod/forum:', '', $capname);
1402             $result[$field] = has_capability($capname, $context);
1403         }
1405         $result['warnings'] = array();
1406         return $result;
1407     }
1409     /**
1410      * Describes the get_forum_access_information return value.
1411      *
1412      * @return external_single_structure
1413      * @since Moodle 3.7
1414      */
1415     public static function get_forum_access_information_returns() {
1417         $structure = array(
1418             'warnings' => new external_warnings()
1419         );
1421         $capabilities = load_capability_def('mod_forum');
1422         foreach ($capabilities as $capname => $capdata) {
1423             // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1424             $field = 'can' . str_replace('mod/forum:', '', $capname);
1425             $structure[$field] = new external_value(PARAM_BOOL, 'Whether the user has the capability ' . $capname . ' allowed.',
1426                 VALUE_OPTIONAL);
1427         }
1429         return new external_single_structure($structure);
1430     }
1432     /**
1433      * Set the subscription state.
1434      *
1435      * @param   int     $forumid
1436      * @param   int     $discussionid
1437      * @param   bool    $targetstate
1438      * @return  \stdClass
1439      */
1440     public static function set_subscription_state($forumid, $discussionid, $targetstate) {
1441         global $PAGE, $USER;
1443         $params = self::validate_parameters(self::set_subscription_state_parameters(), [
1444             'forumid' => $forumid,
1445             'discussionid' => $discussionid,
1446             'targetstate' => $targetstate
1447         ]);
1449         $vaultfactory = mod_forum\local\container::get_vault_factory();
1450         $forumvault = $vaultfactory->get_forum_vault();
1451         $forum = $forumvault->get_from_id($params['forumid']);
1452         $coursemodule = $forum->get_course_module_record();
1453         $context = $forum->get_context();
1455         self::validate_context($context);
1457         $discussionvault = $vaultfactory->get_discussion_vault();
1458         $discussion = $discussionvault->get_from_id($params['discussionid']);
1459         $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1461         $forumrecord = $legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
1462         $discussionrecord = $legacydatamapperfactory->get_discussion_data_mapper()->to_legacy_object($discussion);
1464         if (!\mod_forum\subscriptions::is_subscribable($forumrecord)) {
1465             // Nothing to do. We won't actually output any content here though.
1466             throw new \moodle_exception('cannotsubscribe', 'mod_forum');
1467         }
1469         $issubscribed = \mod_forum\subscriptions::is_subscribed(
1470             $USER->id,
1471             $forumrecord,
1472             $discussion->get_id(),
1473             $coursemodule
1474         );
1476         // If the current state doesn't equal the desired state then update the current
1477         // state to the desired state.
1478         if ($issubscribed != (bool) $params['targetstate']) {
1479             if ($params['targetstate']) {
1480                 \mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussionrecord, $context);
1481             } else {
1482                 \mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussionrecord, $context);
1483             }
1484         }
1486         $exporterfactory = mod_forum\local\container::get_exporter_factory();
1487         $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1488         return $exporter->export($PAGE->get_renderer('mod_forum'));
1489     }
1491     /**
1492      * Returns description of method parameters.
1493      *
1494      * @return external_function_parameters
1495      */
1496     public static function set_subscription_state_parameters() {
1497         return new external_function_parameters(
1498             [
1499                 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1500                 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1501                 'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1502             ]
1503         );
1504     }
1506     /**
1507      * Returns description of method result value.
1508      *
1509      * @return external_description
1510      */
1511     public static function set_subscription_state_returns() {
1512         return \mod_forum\local\exporters\discussion::get_read_structure();
1513     }
1515     /**
1516      * Set the lock state.
1517      *
1518      * @param   int     $forumid
1519      * @param   int     $discussionid
1520      * @param   string    $targetstate
1521      * @return  \stdClass
1522      */
1523     public static function set_lock_state($forumid, $discussionid, $targetstate) {
1524         global $DB, $PAGE, $USER;
1526         $params = self::validate_parameters(self::set_lock_state_parameters(), [
1527             'forumid' => $forumid,
1528             'discussionid' => $discussionid,
1529             'targetstate' => $targetstate
1530         ]);
1532         $vaultfactory = mod_forum\local\container::get_vault_factory();
1533         $forumvault = $vaultfactory->get_forum_vault();
1534         $forum = $forumvault->get_from_id($params['forumid']);
1536         $managerfactory = mod_forum\local\container::get_manager_factory();
1537         $capabilitymanager = $managerfactory->get_capability_manager($forum);
1538         if (!$capabilitymanager->can_manage_forum($USER)) {
1539             throw new moodle_exception('errorcannotlock', 'forum');
1540         }
1542         // If the targetstate(currentstate) is not 0 then it should be set to the current time.
1543         $lockedvalue = $targetstate ? 0 : time();
1544         self::validate_context($forum->get_context());
1546         $discussionvault = $vaultfactory->get_discussion_vault();
1547         $discussion = $discussionvault->get_from_id($params['discussionid']);
1549         // If the current state doesn't equal the desired state then update the current.
1550         // state to the desired state.
1551         $discussion->toggle_locked_state($lockedvalue);
1552         $response = $discussionvault->update_discussion($discussion);
1553         $discussion = !$response ? $response : $discussion;
1555         $exporterfactory = mod_forum\local\container::get_exporter_factory();
1556         $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1557         return $exporter->export($PAGE->get_renderer('mod_forum'));
1558     }
1560     /**
1561      * Returns description of method parameters.
1562      *
1563      * @return external_function_parameters
1564      */
1565     public static function set_lock_state_parameters() {
1566         return new external_function_parameters(
1567             [
1568                 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1569                 'discussionid' => new external_value(PARAM_INT, 'The discussion to lock / unlock'),
1570                 'targetstate' => new external_value(PARAM_INT, 'The timestamp for the lock state')
1571             ]
1572         );
1573     }
1575     /**
1576      * Returns description of method result value.
1577      *
1578      * @return external_description
1579      */
1580     public static function set_lock_state_returns() {
1581         return new external_single_structure([
1582                 'id' => new external_value(PARAM_INT, 'The discussion we are locking.'),
1583                 'locked' => new external_value(PARAM_BOOL, 'The locked state of the discussion.'),
1584                 'times' => new external_single_structure([
1585                     'locked' => new external_value(PARAM_INT, 'The locked time of the discussion.'),
1586                 ])
1587         ]);
1588     }