Merge branch 'MDL-64656-master' of git://github.com/jleyva/moodle
[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 class mod_forum_external extends external_api {
32     /**
33      * Describes the parameters for get_forum.
34      *
35      * @return external_function_parameters
36      * @since Moodle 2.5
37      */
38     public static function get_forums_by_courses_parameters() {
39         return new external_function_parameters (
40             array(
41                 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID',
42                         VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of Course IDs', VALUE_DEFAULT, array()),
43             )
44         );
45     }
47     /**
48      * Returns a list of forums in a provided list of courses,
49      * if no list is provided all forums that the user can view
50      * will be returned.
51      *
52      * @param array $courseids the course ids
53      * @return array the forum details
54      * @since Moodle 2.5
55      */
56     public static function get_forums_by_courses($courseids = array()) {
57         global $CFG;
59         require_once($CFG->dirroot . "/mod/forum/lib.php");
61         $params = self::validate_parameters(self::get_forums_by_courses_parameters(), array('courseids' => $courseids));
63         $courses = array();
64         if (empty($params['courseids'])) {
65             $courses = enrol_get_my_courses();
66             $params['courseids'] = array_keys($courses);
67         }
69         // Array to store the forums to return.
70         $arrforums = array();
71         $warnings = array();
73         // Ensure there are courseids to loop through.
74         if (!empty($params['courseids'])) {
76             list($courses, $warnings) = external_util::validate_courses($params['courseids'], $courses);
78             // Get the forums in this course. This function checks users visibility permissions.
79             $forums = get_all_instances_in_courses("forum", $courses);
80             foreach ($forums as $forum) {
82                 $course = $courses[$forum->course];
83                 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
84                 $context = context_module::instance($cm->id);
86                 // Skip forums we are not allowed to see discussions.
87                 if (!has_capability('mod/forum:viewdiscussion', $context)) {
88                     continue;
89                 }
91                 $forum->name = external_format_string($forum->name, $context->id);
92                 // Format the intro before being returning using the format setting.
93                 list($forum->intro, $forum->introformat) = external_format_text($forum->intro, $forum->introformat,
94                                                                                 $context->id, 'mod_forum', 'intro', null);
95                 $forum->introfiles = external_util::get_area_files($context->id, 'mod_forum', 'intro', false, false);
96                 // Discussions count. This function does static request cache.
97                 $forum->numdiscussions = forum_count_discussions($forum, $cm, $course);
98                 $forum->cmid = $forum->coursemodule;
99                 $forum->cancreatediscussions = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
100                 $forum->istracked = forum_tp_is_tracked($forum);
101                 if ($forum->istracked) {
102                     $forum->unreadpostscount = forum_tp_count_forum_unread_posts($cm, $course);
103                 }
105                 // Add the forum to the array to return.
106                 $arrforums[$forum->id] = $forum;
107             }
108         }
110         return $arrforums;
111     }
113     /**
114      * Describes the get_forum return value.
115      *
116      * @return external_single_structure
117      * @since Moodle 2.5
118      */
119     public static function get_forums_by_courses_returns() {
120         return new external_multiple_structure(
121             new external_single_structure(
122                 array(
123                     'id' => new external_value(PARAM_INT, 'Forum id'),
124                     'course' => new external_value(PARAM_INT, 'Course id'),
125                     'type' => new external_value(PARAM_TEXT, 'The forum type'),
126                     'name' => new external_value(PARAM_RAW, 'Forum name'),
127                     'intro' => new external_value(PARAM_RAW, 'The forum intro'),
128                     'introformat' => new external_format_value('intro'),
129                     'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
130                     'duedate' => new external_value(PARAM_INT, 'duedate for the user', VALUE_OPTIONAL),
131                     'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user', VALUE_OPTIONAL),
132                     'assessed' => new external_value(PARAM_INT, 'Aggregate type'),
133                     'assesstimestart' => new external_value(PARAM_INT, 'Assess start time'),
134                     'assesstimefinish' => new external_value(PARAM_INT, 'Assess finish time'),
135                     'scale' => new external_value(PARAM_INT, 'Scale'),
136                     'maxbytes' => new external_value(PARAM_INT, 'Maximum attachment size'),
137                     'maxattachments' => new external_value(PARAM_INT, 'Maximum number of attachments'),
138                     'forcesubscribe' => new external_value(PARAM_INT, 'Force users to subscribe'),
139                     'trackingtype' => new external_value(PARAM_INT, 'Subscription mode'),
140                     'rsstype' => new external_value(PARAM_INT, 'RSS feed for this activity'),
141                     'rssarticles' => new external_value(PARAM_INT, 'Number of RSS recent articles'),
142                     'timemodified' => new external_value(PARAM_INT, 'Time modified'),
143                     'warnafter' => new external_value(PARAM_INT, 'Post threshold for warning'),
144                     'blockafter' => new external_value(PARAM_INT, 'Post threshold for blocking'),
145                     'blockperiod' => new external_value(PARAM_INT, 'Time period for blocking'),
146                     'completiondiscussions' => new external_value(PARAM_INT, 'Student must create discussions'),
147                     'completionreplies' => new external_value(PARAM_INT, 'Student must post replies'),
148                     'completionposts' => new external_value(PARAM_INT, 'Student must post discussions or replies'),
149                     'cmid' => new external_value(PARAM_INT, 'Course module id'),
150                     'numdiscussions' => new external_value(PARAM_INT, 'Number of discussions in the forum', VALUE_OPTIONAL),
151                     'cancreatediscussions' => new external_value(PARAM_BOOL, 'If the user can create discussions', VALUE_OPTIONAL),
152                     'lockdiscussionafter' => new external_value(PARAM_INT, 'After what period a discussion is locked', VALUE_OPTIONAL),
153                     'istracked' => new external_value(PARAM_BOOL, 'If the user is tracking the forum', VALUE_OPTIONAL),
154                     'unreadpostscount' => new external_value(PARAM_INT, 'The number of unread posts for tracked forums',
155                         VALUE_OPTIONAL),
156                 ), 'forum'
157             )
158         );
159     }
161     /**
162      * Get the forum posts in the specified discussion.
163      *
164      * @param   int $discussionid
165      * @param   string $sortby
166      * @param   string $sortdirection
167      * @return  array
168      */
169     public static function get_discussion_posts(int $discussionid, ?string $sortby, ?string $sortdirection) {
170         global $USER;
171         // Validate the parameter.
172         $params = self::validate_parameters(self::get_discussion_posts_parameters(), [
173                 'discussionid' => $discussionid,
174                 'sortby' => $sortby,
175                 'sortdirection' => $sortdirection,
176             ]);
177         $warnings = [];
179         $vaultfactory = mod_forum\local\container::get_vault_factory();
181         $discussionvault = $vaultfactory->get_discussion_vault();
182         $discussion = $discussionvault->get_from_id($params['discussionid']);
184         $forumvault = $vaultfactory->get_forum_vault();
185         $forum = $forumvault->get_from_id($discussion->get_forum_id());
187         $sortby = $params['sortby'];
188         $sortdirection = $params['sortdirection'];
189         $sortallowedvalues = ['id', 'created', 'modified'];
190         $directionallowedvalues = ['ASC', 'DESC'];
192         if (!in_array(strtolower($sortby), $sortallowedvalues)) {
193             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
194                 'allowed values are: ' . implode(', ', $sortallowedvalues));
195         }
197         $sortdirection = strtoupper($sortdirection);
198         if (!in_array($sortdirection, $directionallowedvalues)) {
199             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
200                 'allowed values are: ' . implode(',', $directionallowedvalues));
201         }
203         $managerfactory = mod_forum\local\container::get_manager_factory();
204         $capabilitymanager = $managerfactory->get_capability_manager($forum);
206         $postvault = $vaultfactory->get_post_vault();
207         $posts = $postvault->get_from_discussion_id(
208                 $USER,
209                 $discussion->get_id(),
210                 $capabilitymanager->can_view_any_private_reply($USER),
211                 "{$sortby} {$sortdirection}"
212             );
214         $builderfactory = mod_forum\local\container::get_builder_factory();
215         $postbuilder = $builderfactory->get_exported_posts_builder();
217         $legacydatamapper = mod_forum\local\container::get_legacy_data_mapper_factory();
219         return [
220             'posts' => $postbuilder->build($USER, [$forum], [$discussion], $posts),
221             'ratinginfo' => \core_rating\external\util::get_rating_info(
222                 $legacydatamapper->get_forum_data_mapper()->to_legacy_object($forum),
223                 $forum->get_context(),
224                 'mod_forum',
225                 'post',
226                 $legacydatamapper->get_post_data_mapper()->to_legacy_objects($posts)
227             ),
228             'warnings' => $warnings,
229         ];
230     }
232     /**
233      * Describe the post parameters.
234      *
235      * @return external_function_parameters
236      */
237     public static function get_discussion_posts_parameters() {
238         return new external_function_parameters ([
239             'discussionid' => new external_value(PARAM_INT, 'The ID of the discussion from which to fetch posts.', VALUE_REQUIRED),
240             'sortby' => new external_value(PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
241             'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
242         ]);
243     }
245     /**
246      * Describe the post return format.
247      *
248      * @return external_single_structure
249      */
250     public static function get_discussion_posts_returns() {
251         return new external_single_structure([
252             'posts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
253             'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
254             'warnings' => new external_warnings()
255         ]);
256     }
258     /**
259      * Describes the parameters for get_forum_discussion_posts.
260      *
261      * @return external_function_parameters
262      * @since Moodle 2.7
263      */
264     public static function get_forum_discussion_posts_parameters() {
265         return new external_function_parameters (
266             array(
267                 'discussionid' => new external_value(PARAM_INT, 'discussion ID', VALUE_REQUIRED),
268                 'sortby' => new external_value(PARAM_ALPHA,
269                     'sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
270                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
271             )
272         );
273     }
275     /**
276      * Returns a list of forum posts for a discussion
277      *
278      * @param int $discussionid the post ids
279      * @param string $sortby sort by this element (id, created or modified)
280      * @param string $sortdirection sort direction: ASC or DESC
281      *
282      * @return array the forum post details
283      * @since Moodle 2.7
284      * @todo MDL-65252 This will be removed in Moodle 4.1
285      */
286     public static function get_forum_discussion_posts($discussionid, $sortby = "created", $sortdirection = "DESC") {
287         global $CFG, $DB, $USER, $PAGE;
289         $posts = array();
290         $warnings = array();
292         // Validate the parameter.
293         $params = self::validate_parameters(self::get_forum_discussion_posts_parameters(),
294             array(
295                 'discussionid' => $discussionid,
296                 'sortby' => $sortby,
297                 'sortdirection' => $sortdirection));
299         // Compact/extract functions are not recommended.
300         $discussionid   = $params['discussionid'];
301         $sortby         = $params['sortby'];
302         $sortdirection  = $params['sortdirection'];
304         $sortallowedvalues = array('id', 'created', 'modified');
305         if (!in_array($sortby, $sortallowedvalues)) {
306             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
307                 'allowed values are: ' . implode(',', $sortallowedvalues));
308         }
310         $sortdirection = strtoupper($sortdirection);
311         $directionallowedvalues = array('ASC', 'DESC');
312         if (!in_array($sortdirection, $directionallowedvalues)) {
313             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
314                 'allowed values are: ' . implode(',', $directionallowedvalues));
315         }
317         $discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
318         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
319         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
320         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
322         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
323         $modcontext = context_module::instance($cm->id);
324         self::validate_context($modcontext);
326         // This require must be here, see mod/forum/discuss.php.
327         require_once($CFG->dirroot . "/mod/forum/lib.php");
329         // Check they have the view forum capability.
330         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
332         if (! $post = forum_get_post_full($discussion->firstpost)) {
333             throw new moodle_exception('notexists', 'forum');
334         }
336         // This function check groups, qanda, timed discussions, etc.
337         if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
338             throw new moodle_exception('noviewdiscussionspermission', 'forum');
339         }
341         $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
343         // We will add this field in the response.
344         $canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
346         $forumtracked = forum_tp_is_tracked($forum);
348         $sort = 'p.' . $sortby . ' ' . $sortdirection;
349         $allposts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
351         foreach ($allposts as $post) {
352             if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
353                 $warning = array();
354                 $warning['item'] = 'post';
355                 $warning['itemid'] = $post->id;
356                 $warning['warningcode'] = '1';
357                 $warning['message'] = 'You can\'t see this post';
358                 $warnings[] = $warning;
359                 continue;
360             }
362             // Function forum_get_all_discussion_posts adds postread field.
363             // Note that the value returned can be a boolean or an integer. The WS expects a boolean.
364             if (empty($post->postread)) {
365                 $post->postread = false;
366             } else {
367                 $post->postread = true;
368             }
370             $post->isprivatereply = !empty($post->privatereplyto);
372             $post->canreply = $canreply;
373             if (!empty($post->children)) {
374                 $post->children = array_keys($post->children);
375             } else {
376                 $post->children = array();
377             }
379             if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
380                 // The post is available, but has been marked as deleted.
381                 // It will still be available but filled with a placeholder.
382                 $post->userid = null;
383                 $post->userfullname = null;
384                 $post->userpictureurl = null;
386                 $post->subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
387                 $post->message = get_string('privacy:request:delete:post:message', 'mod_forum');
389                 $post->deleted = true;
390                 $posts[] = $post;
392                 continue;
393             }
394             $post->deleted = false;
396             if (forum_is_author_hidden($post, $forum)) {
397                 $post->userid = null;
398                 $post->userfullname = null;
399                 $post->userpictureurl = null;
400             } else {
401                 $user = new stdclass();
402                 $user->id = $post->userid;
403                 $user = username_load_fields_from_object($user, $post, null, array('picture', 'imagealt', 'email'));
404                 $post->userfullname = fullname($user, $canviewfullname);
406                 $userpicture = new user_picture($user);
407                 $userpicture->size = 1; // Size f1.
408                 $post->userpictureurl = $userpicture->get_url($PAGE)->out(false);
409             }
411             $post->subject = external_format_string($post->subject, $modcontext->id);
412             // Rewrite embedded images URLs.
413             list($post->message, $post->messageformat) =
414                 external_format_text($post->message, $post->messageformat, $modcontext->id, 'mod_forum', 'post', $post->id);
416             // List attachments.
417             if (!empty($post->attachment)) {
418                 $post->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment', $post->id);
419             }
420             $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $post->id);
421             if (!empty($messageinlinefiles)) {
422                 $post->messageinlinefiles = $messageinlinefiles;
423             }
424             // Post tags.
425             $post->tags = \core_tag\external\util::get_item_tags('mod_forum', 'forum_posts', $post->id);
427             $posts[] = $post;
428         }
430         $result = array();
431         $result['posts'] = $posts;
432         $result['ratinginfo'] = \core_rating\external\util::get_rating_info($forum, $modcontext, 'mod_forum', 'post', $posts);
433         $result['warnings'] = $warnings;
434         return $result;
435     }
437     /**
438      * Describes the get_forum_discussion_posts return value.
439      *
440      * @return external_single_structure
441      * @since Moodle 2.7
442      */
443     public static function get_forum_discussion_posts_returns() {
444         return new external_single_structure(
445             array(
446                 'posts' => new external_multiple_structure(
447                         new external_single_structure(
448                             array(
449                                 'id' => new external_value(PARAM_INT, 'Post id'),
450                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
451                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
452                                 'userid' => new external_value(PARAM_INT, 'User id'),
453                                 'created' => new external_value(PARAM_INT, 'Creation time'),
454                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
455                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
456                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
457                                 'message' => new external_value(PARAM_RAW, 'The post message'),
458                                 'messageformat' => new external_format_value('message'),
459                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
460                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
461                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
462                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
463                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
464                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
465                                 'children' => new external_multiple_structure(new external_value(PARAM_INT, 'children post id')),
466                                 'canreply' => new external_value(PARAM_BOOL, 'The user can reply to posts?'),
467                                 'postread' => new external_value(PARAM_BOOL, 'The post was read'),
468                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
469                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL),
470                                 'deleted' => new external_value(PARAM_BOOL, 'This post has been removed.'),
471                                 'isprivatereply' => new external_value(PARAM_BOOL, 'The post is a private reply'),
472                                 'tags' => new external_multiple_structure(
473                                     \core_tag\external\tag_item_exporter::get_read_structure(), 'Tags', VALUE_OPTIONAL
474                                 ),
475                             ), 'post'
476                         )
477                     ),
478                 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
479                 'warnings' => new external_warnings()
480             )
481         );
482     }
484     /**
485      * Mark the get_forum_discussion_posts web service as deprecated.
486      *
487      * @return  bool
488      */
489     public static function get_forum_discussion_posts_is_deprecated() {
490         return true;
491     }
493     /**
494      * Describes the parameters for get_forum_discussions_paginated.
495      *
496      * @return external_function_parameters
497      * @since Moodle 2.8
498      */
499     public static function get_forum_discussions_paginated_parameters() {
500         return new external_function_parameters (
501             array(
502                 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
503                 'sortby' => new external_value(PARAM_ALPHA,
504                     'sort by this element: id, timemodified, timestart or timeend', VALUE_DEFAULT, 'timemodified'),
505                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
506                 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
507                 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
508             )
509         );
510     }
512     /**
513      * Returns a list of forum discussions optionally sorted and paginated.
514      *
515      * @param int $forumid the forum instance id
516      * @param string $sortby sort by this element (id, timemodified, timestart or timeend)
517      * @param string $sortdirection sort direction: ASC or DESC
518      * @param int $page page number
519      * @param int $perpage items per page
520      *
521      * @return array the forum discussion details including warnings
522      * @since Moodle 2.8
523      */
524     public static function get_forum_discussions_paginated($forumid, $sortby = 'timemodified', $sortdirection = 'DESC',
525                                                     $page = -1, $perpage = 0) {
526         global $CFG, $DB, $USER, $PAGE;
528         require_once($CFG->dirroot . "/mod/forum/lib.php");
530         $warnings = array();
531         $discussions = array();
533         $params = self::validate_parameters(self::get_forum_discussions_paginated_parameters(),
534             array(
535                 'forumid' => $forumid,
536                 'sortby' => $sortby,
537                 'sortdirection' => $sortdirection,
538                 'page' => $page,
539                 'perpage' => $perpage
540             )
541         );
543         // Compact/extract functions are not recommended.
544         $forumid        = $params['forumid'];
545         $sortby         = $params['sortby'];
546         $sortdirection  = $params['sortdirection'];
547         $page           = $params['page'];
548         $perpage        = $params['perpage'];
550         $sortallowedvalues = array('id', 'timemodified', 'timestart', 'timeend');
551         if (!in_array($sortby, $sortallowedvalues)) {
552             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
553                 'allowed values are: ' . implode(',', $sortallowedvalues));
554         }
556         $sortdirection = strtoupper($sortdirection);
557         $directionallowedvalues = array('ASC', 'DESC');
558         if (!in_array($sortdirection, $directionallowedvalues)) {
559             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
560                 'allowed values are: ' . implode(',', $directionallowedvalues));
561         }
563         $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
564         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
565         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
567         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
568         $modcontext = context_module::instance($cm->id);
569         self::validate_context($modcontext);
571         // Check they have the view forum capability.
572         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
574         $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
575         $alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
577         if ($alldiscussions) {
578             $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
580             // Get the unreads array, this takes a forum id and returns data for all discussions.
581             $unreads = array();
582             if ($cantrack = forum_tp_can_track_forums($forum)) {
583                 if ($forumtracked = forum_tp_is_tracked($forum)) {
584                     $unreads = forum_get_discussions_unread($cm);
585                 }
586             }
587             // The forum function returns the replies for all the discussions in a given forum.
588             $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
589             $replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage, $canseeprivatereplies);
591             foreach ($alldiscussions as $discussion) {
593                 // This function checks for qanda forums.
594                 // Note that the forum_get_discussions returns as id the post id, not the discussion id so we need to do this.
595                 $discussionrec = clone $discussion;
596                 $discussionrec->id = $discussion->discussion;
597                 if (!forum_user_can_see_discussion($forum, $discussionrec, $modcontext)) {
598                     $warning = array();
599                     // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
600                     $warning['item'] = 'post';
601                     $warning['itemid'] = $discussion->id;
602                     $warning['warningcode'] = '1';
603                     $warning['message'] = 'You can\'t see this discussion';
604                     $warnings[] = $warning;
605                     continue;
606                 }
608                 $discussion->numunread = 0;
609                 if ($cantrack && $forumtracked) {
610                     if (isset($unreads[$discussion->discussion])) {
611                         $discussion->numunread = (int) $unreads[$discussion->discussion];
612                     }
613                 }
615                 $discussion->numreplies = 0;
616                 if (!empty($replies[$discussion->discussion])) {
617                     $discussion->numreplies = (int) $replies[$discussion->discussion]->replies;
618                 }
620                 $discussion->name = external_format_string($discussion->name, $modcontext->id);
621                 $discussion->subject = external_format_string($discussion->subject, $modcontext->id);
622                 // Rewrite embedded images URLs.
623                 list($discussion->message, $discussion->messageformat) =
624                     external_format_text($discussion->message, $discussion->messageformat,
625                                             $modcontext->id, 'mod_forum', 'post', $discussion->id);
627                 // List attachments.
628                 if (!empty($discussion->attachment)) {
629                     $discussion->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment',
630                                                                                 $discussion->id);
631                 }
632                 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $discussion->id);
633                 if (!empty($messageinlinefiles)) {
634                     $discussion->messageinlinefiles = $messageinlinefiles;
635                 }
637                 $discussion->locked = forum_discussion_is_locked($forum, $discussion);
638                 $discussion->canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
640                 if (forum_is_author_hidden($discussion, $forum)) {
641                     $discussion->userid = null;
642                     $discussion->userfullname = null;
643                     $discussion->userpictureurl = null;
645                     $discussion->usermodified = null;
646                     $discussion->usermodifiedfullname = null;
647                     $discussion->usermodifiedpictureurl = null;
648                 } else {
649                     $picturefields = explode(',', user_picture::fields());
651                     // Load user objects from the results of the query.
652                     $user = new stdclass();
653                     $user->id = $discussion->userid;
654                     $user = username_load_fields_from_object($user, $discussion, null, $picturefields);
655                     // Preserve the id, it can be modified by username_load_fields_from_object.
656                     $user->id = $discussion->userid;
657                     $discussion->userfullname = fullname($user, $canviewfullname);
659                     $userpicture = new user_picture($user);
660                     $userpicture->size = 1; // Size f1.
661                     $discussion->userpictureurl = $userpicture->get_url($PAGE)->out(false);
663                     $usermodified = new stdclass();
664                     $usermodified->id = $discussion->usermodified;
665                     $usermodified = username_load_fields_from_object($usermodified, $discussion, 'um', $picturefields);
666                     // Preserve the id (it can be overwritten due to the prefixed $picturefields).
667                     $usermodified->id = $discussion->usermodified;
668                     $discussion->usermodifiedfullname = fullname($usermodified, $canviewfullname);
670                     $userpicture = new user_picture($usermodified);
671                     $userpicture->size = 1; // Size f1.
672                     $discussion->usermodifiedpictureurl = $userpicture->get_url($PAGE)->out(false);
673                 }
675                 $discussions[] = $discussion;
676             }
677         }
679         $result = array();
680         $result['discussions'] = $discussions;
681         $result['warnings'] = $warnings;
682         return $result;
684     }
686     /**
687      * Describes the get_forum_discussions_paginated return value.
688      *
689      * @return external_single_structure
690      * @since Moodle 2.8
691      */
692     public static function get_forum_discussions_paginated_returns() {
693         return new external_single_structure(
694             array(
695                 'discussions' => new external_multiple_structure(
696                         new external_single_structure(
697                             array(
698                                 'id' => new external_value(PARAM_INT, 'Post id'),
699                                 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
700                                 'groupid' => new external_value(PARAM_INT, 'Group id'),
701                                 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
702                                 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
703                                 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
704                                 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
705                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
706                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
707                                 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
708                                 'created' => new external_value(PARAM_INT, 'Creation time'),
709                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
710                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
711                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
712                                 'message' => new external_value(PARAM_RAW, 'The post message'),
713                                 'messageformat' => new external_format_value('message'),
714                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
715                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
716                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
717                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
718                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
719                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
720                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
721                                 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
722                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
723                                 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
724                                 'numreplies' => new external_value(PARAM_TEXT, 'The number of replies in the discussion'),
725                                 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
726                                 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
727                                 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
728                                 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
729                             ), 'post'
730                         )
731                     ),
732                 'warnings' => new external_warnings()
733             )
734         );
735     }
737     /**
738      * Returns description of method parameters
739      *
740      * @return external_function_parameters
741      * @since Moodle 2.9
742      */
743     public static function view_forum_parameters() {
744         return new external_function_parameters(
745             array(
746                 'forumid' => new external_value(PARAM_INT, 'forum instance id')
747             )
748         );
749     }
751     /**
752      * Trigger the course module viewed event and update the module completion status.
753      *
754      * @param int $forumid the forum instance id
755      * @return array of warnings and status result
756      * @since Moodle 2.9
757      * @throws moodle_exception
758      */
759     public static function view_forum($forumid) {
760         global $DB, $CFG;
761         require_once($CFG->dirroot . "/mod/forum/lib.php");
763         $params = self::validate_parameters(self::view_forum_parameters(),
764                                             array(
765                                                 'forumid' => $forumid
766                                             ));
767         $warnings = array();
769         // Request and permission validation.
770         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
771         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
773         $context = context_module::instance($cm->id);
774         self::validate_context($context);
776         require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
778         // Call the forum/lib API.
779         forum_view($forum, $course, $cm, $context);
781         $result = array();
782         $result['status'] = true;
783         $result['warnings'] = $warnings;
784         return $result;
785     }
787     /**
788      * Returns description of method result value
789      *
790      * @return external_description
791      * @since Moodle 2.9
792      */
793     public static function view_forum_returns() {
794         return new external_single_structure(
795             array(
796                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
797                 'warnings' => new external_warnings()
798             )
799         );
800     }
802     /**
803      * Returns description of method parameters
804      *
805      * @return external_function_parameters
806      * @since Moodle 2.9
807      */
808     public static function view_forum_discussion_parameters() {
809         return new external_function_parameters(
810             array(
811                 'discussionid' => new external_value(PARAM_INT, 'discussion id')
812             )
813         );
814     }
816     /**
817      * Trigger the discussion viewed event.
818      *
819      * @param int $discussionid the discussion id
820      * @return array of warnings and status result
821      * @since Moodle 2.9
822      * @throws moodle_exception
823      */
824     public static function view_forum_discussion($discussionid) {
825         global $DB, $CFG, $USER;
826         require_once($CFG->dirroot . "/mod/forum/lib.php");
828         $params = self::validate_parameters(self::view_forum_discussion_parameters(),
829                                             array(
830                                                 'discussionid' => $discussionid
831                                             ));
832         $warnings = array();
834         $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
835         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
836         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
838         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
839         $modcontext = context_module::instance($cm->id);
840         self::validate_context($modcontext);
842         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
844         // Call the forum/lib API.
845         forum_discussion_view($modcontext, $forum, $discussion);
847         // Mark as read if required.
848         if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
849             forum_tp_mark_discussion_read($USER, $discussion->id);
850         }
852         $result = array();
853         $result['status'] = true;
854         $result['warnings'] = $warnings;
855         return $result;
856     }
858     /**
859      * Returns description of method result value
860      *
861      * @return external_description
862      * @since Moodle 2.9
863      */
864     public static function view_forum_discussion_returns() {
865         return new external_single_structure(
866             array(
867                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
868                 'warnings' => new external_warnings()
869             )
870         );
871     }
873     /**
874      * Returns description of method parameters
875      *
876      * @return external_function_parameters
877      * @since Moodle 3.0
878      */
879     public static function add_discussion_post_parameters() {
880         return new external_function_parameters(
881             array(
882                 'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
883                                                 (can be the initial discussion post'),
884                 'subject' => new external_value(PARAM_TEXT, 'new post subject'),
885                 'message' => new external_value(PARAM_RAW, 'new post message (only html format allowed)'),
886                 'options' => new external_multiple_structure (
887                     new external_single_structure(
888                         array(
889                             'name' => new external_value(PARAM_ALPHANUM,
890                                         'The allowed keys (value format) are:
891                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
892                                         private (bool); make this reply private to the author of the parent post, default to false.
893                                         inlineattachmentsid              (int); the draft file area id for inline attachments
894                                         attachmentsid       (int); the draft file area id for attachments
895                             '),
896                             'value' => new external_value(PARAM_RAW, 'the value of the option,
897                                                             this param is validated in the external function.'
898                         )
899                     )
900                 ), 'Options', VALUE_DEFAULT, array())
901             )
902         );
903     }
905     /**
906      * Create new posts into an existing discussion.
907      *
908      * @param int $postid the post id we are going to reply to
909      * @param string $subject new post subject
910      * @param string $message new post message (only html format allowed)
911      * @param array $options optional settings
912      * @return array of warnings and the new post id
913      * @since Moodle 3.0
914      * @throws moodle_exception
915      */
916     public static function add_discussion_post($postid, $subject, $message, $options = array()) {
917         global $DB, $CFG, $USER;
918         require_once($CFG->dirroot . "/mod/forum/lib.php");
920         $params = self::validate_parameters(self::add_discussion_post_parameters(),
921             array(
922                 'postid' => $postid,
923                 'subject' => $subject,
924                 'message' => $message,
925                 'options' => $options
926             )
927         );
929         $warnings = array();
931         if (!$parent = forum_get_post_full($params['postid'])) {
932             throw new moodle_exception('invalidparentpostid', 'forum');
933         }
935         if (!$discussion = $DB->get_record("forum_discussions", array("id" => $parent->discussion))) {
936             throw new moodle_exception('notpartofdiscussion', 'forum');
937         }
939         // Request and permission validation.
940         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
941         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
943         $context = context_module::instance($cm->id);
944         self::validate_context($context);
946         // Validate options.
947         $options = array(
948             'discussionsubscribe' => true,
949             'private'             => false,
950             'inlineattachmentsid' => 0,
951             'attachmentsid' => null
952         );
953         foreach ($params['options'] as $option) {
954             $name = trim($option['name']);
955             switch ($name) {
956                 case 'discussionsubscribe':
957                     $value = clean_param($option['value'], PARAM_BOOL);
958                     break;
959                 case 'private':
960                     $value = clean_param($option['value'], PARAM_BOOL);
961                     break;
962                 case 'inlineattachmentsid':
963                     $value = clean_param($option['value'], PARAM_INT);
964                     break;
965                 case 'attachmentsid':
966                     $value = clean_param($option['value'], PARAM_INT);
967                     // Ensure that the user has permissions to create attachments.
968                     if (!has_capability('mod/forum:createattachment', $context)) {
969                         $value = 0;
970                     }
971                     break;
972                 default:
973                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
974             }
975             $options[$name] = $value;
976         }
978         if (!forum_user_can_post($forum, $discussion, $USER, $cm, $course, $context)) {
979             throw new moodle_exception('nopostforum', 'forum');
980         }
982         $thresholdwarning = forum_check_throttling($forum, $cm);
983         forum_check_blocking_threshold($thresholdwarning);
985         // Create the post.
986         $post = new stdClass();
987         $post->discussion = $discussion->id;
988         $post->parent = $parent->id;
989         $post->subject = $params['subject'];
990         $post->message = $params['message'];
991         $post->messageformat = FORMAT_HTML;   // Force formatting for now.
992         $post->messagetrust = trusttext_trusted($context);
993         $post->itemid = $options['inlineattachmentsid'];
994         $post->attachments = $options['attachmentsid'];
995         $post->isprivatereply = $options['private'];
996         $post->deleted = 0;
997         $fakemform = $post->attachments;
998         if ($postid = forum_add_new_post($post, $fakemform)) {
1000             $post->id = $postid;
1002             // Trigger events and completion.
1003             $params = array(
1004                 'context' => $context,
1005                 'objectid' => $post->id,
1006                 'other' => array(
1007                     'discussionid' => $discussion->id,
1008                     'forumid' => $forum->id,
1009                     'forumtype' => $forum->type,
1010                 )
1011             );
1012             $event = \mod_forum\event\post_created::create($params);
1013             $event->add_record_snapshot('forum_posts', $post);
1014             $event->add_record_snapshot('forum_discussions', $discussion);
1015             $event->trigger();
1017             // Update completion state.
1018             $completion = new completion_info($course);
1019             if ($completion->is_enabled($cm) &&
1020                     ($forum->completionreplies || $forum->completionposts)) {
1021                 $completion->update_state($cm, COMPLETION_COMPLETE);
1022             }
1024             $settings = new stdClass();
1025             $settings->discussionsubscribe = $options['discussionsubscribe'];
1026             forum_post_subscription($settings, $forum, $discussion);
1027         } else {
1028             throw new moodle_exception('couldnotadd', 'forum');
1029         }
1031         $result = array();
1032         $result['postid'] = $postid;
1033         $result['warnings'] = $warnings;
1034         return $result;
1035     }
1037     /**
1038      * Returns description of method result value
1039      *
1040      * @return external_description
1041      * @since Moodle 3.0
1042      */
1043     public static function add_discussion_post_returns() {
1044         return new external_single_structure(
1045             array(
1046                 'postid' => new external_value(PARAM_INT, 'new post id'),
1047                 'warnings' => new external_warnings()
1048             )
1049         );
1050     }
1052     /**
1053      * Returns description of method parameters
1054      *
1055      * @return external_function_parameters
1056      * @since Moodle 3.0
1057      */
1058     public static function add_discussion_parameters() {
1059         return new external_function_parameters(
1060             array(
1061                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1062                 'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
1063                 'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
1064                 'groupid' => new external_value(PARAM_INT, 'The group, default to 0', VALUE_DEFAULT, 0),
1065                 'options' => new external_multiple_structure (
1066                     new external_single_structure(
1067                         array(
1068                             'name' => new external_value(PARAM_ALPHANUM,
1069                                         'The allowed keys (value format) are:
1070                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
1071                                         discussionpinned    (bool); is the discussion pinned, default to false
1072                                         inlineattachmentsid              (int); the draft file area id for inline attachments
1073                                         attachmentsid       (int); the draft file area id for attachments
1074                             '),
1075                             'value' => new external_value(PARAM_RAW, 'The value of the option,
1076                                                             This param is validated in the external function.'
1077                         )
1078                     )
1079                 ), 'Options', VALUE_DEFAULT, array())
1080             )
1081         );
1082     }
1084     /**
1085      * Add a new discussion into an existing forum.
1086      *
1087      * @param int $forumid the forum instance id
1088      * @param string $subject new discussion subject
1089      * @param string $message new discussion message (only html format allowed)
1090      * @param int $groupid the user course group
1091      * @param array $options optional settings
1092      * @return array of warnings and the new discussion id
1093      * @since Moodle 3.0
1094      * @throws moodle_exception
1095      */
1096     public static function add_discussion($forumid, $subject, $message, $groupid = 0, $options = array()) {
1097         global $DB, $CFG;
1098         require_once($CFG->dirroot . "/mod/forum/lib.php");
1100         $params = self::validate_parameters(self::add_discussion_parameters(),
1101                                             array(
1102                                                 'forumid' => $forumid,
1103                                                 'subject' => $subject,
1104                                                 'message' => $message,
1105                                                 'groupid' => $groupid,
1106                                                 'options' => $options
1107                                             ));
1109         $warnings = array();
1111         // Request and permission validation.
1112         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1113         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1115         $context = context_module::instance($cm->id);
1116         self::validate_context($context);
1118         // Validate options.
1119         $options = array(
1120             'discussionsubscribe' => true,
1121             'discussionpinned' => false,
1122             'inlineattachmentsid' => 0,
1123             'attachmentsid' => null
1124         );
1125         foreach ($params['options'] as $option) {
1126             $name = trim($option['name']);
1127             switch ($name) {
1128                 case 'discussionsubscribe':
1129                     $value = clean_param($option['value'], PARAM_BOOL);
1130                     break;
1131                 case 'discussionpinned':
1132                     $value = clean_param($option['value'], PARAM_BOOL);
1133                     break;
1134                 case 'inlineattachmentsid':
1135                     $value = clean_param($option['value'], PARAM_INT);
1136                     break;
1137                 case 'attachmentsid':
1138                     $value = clean_param($option['value'], PARAM_INT);
1139                     // Ensure that the user has permissions to create attachments.
1140                     if (!has_capability('mod/forum:createattachment', $context)) {
1141                         $value = 0;
1142                     }
1143                     break;
1144                 default:
1145                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1146             }
1147             $options[$name] = $value;
1148         }
1150         // Normalize group.
1151         if (!groups_get_activity_groupmode($cm)) {
1152             // Groups not supported, force to -1.
1153             $groupid = -1;
1154         } else {
1155             // Check if we receive the default or and empty value for groupid,
1156             // in this case, get the group for the user in the activity.
1157             if (empty($params['groupid'])) {
1158                 $groupid = groups_get_activity_group($cm);
1159             } else {
1160                 // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
1161                 $groupid = $params['groupid'];
1162             }
1163         }
1165         if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
1166             throw new moodle_exception('cannotcreatediscussion', 'forum');
1167         }
1169         $thresholdwarning = forum_check_throttling($forum, $cm);
1170         forum_check_blocking_threshold($thresholdwarning);
1172         // Create the discussion.
1173         $discussion = new stdClass();
1174         $discussion->course = $course->id;
1175         $discussion->forum = $forum->id;
1176         $discussion->message = $params['message'];
1177         $discussion->messageformat = FORMAT_HTML;   // Force formatting for now.
1178         $discussion->messagetrust = trusttext_trusted($context);
1179         $discussion->itemid = $options['inlineattachmentsid'];
1180         $discussion->groupid = $groupid;
1181         $discussion->mailnow = 0;
1182         $discussion->subject = $params['subject'];
1183         $discussion->name = $discussion->subject;
1184         $discussion->timestart = 0;
1185         $discussion->timeend = 0;
1186         $discussion->attachments = $options['attachmentsid'];
1188         if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
1189             $discussion->pinned = FORUM_DISCUSSION_PINNED;
1190         } else {
1191             $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
1192         }
1193         $fakemform = $options['attachmentsid'];
1194         if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
1196             $discussion->id = $discussionid;
1198             // Trigger events and completion.
1200             $params = array(
1201                 'context' => $context,
1202                 'objectid' => $discussion->id,
1203                 'other' => array(
1204                     'forumid' => $forum->id,
1205                 )
1206             );
1207             $event = \mod_forum\event\discussion_created::create($params);
1208             $event->add_record_snapshot('forum_discussions', $discussion);
1209             $event->trigger();
1211             $completion = new completion_info($course);
1212             if ($completion->is_enabled($cm) &&
1213                     ($forum->completiondiscussions || $forum->completionposts)) {
1214                 $completion->update_state($cm, COMPLETION_COMPLETE);
1215             }
1217             $settings = new stdClass();
1218             $settings->discussionsubscribe = $options['discussionsubscribe'];
1219             forum_post_subscription($settings, $forum, $discussion);
1220         } else {
1221             throw new moodle_exception('couldnotadd', 'forum');
1222         }
1224         $result = array();
1225         $result['discussionid'] = $discussionid;
1226         $result['warnings'] = $warnings;
1227         return $result;
1228     }
1230     /**
1231      * Returns description of method result value
1232      *
1233      * @return external_description
1234      * @since Moodle 3.0
1235      */
1236     public static function add_discussion_returns() {
1237         return new external_single_structure(
1238             array(
1239                 'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
1240                 'warnings' => new external_warnings()
1241             )
1242         );
1243     }
1245     /**
1246      * Returns description of method parameters
1247      *
1248      * @return external_function_parameters
1249      * @since Moodle 3.1
1250      */
1251     public static function can_add_discussion_parameters() {
1252         return new external_function_parameters(
1253             array(
1254                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1255                 'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1256                                                 Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1257             )
1258         );
1259     }
1261     /**
1262      * Check if the current user can add discussions in the given forum (and optionally for the given group).
1263      *
1264      * @param int $forumid the forum instance id
1265      * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1266      * @return array of warnings and the status (true if the user can add discussions)
1267      * @since Moodle 3.1
1268      * @throws moodle_exception
1269      */
1270     public static function can_add_discussion($forumid, $groupid = null) {
1271         global $DB, $CFG;
1272         require_once($CFG->dirroot . "/mod/forum/lib.php");
1274         $params = self::validate_parameters(self::can_add_discussion_parameters(),
1275                                             array(
1276                                                 'forumid' => $forumid,
1277                                                 'groupid' => $groupid,
1278                                             ));
1279         $warnings = array();
1281         // Request and permission validation.
1282         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1283         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1285         $context = context_module::instance($cm->id);
1286         self::validate_context($context);
1288         $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1290         $result = array();
1291         $result['status'] = $status;
1292         $result['canpindiscussions'] = has_capability('mod/forum:pindiscussions', $context);
1293         $result['cancreateattachment'] = forum_can_create_attachment($forum, $context);
1294         $result['warnings'] = $warnings;
1295         return $result;
1296     }
1298     /**
1299      * Returns description of method result value
1300      *
1301      * @return external_description
1302      * @since Moodle 3.1
1303      */
1304     public static function can_add_discussion_returns() {
1305         return new external_single_structure(
1306             array(
1307                 'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
1308                 'canpindiscussions' => new external_value(PARAM_BOOL, 'True if the user can pin discussions, false otherwise.',
1309                     VALUE_OPTIONAL),
1310                 'cancreateattachment' => new external_value(PARAM_BOOL, 'True if the user can add attachments, false otherwise.',
1311                     VALUE_OPTIONAL),
1312                 'warnings' => new external_warnings()
1313             )
1314         );
1315     }
1317     /**
1318      * Describes the parameters for get_forum_access_information.
1319      *
1320      * @return external_external_function_parameters
1321      * @since Moodle 3.7
1322      */
1323     public static function get_forum_access_information_parameters() {
1324         return new external_function_parameters (
1325             array(
1326                 'forumid' => new external_value(PARAM_INT, 'Forum instance id.')
1327             )
1328         );
1329     }
1331     /**
1332      * Return access information for a given forum.
1333      *
1334      * @param int $forumid forum instance id
1335      * @return array of warnings and the access information
1336      * @since Moodle 3.7
1337      * @throws  moodle_exception
1338      */
1339     public static function get_forum_access_information($forumid) {
1340         global $DB;
1342         $params = self::validate_parameters(self::get_forum_access_information_parameters(), array('forumid' => $forumid));
1344         // Request and permission validation.
1345         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1346         $cm = get_coursemodule_from_instance('forum', $forum->id);
1348         $context = context_module::instance($cm->id);
1349         self::validate_context($context);
1351         $result = array();
1352         // Return all the available capabilities.
1353         $capabilities = load_capability_def('mod_forum');
1354         foreach ($capabilities as $capname => $capdata) {
1355             // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1356             $field = 'can' . str_replace('mod/forum:', '', $capname);
1357             $result[$field] = has_capability($capname, $context);
1358         }
1360         $result['warnings'] = array();
1361         return $result;
1362     }
1364     /**
1365      * Describes the get_forum_access_information return value.
1366      *
1367      * @return external_single_structure
1368      * @since Moodle 3.7
1369      */
1370     public static function get_forum_access_information_returns() {
1372         $structure = array(
1373             'warnings' => new external_warnings()
1374         );
1376         $capabilities = load_capability_def('mod_forum');
1377         foreach ($capabilities as $capname => $capdata) {
1378             // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1379             $field = 'can' . str_replace('mod/forum:', '', $capname);
1380             $structure[$field] = new external_value(PARAM_BOOL, 'Whether the user has the capability ' . $capname . ' allowed.',
1381                 VALUE_OPTIONAL);
1382         }
1384         return new external_single_structure($structure);
1385     }
1387     /**
1388      * Set the subscription state.
1389      *
1390      * @param   int     $forumid
1391      * @param   int     $discussionid
1392      * @param   bool    $targetstate
1393      * @return  \stdClass
1394      */
1395     public static function set_subscription_state($forumid, $discussionid, $targetstate) {
1396         global $PAGE, $USER;
1398         $params = self::validate_parameters(self::set_subscription_state_parameters(), [
1399             'forumid' => $forumid,
1400             'discussionid' => $discussionid,
1401             'targetstate' => $targetstate
1402         ]);
1404         $vaultfactory = mod_forum\local\container::get_vault_factory();
1405         $forumvault = $vaultfactory->get_forum_vault();
1406         $forum = $forumvault->get_from_id($params['forumid']);
1407         $coursemodule = $forum->get_course_module_record();
1408         $context = $forum->get_context();
1410         self::validate_context($context);
1412         $discussionvault = $vaultfactory->get_discussion_vault();
1413         $discussion = $discussionvault->get_from_id($params['discussionid']);
1414         $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1416         $forumrecord = $legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
1417         $discussionrecord = $legacydatamapperfactory->get_discussion_data_mapper()->to_legacy_object($discussion);
1419         if (!\mod_forum\subscriptions::is_subscribable($forumrecord)) {
1420             // Nothing to do. We won't actually output any content here though.
1421             throw new \moodle_exception('cannotsubscribe', 'mod_forum');
1422         }
1424         $issubscribed = \mod_forum\subscriptions::is_subscribed(
1425             $USER->id,
1426             $forumrecord,
1427             $discussion->get_id(),
1428             $coursemodule
1429         );
1431         // If the current state doesn't equal the desired state then update the current
1432         // state to the desired state.
1433         if ($issubscribed != (bool) $params['targetstate']) {
1434             if ($params['targetstate']) {
1435                 \mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussionrecord, $context);
1436             } else {
1437                 \mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussionrecord, $context);
1438             }
1439         }
1441         $exporterfactory = mod_forum\local\container::get_exporter_factory();
1442         $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1443         return $exporter->export($PAGE->get_renderer('mod_forum'));
1444     }
1446     /**
1447      * Returns description of method parameters.
1448      *
1449      * @return external_function_parameters
1450      */
1451     public static function set_subscription_state_parameters() {
1452         return new external_function_parameters(
1453             [
1454                 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1455                 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1456                 'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1457             ]
1458         );
1459     }
1461     /**
1462      * Returns description of method result value.
1463      *
1464      * @return external_description
1465      */
1466     public static function set_subscription_state_returns() {
1467         return \mod_forum\local\exporters\discussion::get_read_structure();
1468     }