MDL-31355 mod_forum: WS should return duedate and cutoffdate.
[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             }
425             $posts[] = $post;
426         }
428         $result = array();
429         $result['posts'] = $posts;
430         $result['ratinginfo'] = \core_rating\external\util::get_rating_info($forum, $modcontext, 'mod_forum', 'post', $posts);
431         $result['warnings'] = $warnings;
432         return $result;
433     }
435     /**
436      * Describes the get_forum_discussion_posts return value.
437      *
438      * @return external_single_structure
439      * @since Moodle 2.7
440      */
441     public static function get_forum_discussion_posts_returns() {
442         return new external_single_structure(
443             array(
444                 'posts' => new external_multiple_structure(
445                         new external_single_structure(
446                             array(
447                                 'id' => new external_value(PARAM_INT, 'Post id'),
448                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
449                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
450                                 'userid' => new external_value(PARAM_INT, 'User id'),
451                                 'created' => new external_value(PARAM_INT, 'Creation time'),
452                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
453                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
454                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
455                                 'message' => new external_value(PARAM_RAW, 'The post message'),
456                                 'messageformat' => new external_format_value('message'),
457                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
458                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
459                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
460                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
461                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
462                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
463                                 'children' => new external_multiple_structure(new external_value(PARAM_INT, 'children post id')),
464                                 'canreply' => new external_value(PARAM_BOOL, 'The user can reply to posts?'),
465                                 'postread' => new external_value(PARAM_BOOL, 'The post was read'),
466                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
467                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL),
468                                 'deleted' => new external_value(PARAM_BOOL, 'This post has been removed.'),
469                                 'isprivatereply' => new external_value(PARAM_BOOL, 'The post is a private reply'),
470                             ), 'post'
471                         )
472                     ),
473                 'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
474                 'warnings' => new external_warnings()
475             )
476         );
477     }
479     /**
480      * Mark the get_forum_discussion_posts web service as deprecated.
481      *
482      * @return  bool
483      */
484     public static function get_forum_discussion_posts_is_deprecated() {
485         return true;
486     }
488     /**
489      * Describes the parameters for get_forum_discussions_paginated.
490      *
491      * @return external_function_parameters
492      * @since Moodle 2.8
493      */
494     public static function get_forum_discussions_paginated_parameters() {
495         return new external_function_parameters (
496             array(
497                 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
498                 'sortby' => new external_value(PARAM_ALPHA,
499                     'sort by this element: id, timemodified, timestart or timeend', VALUE_DEFAULT, 'timemodified'),
500                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
501                 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
502                 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
503             )
504         );
505     }
507     /**
508      * Returns a list of forum discussions optionally sorted and paginated.
509      *
510      * @param int $forumid the forum instance id
511      * @param string $sortby sort by this element (id, timemodified, timestart or timeend)
512      * @param string $sortdirection sort direction: ASC or DESC
513      * @param int $page page number
514      * @param int $perpage items per page
515      *
516      * @return array the forum discussion details including warnings
517      * @since Moodle 2.8
518      */
519     public static function get_forum_discussions_paginated($forumid, $sortby = 'timemodified', $sortdirection = 'DESC',
520                                                     $page = -1, $perpage = 0) {
521         global $CFG, $DB, $USER, $PAGE;
523         require_once($CFG->dirroot . "/mod/forum/lib.php");
525         $warnings = array();
526         $discussions = array();
528         $params = self::validate_parameters(self::get_forum_discussions_paginated_parameters(),
529             array(
530                 'forumid' => $forumid,
531                 'sortby' => $sortby,
532                 'sortdirection' => $sortdirection,
533                 'page' => $page,
534                 'perpage' => $perpage
535             )
536         );
538         // Compact/extract functions are not recommended.
539         $forumid        = $params['forumid'];
540         $sortby         = $params['sortby'];
541         $sortdirection  = $params['sortdirection'];
542         $page           = $params['page'];
543         $perpage        = $params['perpage'];
545         $sortallowedvalues = array('id', 'timemodified', 'timestart', 'timeend');
546         if (!in_array($sortby, $sortallowedvalues)) {
547             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
548                 'allowed values are: ' . implode(',', $sortallowedvalues));
549         }
551         $sortdirection = strtoupper($sortdirection);
552         $directionallowedvalues = array('ASC', 'DESC');
553         if (!in_array($sortdirection, $directionallowedvalues)) {
554             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
555                 'allowed values are: ' . implode(',', $directionallowedvalues));
556         }
558         $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
559         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
560         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
562         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
563         $modcontext = context_module::instance($cm->id);
564         self::validate_context($modcontext);
566         // Check they have the view forum capability.
567         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
569         $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
570         $alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
572         if ($alldiscussions) {
573             $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
575             // Get the unreads array, this takes a forum id and returns data for all discussions.
576             $unreads = array();
577             if ($cantrack = forum_tp_can_track_forums($forum)) {
578                 if ($forumtracked = forum_tp_is_tracked($forum)) {
579                     $unreads = forum_get_discussions_unread($cm);
580                 }
581             }
582             // The forum function returns the replies for all the discussions in a given forum.
583             $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
584             $replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage, $canseeprivatereplies);
586             foreach ($alldiscussions as $discussion) {
588                 // This function checks for qanda forums.
589                 // Note that the forum_get_discussions returns as id the post id, not the discussion id so we need to do this.
590                 $discussionrec = clone $discussion;
591                 $discussionrec->id = $discussion->discussion;
592                 if (!forum_user_can_see_discussion($forum, $discussionrec, $modcontext)) {
593                     $warning = array();
594                     // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
595                     $warning['item'] = 'post';
596                     $warning['itemid'] = $discussion->id;
597                     $warning['warningcode'] = '1';
598                     $warning['message'] = 'You can\'t see this discussion';
599                     $warnings[] = $warning;
600                     continue;
601                 }
603                 $discussion->numunread = 0;
604                 if ($cantrack && $forumtracked) {
605                     if (isset($unreads[$discussion->discussion])) {
606                         $discussion->numunread = (int) $unreads[$discussion->discussion];
607                     }
608                 }
610                 $discussion->numreplies = 0;
611                 if (!empty($replies[$discussion->discussion])) {
612                     $discussion->numreplies = (int) $replies[$discussion->discussion]->replies;
613                 }
615                 $discussion->name = external_format_string($discussion->name, $modcontext->id);
616                 $discussion->subject = external_format_string($discussion->subject, $modcontext->id);
617                 // Rewrite embedded images URLs.
618                 list($discussion->message, $discussion->messageformat) =
619                     external_format_text($discussion->message, $discussion->messageformat,
620                                             $modcontext->id, 'mod_forum', 'post', $discussion->id);
622                 // List attachments.
623                 if (!empty($discussion->attachment)) {
624                     $discussion->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment',
625                                                                                 $discussion->id);
626                 }
627                 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $discussion->id);
628                 if (!empty($messageinlinefiles)) {
629                     $discussion->messageinlinefiles = $messageinlinefiles;
630                 }
632                 $discussion->locked = forum_discussion_is_locked($forum, $discussion);
633                 $discussion->canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
635                 if (forum_is_author_hidden($discussion, $forum)) {
636                     $discussion->userid = null;
637                     $discussion->userfullname = null;
638                     $discussion->userpictureurl = null;
640                     $discussion->usermodified = null;
641                     $discussion->usermodifiedfullname = null;
642                     $discussion->usermodifiedpictureurl = null;
643                 } else {
644                     $picturefields = explode(',', user_picture::fields());
646                     // Load user objects from the results of the query.
647                     $user = new stdclass();
648                     $user->id = $discussion->userid;
649                     $user = username_load_fields_from_object($user, $discussion, null, $picturefields);
650                     // Preserve the id, it can be modified by username_load_fields_from_object.
651                     $user->id = $discussion->userid;
652                     $discussion->userfullname = fullname($user, $canviewfullname);
654                     $userpicture = new user_picture($user);
655                     $userpicture->size = 1; // Size f1.
656                     $discussion->userpictureurl = $userpicture->get_url($PAGE)->out(false);
658                     $usermodified = new stdclass();
659                     $usermodified->id = $discussion->usermodified;
660                     $usermodified = username_load_fields_from_object($usermodified, $discussion, 'um', $picturefields);
661                     // Preserve the id (it can be overwritten due to the prefixed $picturefields).
662                     $usermodified->id = $discussion->usermodified;
663                     $discussion->usermodifiedfullname = fullname($usermodified, $canviewfullname);
665                     $userpicture = new user_picture($usermodified);
666                     $userpicture->size = 1; // Size f1.
667                     $discussion->usermodifiedpictureurl = $userpicture->get_url($PAGE)->out(false);
668                 }
670                 $discussions[] = $discussion;
671             }
672         }
674         $result = array();
675         $result['discussions'] = $discussions;
676         $result['warnings'] = $warnings;
677         return $result;
679     }
681     /**
682      * Describes the get_forum_discussions_paginated return value.
683      *
684      * @return external_single_structure
685      * @since Moodle 2.8
686      */
687     public static function get_forum_discussions_paginated_returns() {
688         return new external_single_structure(
689             array(
690                 'discussions' => new external_multiple_structure(
691                         new external_single_structure(
692                             array(
693                                 'id' => new external_value(PARAM_INT, 'Post id'),
694                                 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
695                                 'groupid' => new external_value(PARAM_INT, 'Group id'),
696                                 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
697                                 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
698                                 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
699                                 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
700                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
701                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
702                                 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
703                                 'created' => new external_value(PARAM_INT, 'Creation time'),
704                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
705                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
706                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
707                                 'message' => new external_value(PARAM_RAW, 'The post message'),
708                                 'messageformat' => new external_format_value('message'),
709                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
710                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
711                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
712                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
713                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
714                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
715                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
716                                 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
717                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
718                                 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
719                                 'numreplies' => new external_value(PARAM_TEXT, 'The number of replies in the discussion'),
720                                 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
721                                 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
722                                 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
723                                 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
724                             ), 'post'
725                         )
726                     ),
727                 'warnings' => new external_warnings()
728             )
729         );
730     }
732     /**
733      * Returns description of method parameters
734      *
735      * @return external_function_parameters
736      * @since Moodle 2.9
737      */
738     public static function view_forum_parameters() {
739         return new external_function_parameters(
740             array(
741                 'forumid' => new external_value(PARAM_INT, 'forum instance id')
742             )
743         );
744     }
746     /**
747      * Trigger the course module viewed event and update the module completion status.
748      *
749      * @param int $forumid the forum instance id
750      * @return array of warnings and status result
751      * @since Moodle 2.9
752      * @throws moodle_exception
753      */
754     public static function view_forum($forumid) {
755         global $DB, $CFG;
756         require_once($CFG->dirroot . "/mod/forum/lib.php");
758         $params = self::validate_parameters(self::view_forum_parameters(),
759                                             array(
760                                                 'forumid' => $forumid
761                                             ));
762         $warnings = array();
764         // Request and permission validation.
765         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
766         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
768         $context = context_module::instance($cm->id);
769         self::validate_context($context);
771         require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
773         // Call the forum/lib API.
774         forum_view($forum, $course, $cm, $context);
776         $result = array();
777         $result['status'] = true;
778         $result['warnings'] = $warnings;
779         return $result;
780     }
782     /**
783      * Returns description of method result value
784      *
785      * @return external_description
786      * @since Moodle 2.9
787      */
788     public static function view_forum_returns() {
789         return new external_single_structure(
790             array(
791                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
792                 'warnings' => new external_warnings()
793             )
794         );
795     }
797     /**
798      * Returns description of method parameters
799      *
800      * @return external_function_parameters
801      * @since Moodle 2.9
802      */
803     public static function view_forum_discussion_parameters() {
804         return new external_function_parameters(
805             array(
806                 'discussionid' => new external_value(PARAM_INT, 'discussion id')
807             )
808         );
809     }
811     /**
812      * Trigger the discussion viewed event.
813      *
814      * @param int $discussionid the discussion id
815      * @return array of warnings and status result
816      * @since Moodle 2.9
817      * @throws moodle_exception
818      */
819     public static function view_forum_discussion($discussionid) {
820         global $DB, $CFG, $USER;
821         require_once($CFG->dirroot . "/mod/forum/lib.php");
823         $params = self::validate_parameters(self::view_forum_discussion_parameters(),
824                                             array(
825                                                 'discussionid' => $discussionid
826                                             ));
827         $warnings = array();
829         $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
830         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
831         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
833         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
834         $modcontext = context_module::instance($cm->id);
835         self::validate_context($modcontext);
837         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
839         // Call the forum/lib API.
840         forum_discussion_view($modcontext, $forum, $discussion);
842         // Mark as read if required.
843         if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
844             forum_tp_mark_discussion_read($USER, $discussion->id);
845         }
847         $result = array();
848         $result['status'] = true;
849         $result['warnings'] = $warnings;
850         return $result;
851     }
853     /**
854      * Returns description of method result value
855      *
856      * @return external_description
857      * @since Moodle 2.9
858      */
859     public static function view_forum_discussion_returns() {
860         return new external_single_structure(
861             array(
862                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
863                 'warnings' => new external_warnings()
864             )
865         );
866     }
868     /**
869      * Returns description of method parameters
870      *
871      * @return external_function_parameters
872      * @since Moodle 3.0
873      */
874     public static function add_discussion_post_parameters() {
875         return new external_function_parameters(
876             array(
877                 'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
878                                                 (can be the initial discussion post'),
879                 'subject' => new external_value(PARAM_TEXT, 'new post subject'),
880                 'message' => new external_value(PARAM_RAW, 'new post message (only html format allowed)'),
881                 'options' => new external_multiple_structure (
882                     new external_single_structure(
883                         array(
884                             'name' => new external_value(PARAM_ALPHANUM,
885                                         'The allowed keys (value format) are:
886                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
887                                         private (bool); make this reply private to the author of the parent post, default to false.
888                                         inlineattachmentsid              (int); the draft file area id for inline attachments
889                                         attachmentsid       (int); the draft file area id for attachments
890                             '),
891                             'value' => new external_value(PARAM_RAW, 'the value of the option,
892                                                             this param is validated in the external function.'
893                         )
894                     )
895                 ), 'Options', VALUE_DEFAULT, array())
896             )
897         );
898     }
900     /**
901      * Create new posts into an existing discussion.
902      *
903      * @param int $postid the post id we are going to reply to
904      * @param string $subject new post subject
905      * @param string $message new post message (only html format allowed)
906      * @param array $options optional settings
907      * @return array of warnings and the new post id
908      * @since Moodle 3.0
909      * @throws moodle_exception
910      */
911     public static function add_discussion_post($postid, $subject, $message, $options = array()) {
912         global $DB, $CFG, $USER;
913         require_once($CFG->dirroot . "/mod/forum/lib.php");
915         $params = self::validate_parameters(self::add_discussion_post_parameters(),
916             array(
917                 'postid' => $postid,
918                 'subject' => $subject,
919                 'message' => $message,
920                 'options' => $options
921             )
922         );
924         $warnings = array();
926         if (!$parent = forum_get_post_full($params['postid'])) {
927             throw new moodle_exception('invalidparentpostid', 'forum');
928         }
930         if (!$discussion = $DB->get_record("forum_discussions", array("id" => $parent->discussion))) {
931             throw new moodle_exception('notpartofdiscussion', 'forum');
932         }
934         // Request and permission validation.
935         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
936         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
938         $context = context_module::instance($cm->id);
939         self::validate_context($context);
941         // Validate options.
942         $options = array(
943             'discussionsubscribe' => true,
944             'private'             => false,
945             'inlineattachmentsid' => 0,
946             'attachmentsid' => null
947         );
948         foreach ($params['options'] as $option) {
949             $name = trim($option['name']);
950             switch ($name) {
951                 case 'discussionsubscribe':
952                     $value = clean_param($option['value'], PARAM_BOOL);
953                     break;
954                 case 'private':
955                     $value = clean_param($option['value'], PARAM_BOOL);
956                     break;
957                 case 'inlineattachmentsid':
958                     $value = clean_param($option['value'], PARAM_INT);
959                     break;
960                 case 'attachmentsid':
961                     $value = clean_param($option['value'], PARAM_INT);
962                     // Ensure that the user has permissions to create attachments.
963                     if (!has_capability('mod/forum:createattachment', $context)) {
964                         $value = 0;
965                     }
966                     break;
967                 default:
968                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
969             }
970             $options[$name] = $value;
971         }
973         if (!forum_user_can_post($forum, $discussion, $USER, $cm, $course, $context)) {
974             throw new moodle_exception('nopostforum', 'forum');
975         }
977         $thresholdwarning = forum_check_throttling($forum, $cm);
978         forum_check_blocking_threshold($thresholdwarning);
980         // Create the post.
981         $post = new stdClass();
982         $post->discussion = $discussion->id;
983         $post->parent = $parent->id;
984         $post->subject = $params['subject'];
985         $post->message = $params['message'];
986         $post->messageformat = FORMAT_HTML;   // Force formatting for now.
987         $post->messagetrust = trusttext_trusted($context);
988         $post->itemid = $options['inlineattachmentsid'];
989         $post->attachments = $options['attachmentsid'];
990         $post->isprivatereply = $options['private'];
991         $post->deleted = 0;
992         $fakemform = $post->attachments;
993         if ($postid = forum_add_new_post($post, $fakemform)) {
995             $post->id = $postid;
997             // Trigger events and completion.
998             $params = array(
999                 'context' => $context,
1000                 'objectid' => $post->id,
1001                 'other' => array(
1002                     'discussionid' => $discussion->id,
1003                     'forumid' => $forum->id,
1004                     'forumtype' => $forum->type,
1005                 )
1006             );
1007             $event = \mod_forum\event\post_created::create($params);
1008             $event->add_record_snapshot('forum_posts', $post);
1009             $event->add_record_snapshot('forum_discussions', $discussion);
1010             $event->trigger();
1012             // Update completion state.
1013             $completion = new completion_info($course);
1014             if ($completion->is_enabled($cm) &&
1015                     ($forum->completionreplies || $forum->completionposts)) {
1016                 $completion->update_state($cm, COMPLETION_COMPLETE);
1017             }
1019             $settings = new stdClass();
1020             $settings->discussionsubscribe = $options['discussionsubscribe'];
1021             forum_post_subscription($settings, $forum, $discussion);
1022         } else {
1023             throw new moodle_exception('couldnotadd', 'forum');
1024         }
1026         $result = array();
1027         $result['postid'] = $postid;
1028         $result['warnings'] = $warnings;
1029         return $result;
1030     }
1032     /**
1033      * Returns description of method result value
1034      *
1035      * @return external_description
1036      * @since Moodle 3.0
1037      */
1038     public static function add_discussion_post_returns() {
1039         return new external_single_structure(
1040             array(
1041                 'postid' => new external_value(PARAM_INT, 'new post id'),
1042                 'warnings' => new external_warnings()
1043             )
1044         );
1045     }
1047     /**
1048      * Returns description of method parameters
1049      *
1050      * @return external_function_parameters
1051      * @since Moodle 3.0
1052      */
1053     public static function add_discussion_parameters() {
1054         return new external_function_parameters(
1055             array(
1056                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1057                 'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
1058                 'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
1059                 'groupid' => new external_value(PARAM_INT, 'The group, default to 0', VALUE_DEFAULT, 0),
1060                 'options' => new external_multiple_structure (
1061                     new external_single_structure(
1062                         array(
1063                             'name' => new external_value(PARAM_ALPHANUM,
1064                                         'The allowed keys (value format) are:
1065                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
1066                                         discussionpinned    (bool); is the discussion pinned, default to false
1067                                         inlineattachmentsid              (int); the draft file area id for inline attachments
1068                                         attachmentsid       (int); the draft file area id for attachments
1069                             '),
1070                             'value' => new external_value(PARAM_RAW, 'The value of the option,
1071                                                             This param is validated in the external function.'
1072                         )
1073                     )
1074                 ), 'Options', VALUE_DEFAULT, array())
1075             )
1076         );
1077     }
1079     /**
1080      * Add a new discussion into an existing forum.
1081      *
1082      * @param int $forumid the forum instance id
1083      * @param string $subject new discussion subject
1084      * @param string $message new discussion message (only html format allowed)
1085      * @param int $groupid the user course group
1086      * @param array $options optional settings
1087      * @return array of warnings and the new discussion id
1088      * @since Moodle 3.0
1089      * @throws moodle_exception
1090      */
1091     public static function add_discussion($forumid, $subject, $message, $groupid = 0, $options = array()) {
1092         global $DB, $CFG;
1093         require_once($CFG->dirroot . "/mod/forum/lib.php");
1095         $params = self::validate_parameters(self::add_discussion_parameters(),
1096                                             array(
1097                                                 'forumid' => $forumid,
1098                                                 'subject' => $subject,
1099                                                 'message' => $message,
1100                                                 'groupid' => $groupid,
1101                                                 'options' => $options
1102                                             ));
1104         $warnings = array();
1106         // Request and permission validation.
1107         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1108         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1110         $context = context_module::instance($cm->id);
1111         self::validate_context($context);
1113         // Validate options.
1114         $options = array(
1115             'discussionsubscribe' => true,
1116             'discussionpinned' => false,
1117             'inlineattachmentsid' => 0,
1118             'attachmentsid' => null
1119         );
1120         foreach ($params['options'] as $option) {
1121             $name = trim($option['name']);
1122             switch ($name) {
1123                 case 'discussionsubscribe':
1124                     $value = clean_param($option['value'], PARAM_BOOL);
1125                     break;
1126                 case 'discussionpinned':
1127                     $value = clean_param($option['value'], PARAM_BOOL);
1128                     break;
1129                 case 'inlineattachmentsid':
1130                     $value = clean_param($option['value'], PARAM_INT);
1131                     break;
1132                 case 'attachmentsid':
1133                     $value = clean_param($option['value'], PARAM_INT);
1134                     // Ensure that the user has permissions to create attachments.
1135                     if (!has_capability('mod/forum:createattachment', $context)) {
1136                         $value = 0;
1137                     }
1138                     break;
1139                 default:
1140                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1141             }
1142             $options[$name] = $value;
1143         }
1145         // Normalize group.
1146         if (!groups_get_activity_groupmode($cm)) {
1147             // Groups not supported, force to -1.
1148             $groupid = -1;
1149         } else {
1150             // Check if we receive the default or and empty value for groupid,
1151             // in this case, get the group for the user in the activity.
1152             if (empty($params['groupid'])) {
1153                 $groupid = groups_get_activity_group($cm);
1154             } else {
1155                 // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
1156                 $groupid = $params['groupid'];
1157             }
1158         }
1160         if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
1161             throw new moodle_exception('cannotcreatediscussion', 'forum');
1162         }
1164         $thresholdwarning = forum_check_throttling($forum, $cm);
1165         forum_check_blocking_threshold($thresholdwarning);
1167         // Create the discussion.
1168         $discussion = new stdClass();
1169         $discussion->course = $course->id;
1170         $discussion->forum = $forum->id;
1171         $discussion->message = $params['message'];
1172         $discussion->messageformat = FORMAT_HTML;   // Force formatting for now.
1173         $discussion->messagetrust = trusttext_trusted($context);
1174         $discussion->itemid = $options['inlineattachmentsid'];
1175         $discussion->groupid = $groupid;
1176         $discussion->mailnow = 0;
1177         $discussion->subject = $params['subject'];
1178         $discussion->name = $discussion->subject;
1179         $discussion->timestart = 0;
1180         $discussion->timeend = 0;
1181         $discussion->attachments = $options['attachmentsid'];
1183         if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
1184             $discussion->pinned = FORUM_DISCUSSION_PINNED;
1185         } else {
1186             $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
1187         }
1188         $fakemform = $options['attachmentsid'];
1189         if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
1191             $discussion->id = $discussionid;
1193             // Trigger events and completion.
1195             $params = array(
1196                 'context' => $context,
1197                 'objectid' => $discussion->id,
1198                 'other' => array(
1199                     'forumid' => $forum->id,
1200                 )
1201             );
1202             $event = \mod_forum\event\discussion_created::create($params);
1203             $event->add_record_snapshot('forum_discussions', $discussion);
1204             $event->trigger();
1206             $completion = new completion_info($course);
1207             if ($completion->is_enabled($cm) &&
1208                     ($forum->completiondiscussions || $forum->completionposts)) {
1209                 $completion->update_state($cm, COMPLETION_COMPLETE);
1210             }
1212             $settings = new stdClass();
1213             $settings->discussionsubscribe = $options['discussionsubscribe'];
1214             forum_post_subscription($settings, $forum, $discussion);
1215         } else {
1216             throw new moodle_exception('couldnotadd', 'forum');
1217         }
1219         $result = array();
1220         $result['discussionid'] = $discussionid;
1221         $result['warnings'] = $warnings;
1222         return $result;
1223     }
1225     /**
1226      * Returns description of method result value
1227      *
1228      * @return external_description
1229      * @since Moodle 3.0
1230      */
1231     public static function add_discussion_returns() {
1232         return new external_single_structure(
1233             array(
1234                 'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
1235                 'warnings' => new external_warnings()
1236             )
1237         );
1238     }
1240     /**
1241      * Returns description of method parameters
1242      *
1243      * @return external_function_parameters
1244      * @since Moodle 3.1
1245      */
1246     public static function can_add_discussion_parameters() {
1247         return new external_function_parameters(
1248             array(
1249                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1250                 'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1251                                                 Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1252             )
1253         );
1254     }
1256     /**
1257      * Check if the current user can add discussions in the given forum (and optionally for the given group).
1258      *
1259      * @param int $forumid the forum instance id
1260      * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1261      * @return array of warnings and the status (true if the user can add discussions)
1262      * @since Moodle 3.1
1263      * @throws moodle_exception
1264      */
1265     public static function can_add_discussion($forumid, $groupid = null) {
1266         global $DB, $CFG;
1267         require_once($CFG->dirroot . "/mod/forum/lib.php");
1269         $params = self::validate_parameters(self::can_add_discussion_parameters(),
1270                                             array(
1271                                                 'forumid' => $forumid,
1272                                                 'groupid' => $groupid,
1273                                             ));
1274         $warnings = array();
1276         // Request and permission validation.
1277         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1278         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1280         $context = context_module::instance($cm->id);
1281         self::validate_context($context);
1283         $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1285         $result = array();
1286         $result['status'] = $status;
1287         $result['canpindiscussions'] = has_capability('mod/forum:pindiscussions', $context);
1288         $result['cancreateattachment'] = forum_can_create_attachment($forum, $context);
1289         $result['warnings'] = $warnings;
1290         return $result;
1291     }
1293     /**
1294      * Returns description of method result value
1295      *
1296      * @return external_description
1297      * @since Moodle 3.1
1298      */
1299     public static function can_add_discussion_returns() {
1300         return new external_single_structure(
1301             array(
1302                 'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
1303                 'canpindiscussions' => new external_value(PARAM_BOOL, 'True if the user can pin discussions, false otherwise.',
1304                     VALUE_OPTIONAL),
1305                 'cancreateattachment' => new external_value(PARAM_BOOL, 'True if the user can add attachments, false otherwise.',
1306                     VALUE_OPTIONAL),
1307                 'warnings' => new external_warnings()
1308             )
1309         );
1310     }
1312     /**
1313      * Describes the parameters for get_forum_access_information.
1314      *
1315      * @return external_external_function_parameters
1316      * @since Moodle 3.7
1317      */
1318     public static function get_forum_access_information_parameters() {
1319         return new external_function_parameters (
1320             array(
1321                 'forumid' => new external_value(PARAM_INT, 'Forum instance id.')
1322             )
1323         );
1324     }
1326     /**
1327      * Return access information for a given forum.
1328      *
1329      * @param int $forumid forum instance id
1330      * @return array of warnings and the access information
1331      * @since Moodle 3.7
1332      * @throws  moodle_exception
1333      */
1334     public static function get_forum_access_information($forumid) {
1335         global $DB;
1337         $params = self::validate_parameters(self::get_forum_access_information_parameters(), array('forumid' => $forumid));
1339         // Request and permission validation.
1340         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1341         $cm = get_coursemodule_from_instance('forum', $forum->id);
1343         $context = context_module::instance($cm->id);
1344         self::validate_context($context);
1346         $result = array();
1347         // Return all the available capabilities.
1348         $capabilities = load_capability_def('mod_forum');
1349         foreach ($capabilities as $capname => $capdata) {
1350             // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1351             $field = 'can' . str_replace('mod/forum:', '', $capname);
1352             $result[$field] = has_capability($capname, $context);
1353         }
1355         $result['warnings'] = array();
1356         return $result;
1357     }
1359     /**
1360      * Describes the get_forum_access_information return value.
1361      *
1362      * @return external_single_structure
1363      * @since Moodle 3.7
1364      */
1365     public static function get_forum_access_information_returns() {
1367         $structure = array(
1368             'warnings' => new external_warnings()
1369         );
1371         $capabilities = load_capability_def('mod_forum');
1372         foreach ($capabilities as $capname => $capdata) {
1373             // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1374             $field = 'can' . str_replace('mod/forum:', '', $capname);
1375             $structure[$field] = new external_value(PARAM_BOOL, 'Whether the user has the capability ' . $capname . ' allowed.',
1376                 VALUE_OPTIONAL);
1377         }
1379         return new external_single_structure($structure);
1380     }
1382     /**
1383      * Set the subscription state.
1384      *
1385      * @param   int     $forumid
1386      * @param   int     $discussionid
1387      * @param   bool    $targetstate
1388      * @return  \stdClass
1389      */
1390     public static function set_subscription_state($forumid, $discussionid, $targetstate) {
1391         global $PAGE, $USER;
1393         $params = self::validate_parameters(self::set_subscription_state_parameters(), [
1394             'forumid' => $forumid,
1395             'discussionid' => $discussionid,
1396             'targetstate' => $targetstate
1397         ]);
1399         $vaultfactory = mod_forum\local\container::get_vault_factory();
1400         $forumvault = $vaultfactory->get_forum_vault();
1401         $forum = $forumvault->get_from_id($params['forumid']);
1402         $coursemodule = $forum->get_course_module_record();
1403         $context = $forum->get_context();
1405         self::validate_context($context);
1407         $discussionvault = $vaultfactory->get_discussion_vault();
1408         $discussion = $discussionvault->get_from_id($params['discussionid']);
1409         $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1411         $forumrecord = $legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
1412         $discussionrecord = $legacydatamapperfactory->get_discussion_data_mapper()->to_legacy_object($discussion);
1414         if (!\mod_forum\subscriptions::is_subscribable($forumrecord)) {
1415             // Nothing to do. We won't actually output any content here though.
1416             throw new \moodle_exception('cannotsubscribe', 'mod_forum');
1417         }
1419         $issubscribed = \mod_forum\subscriptions::is_subscribed(
1420             $USER->id,
1421             $forumrecord,
1422             $discussion->get_id(),
1423             $coursemodule
1424         );
1426         // If the current state doesn't equal the desired state then update the current
1427         // state to the desired state.
1428         if ($issubscribed != (bool) $params['targetstate']) {
1429             if ($params['targetstate']) {
1430                 \mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussionrecord, $context);
1431             } else {
1432                 \mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussionrecord, $context);
1433             }
1434         }
1436         $exporterfactory = mod_forum\local\container::get_exporter_factory();
1437         $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1438         return $exporter->export($PAGE->get_renderer('mod_forum'));
1439     }
1441     /**
1442      * Returns description of method parameters.
1443      *
1444      * @return external_function_parameters
1445      */
1446     public static function set_subscription_state_parameters() {
1447         return new external_function_parameters(
1448             [
1449                 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1450                 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1451                 'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1452             ]
1453         );
1454     }
1456     /**
1457      * Returns description of method result value.
1458      *
1459      * @return external_description
1460      */
1461     public static function set_subscription_state_returns() {
1462         return \mod_forum\local\exporters\discussion::get_read_structure();
1463     }