12c0e1278b195f8b10f732f834cc4e94f7d6ca7b
[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                     'assessed' => new external_value(PARAM_INT, 'Aggregate type'),
131                     'assesstimestart' => new external_value(PARAM_INT, 'Assess start time'),
132                     'assesstimefinish' => new external_value(PARAM_INT, 'Assess finish time'),
133                     'scale' => new external_value(PARAM_INT, 'Scale'),
134                     'maxbytes' => new external_value(PARAM_INT, 'Maximum attachment size'),
135                     'maxattachments' => new external_value(PARAM_INT, 'Maximum number of attachments'),
136                     'forcesubscribe' => new external_value(PARAM_INT, 'Force users to subscribe'),
137                     'trackingtype' => new external_value(PARAM_INT, 'Subscription mode'),
138                     'rsstype' => new external_value(PARAM_INT, 'RSS feed for this activity'),
139                     'rssarticles' => new external_value(PARAM_INT, 'Number of RSS recent articles'),
140                     'timemodified' => new external_value(PARAM_INT, 'Time modified'),
141                     'warnafter' => new external_value(PARAM_INT, 'Post threshold for warning'),
142                     'blockafter' => new external_value(PARAM_INT, 'Post threshold for blocking'),
143                     'blockperiod' => new external_value(PARAM_INT, 'Time period for blocking'),
144                     'completiondiscussions' => new external_value(PARAM_INT, 'Student must create discussions'),
145                     'completionreplies' => new external_value(PARAM_INT, 'Student must post replies'),
146                     'completionposts' => new external_value(PARAM_INT, 'Student must post discussions or replies'),
147                     'cmid' => new external_value(PARAM_INT, 'Course module id'),
148                     'numdiscussions' => new external_value(PARAM_INT, 'Number of discussions in the forum', VALUE_OPTIONAL),
149                     'cancreatediscussions' => new external_value(PARAM_BOOL, 'If the user can create discussions', VALUE_OPTIONAL),
150                     'lockdiscussionafter' => new external_value(PARAM_INT, 'After what period a discussion is locked', VALUE_OPTIONAL),
151                     'istracked' => new external_value(PARAM_BOOL, 'If the user is tracking the forum', VALUE_OPTIONAL),
152                     'unreadpostscount' => new external_value(PARAM_INT, 'The number of unread posts for tracked forums',
153                         VALUE_OPTIONAL),
154                 ), 'forum'
155             )
156         );
157     }
159     /**
160      * Describes the parameters for get_forum_discussion_posts.
161      *
162      * @return external_function_parameters
163      * @since Moodle 2.7
164      */
165     public static function get_forum_discussion_posts_parameters() {
166         return new external_function_parameters (
167             array(
168                 'discussionid' => new external_value(PARAM_INT, 'discussion ID', VALUE_REQUIRED),
169                 'sortby' => new external_value(PARAM_ALPHA,
170                     'sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
171                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
172             )
173         );
174     }
176     /**
177      * Returns a list of forum posts for a discussion
178      *
179      * @param int $discussionid the post ids
180      * @param string $sortby sort by this element (id, created or modified)
181      * @param string $sortdirection sort direction: ASC or DESC
182      *
183      * @return array the forum post details
184      * @since Moodle 2.7
185      */
186     public static function get_forum_discussion_posts($discussionid, $sortby = "created", $sortdirection = "DESC") {
187         global $CFG, $DB, $USER, $PAGE;
189         $posts = array();
190         $warnings = array();
192         // Validate the parameter.
193         $params = self::validate_parameters(self::get_forum_discussion_posts_parameters(),
194             array(
195                 'discussionid' => $discussionid,
196                 'sortby' => $sortby,
197                 'sortdirection' => $sortdirection));
199         // Compact/extract functions are not recommended.
200         $discussionid   = $params['discussionid'];
201         $sortby         = $params['sortby'];
202         $sortdirection  = $params['sortdirection'];
204         $sortallowedvalues = array('id', 'created', 'modified');
205         if (!in_array($sortby, $sortallowedvalues)) {
206             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
207                 'allowed values are: ' . implode(',', $sortallowedvalues));
208         }
210         $sortdirection = strtoupper($sortdirection);
211         $directionallowedvalues = array('ASC', 'DESC');
212         if (!in_array($sortdirection, $directionallowedvalues)) {
213             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
214                 'allowed values are: ' . implode(',', $directionallowedvalues));
215         }
217         $discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
218         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
219         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
220         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
222         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
223         $modcontext = context_module::instance($cm->id);
224         self::validate_context($modcontext);
226         // This require must be here, see mod/forum/discuss.php.
227         require_once($CFG->dirroot . "/mod/forum/lib.php");
229         // Check they have the view forum capability.
230         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
232         if (! $post = forum_get_post_full($discussion->firstpost)) {
233             throw new moodle_exception('notexists', 'forum');
234         }
236         // This function check groups, qanda, timed discussions, etc.
237         if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
238             throw new moodle_exception('noviewdiscussionspermission', 'forum');
239         }
241         $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
243         // We will add this field in the response.
244         $canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
246         $forumtracked = forum_tp_is_tracked($forum);
248         $sort = 'p.' . $sortby . ' ' . $sortdirection;
249         $allposts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
251         foreach ($allposts as $post) {
253             if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
254                 $warning = array();
255                 $warning['item'] = 'post';
256                 $warning['itemid'] = $post->id;
257                 $warning['warningcode'] = '1';
258                 $warning['message'] = 'You can\'t see this post';
259                 $warnings[] = $warning;
260                 continue;
261             }
263             // Function forum_get_all_discussion_posts adds postread field.
264             // Note that the value returned can be a boolean or an integer. The WS expects a boolean.
265             if (empty($post->postread)) {
266                 $post->postread = false;
267             } else {
268                 $post->postread = true;
269             }
271             $post->canreply = $canreply;
272             if (!empty($post->children)) {
273                 $post->children = array_keys($post->children);
274             } else {
275                 $post->children = array();
276             }
278             if (forum_is_author_hidden($post, $forum)) {
279                 $post->userid = null;
280                 $post->userfullname = null;
281                 $post->userpictureurl = null;
282             } else {
283                 $user = new stdclass();
284                 $user->id = $post->userid;
285                 $user = username_load_fields_from_object($user, $post, null, array('picture', 'imagealt', 'email'));
286                 $post->userfullname = fullname($user, $canviewfullname);
288                 $userpicture = new user_picture($user);
289                 $userpicture->size = 1; // Size f1.
290                 $post->userpictureurl = $userpicture->get_url($PAGE)->out(false);
291             }
293             $post->subject = external_format_string($post->subject, $modcontext->id);
294             // Rewrite embedded images URLs.
295             list($post->message, $post->messageformat) =
296                 external_format_text($post->message, $post->messageformat, $modcontext->id, 'mod_forum', 'post', $post->id);
298             // List attachments.
299             if (!empty($post->attachment)) {
300                 $post->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment', $post->id);
301             }
302             $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $post->id);
303             if (!empty($messageinlinefiles)) {
304                 $post->messageinlinefiles = $messageinlinefiles;
305             }
307             $posts[] = $post;
308         }
310         $result = array();
311         $result['posts'] = $posts;
312         $result['warnings'] = $warnings;
313         return $result;
314     }
316     /**
317      * Describes the get_forum_discussion_posts return value.
318      *
319      * @return external_single_structure
320      * @since Moodle 2.7
321      */
322     public static function get_forum_discussion_posts_returns() {
323         return new external_single_structure(
324             array(
325                 'posts' => new external_multiple_structure(
326                         new external_single_structure(
327                             array(
328                                 'id' => new external_value(PARAM_INT, 'Post id'),
329                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
330                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
331                                 'userid' => new external_value(PARAM_INT, 'User id'),
332                                 'created' => new external_value(PARAM_INT, 'Creation time'),
333                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
334                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
335                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
336                                 'message' => new external_value(PARAM_RAW, 'The post message'),
337                                 'messageformat' => new external_format_value('message'),
338                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
339                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
340                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
341                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
342                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
343                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
344                                 'children' => new external_multiple_structure(new external_value(PARAM_INT, 'children post id')),
345                                 'canreply' => new external_value(PARAM_BOOL, 'The user can reply to posts?'),
346                                 'postread' => new external_value(PARAM_BOOL, 'The post was read'),
347                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
348                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL)
349                             ), 'post'
350                         )
351                     ),
352                 'warnings' => new external_warnings()
353             )
354         );
355     }
357     /**
358      * Describes the parameters for get_forum_discussions_paginated.
359      *
360      * @return external_function_parameters
361      * @since Moodle 2.8
362      */
363     public static function get_forum_discussions_paginated_parameters() {
364         return new external_function_parameters (
365             array(
366                 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
367                 'sortby' => new external_value(PARAM_ALPHA,
368                     'sort by this element: id, timemodified, timestart or timeend', VALUE_DEFAULT, 'timemodified'),
369                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
370                 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
371                 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
372             )
373         );
374     }
376     /**
377      * Returns a list of forum discussions optionally sorted and paginated.
378      *
379      * @param int $forumid the forum instance id
380      * @param string $sortby sort by this element (id, timemodified, timestart or timeend)
381      * @param string $sortdirection sort direction: ASC or DESC
382      * @param int $page page number
383      * @param int $perpage items per page
384      *
385      * @return array the forum discussion details including warnings
386      * @since Moodle 2.8
387      */
388     public static function get_forum_discussions_paginated($forumid, $sortby = 'timemodified', $sortdirection = 'DESC',
389                                                     $page = -1, $perpage = 0) {
390         global $CFG, $DB, $USER, $PAGE;
392         require_once($CFG->dirroot . "/mod/forum/lib.php");
394         $warnings = array();
395         $discussions = array();
397         $params = self::validate_parameters(self::get_forum_discussions_paginated_parameters(),
398             array(
399                 'forumid' => $forumid,
400                 'sortby' => $sortby,
401                 'sortdirection' => $sortdirection,
402                 'page' => $page,
403                 'perpage' => $perpage
404             )
405         );
407         // Compact/extract functions are not recommended.
408         $forumid        = $params['forumid'];
409         $sortby         = $params['sortby'];
410         $sortdirection  = $params['sortdirection'];
411         $page           = $params['page'];
412         $perpage        = $params['perpage'];
414         $sortallowedvalues = array('id', 'timemodified', 'timestart', 'timeend');
415         if (!in_array($sortby, $sortallowedvalues)) {
416             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
417                 'allowed values are: ' . implode(',', $sortallowedvalues));
418         }
420         $sortdirection = strtoupper($sortdirection);
421         $directionallowedvalues = array('ASC', 'DESC');
422         if (!in_array($sortdirection, $directionallowedvalues)) {
423             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
424                 'allowed values are: ' . implode(',', $directionallowedvalues));
425         }
427         $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
428         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
429         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
431         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
432         $modcontext = context_module::instance($cm->id);
433         self::validate_context($modcontext);
435         // Check they have the view forum capability.
436         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
438         $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
439         $alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
441         if ($alldiscussions) {
442             $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
444             // Get the unreads array, this takes a forum id and returns data for all discussions.
445             $unreads = array();
446             if ($cantrack = forum_tp_can_track_forums($forum)) {
447                 if ($forumtracked = forum_tp_is_tracked($forum)) {
448                     $unreads = forum_get_discussions_unread($cm);
449                 }
450             }
451             // The forum function returns the replies for all the discussions in a given forum.
452             $replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage);
454             foreach ($alldiscussions as $discussion) {
456                 // This function checks for qanda forums.
457                 // Note that the forum_get_discussions returns as id the post id, not the discussion id so we need to do this.
458                 $discussionrec = clone $discussion;
459                 $discussionrec->id = $discussion->discussion;
460                 if (!forum_user_can_see_discussion($forum, $discussionrec, $modcontext)) {
461                     $warning = array();
462                     // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
463                     $warning['item'] = 'post';
464                     $warning['itemid'] = $discussion->id;
465                     $warning['warningcode'] = '1';
466                     $warning['message'] = 'You can\'t see this discussion';
467                     $warnings[] = $warning;
468                     continue;
469                 }
471                 $discussion->numunread = 0;
472                 if ($cantrack && $forumtracked) {
473                     if (isset($unreads[$discussion->discussion])) {
474                         $discussion->numunread = (int) $unreads[$discussion->discussion];
475                     }
476                 }
478                 $discussion->numreplies = 0;
479                 if (!empty($replies[$discussion->discussion])) {
480                     $discussion->numreplies = (int) $replies[$discussion->discussion]->replies;
481                 }
483                 $discussion->name = external_format_string($discussion->name, $modcontext->id);
484                 $discussion->subject = external_format_string($discussion->subject, $modcontext->id);
485                 // Rewrite embedded images URLs.
486                 list($discussion->message, $discussion->messageformat) =
487                     external_format_text($discussion->message, $discussion->messageformat,
488                                             $modcontext->id, 'mod_forum', 'post', $discussion->id);
490                 // List attachments.
491                 if (!empty($discussion->attachment)) {
492                     $discussion->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment',
493                                                                                 $discussion->id);
494                 }
495                 $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post', $discussion->id);
496                 if (!empty($messageinlinefiles)) {
497                     $discussion->messageinlinefiles = $messageinlinefiles;
498                 }
500                 $discussion->locked = forum_discussion_is_locked($forum, $discussion);
501                 $discussion->canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
503                 if (forum_is_author_hidden($discussion, $forum)) {
504                     $discussion->userid = null;
505                     $discussion->userfullname = null;
506                     $discussion->userpictureurl = null;
508                     $discussion->usermodified = null;
509                     $discussion->usermodifiedfullname = null;
510                     $discussion->usermodifiedpictureurl = null;
511                 } else {
512                     $picturefields = explode(',', user_picture::fields());
514                     // Load user objects from the results of the query.
515                     $user = new stdclass();
516                     $user->id = $discussion->userid;
517                     $user = username_load_fields_from_object($user, $discussion, null, $picturefields);
518                     // Preserve the id, it can be modified by username_load_fields_from_object.
519                     $user->id = $discussion->userid;
520                     $discussion->userfullname = fullname($user, $canviewfullname);
522                     $userpicture = new user_picture($user);
523                     $userpicture->size = 1; // Size f1.
524                     $discussion->userpictureurl = $userpicture->get_url($PAGE)->out(false);
526                     $usermodified = new stdclass();
527                     $usermodified->id = $discussion->usermodified;
528                     $usermodified = username_load_fields_from_object($usermodified, $discussion, 'um', $picturefields);
529                     // Preserve the id (it can be overwritten due to the prefixed $picturefields).
530                     $usermodified->id = $discussion->usermodified;
531                     $discussion->usermodifiedfullname = fullname($usermodified, $canviewfullname);
533                     $userpicture = new user_picture($usermodified);
534                     $userpicture->size = 1; // Size f1.
535                     $discussion->usermodifiedpictureurl = $userpicture->get_url($PAGE)->out(false);
536                 }
538                 $discussions[] = $discussion;
539             }
540         }
542         $result = array();
543         $result['discussions'] = $discussions;
544         $result['warnings'] = $warnings;
545         return $result;
547     }
549     /**
550      * Describes the get_forum_discussions_paginated return value.
551      *
552      * @return external_single_structure
553      * @since Moodle 2.8
554      */
555     public static function get_forum_discussions_paginated_returns() {
556         return new external_single_structure(
557             array(
558                 'discussions' => new external_multiple_structure(
559                         new external_single_structure(
560                             array(
561                                 'id' => new external_value(PARAM_INT, 'Post id'),
562                                 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
563                                 'groupid' => new external_value(PARAM_INT, 'Group id'),
564                                 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
565                                 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
566                                 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
567                                 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
568                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
569                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
570                                 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
571                                 'created' => new external_value(PARAM_INT, 'Creation time'),
572                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
573                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
574                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
575                                 'message' => new external_value(PARAM_RAW, 'The post message'),
576                                 'messageformat' => new external_format_value('message'),
577                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
578                                 'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
579                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
580                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
581                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
582                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
583                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
584                                 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
585                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
586                                 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
587                                 'numreplies' => new external_value(PARAM_TEXT, 'The number of replies in the discussion'),
588                                 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
589                                 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
590                                 'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
591                                 'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
592                             ), 'post'
593                         )
594                     ),
595                 'warnings' => new external_warnings()
596             )
597         );
598     }
600     /**
601      * Returns description of method parameters
602      *
603      * @return external_function_parameters
604      * @since Moodle 2.9
605      */
606     public static function view_forum_parameters() {
607         return new external_function_parameters(
608             array(
609                 'forumid' => new external_value(PARAM_INT, 'forum instance id')
610             )
611         );
612     }
614     /**
615      * Trigger the course module viewed event and update the module completion status.
616      *
617      * @param int $forumid the forum instance id
618      * @return array of warnings and status result
619      * @since Moodle 2.9
620      * @throws moodle_exception
621      */
622     public static function view_forum($forumid) {
623         global $DB, $CFG;
624         require_once($CFG->dirroot . "/mod/forum/lib.php");
626         $params = self::validate_parameters(self::view_forum_parameters(),
627                                             array(
628                                                 'forumid' => $forumid
629                                             ));
630         $warnings = array();
632         // Request and permission validation.
633         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
634         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
636         $context = context_module::instance($cm->id);
637         self::validate_context($context);
639         require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
641         // Call the forum/lib API.
642         forum_view($forum, $course, $cm, $context);
644         $result = array();
645         $result['status'] = true;
646         $result['warnings'] = $warnings;
647         return $result;
648     }
650     /**
651      * Returns description of method result value
652      *
653      * @return external_description
654      * @since Moodle 2.9
655      */
656     public static function view_forum_returns() {
657         return new external_single_structure(
658             array(
659                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
660                 'warnings' => new external_warnings()
661             )
662         );
663     }
665     /**
666      * Returns description of method parameters
667      *
668      * @return external_function_parameters
669      * @since Moodle 2.9
670      */
671     public static function view_forum_discussion_parameters() {
672         return new external_function_parameters(
673             array(
674                 'discussionid' => new external_value(PARAM_INT, 'discussion id')
675             )
676         );
677     }
679     /**
680      * Trigger the discussion viewed event.
681      *
682      * @param int $discussionid the discussion id
683      * @return array of warnings and status result
684      * @since Moodle 2.9
685      * @throws moodle_exception
686      */
687     public static function view_forum_discussion($discussionid) {
688         global $DB, $CFG, $USER;
689         require_once($CFG->dirroot . "/mod/forum/lib.php");
691         $params = self::validate_parameters(self::view_forum_discussion_parameters(),
692                                             array(
693                                                 'discussionid' => $discussionid
694                                             ));
695         $warnings = array();
697         $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
698         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
699         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
701         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
702         $modcontext = context_module::instance($cm->id);
703         self::validate_context($modcontext);
705         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
707         // Call the forum/lib API.
708         forum_discussion_view($modcontext, $forum, $discussion);
710         // Mark as read if required.
711         if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
712             forum_tp_mark_discussion_read($USER, $discussion->id);
713         }
715         $result = array();
716         $result['status'] = true;
717         $result['warnings'] = $warnings;
718         return $result;
719     }
721     /**
722      * Returns description of method result value
723      *
724      * @return external_description
725      * @since Moodle 2.9
726      */
727     public static function view_forum_discussion_returns() {
728         return new external_single_structure(
729             array(
730                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
731                 'warnings' => new external_warnings()
732             )
733         );
734     }
736     /**
737      * Returns description of method parameters
738      *
739      * @return external_function_parameters
740      * @since Moodle 3.0
741      */
742     public static function add_discussion_post_parameters() {
743         return new external_function_parameters(
744             array(
745                 'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
746                                                 (can be the initial discussion post'),
747                 'subject' => new external_value(PARAM_TEXT, 'new post subject'),
748                 'message' => new external_value(PARAM_RAW, 'new post message (only html format allowed)'),
749                 'options' => new external_multiple_structure (
750                     new external_single_structure(
751                         array(
752                             'name' => new external_value(PARAM_ALPHANUM,
753                                         'The allowed keys (value format) are:
754                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
755                                         inlineattachmentsid              (int); the draft file area id for inline attachments
756                                         attachmentsid       (int); the draft file area id for attachments
757                             '),
758                             'value' => new external_value(PARAM_RAW, 'the value of the option,
759                                                             this param is validated in the external function.'
760                         )
761                     )
762                 ), 'Options', VALUE_DEFAULT, array())
763             )
764         );
765     }
767     /**
768      * Create new posts into an existing discussion.
769      *
770      * @param int $postid the post id we are going to reply to
771      * @param string $subject new post subject
772      * @param string $message new post message (only html format allowed)
773      * @param array $options optional settings
774      * @return array of warnings and the new post id
775      * @since Moodle 3.0
776      * @throws moodle_exception
777      */
778     public static function add_discussion_post($postid, $subject, $message, $options = array()) {
779         global $DB, $CFG, $USER;
780         require_once($CFG->dirroot . "/mod/forum/lib.php");
782         $params = self::validate_parameters(self::add_discussion_post_parameters(),
783             array(
784                 'postid' => $postid,
785                 'subject' => $subject,
786                 'message' => $message,
787                 'options' => $options
788             )
789         );
790         $warnings = array();
792         if (!$parent = forum_get_post_full($params['postid'])) {
793             throw new moodle_exception('invalidparentpostid', 'forum');
794         }
796         if (!$discussion = $DB->get_record("forum_discussions", array("id" => $parent->discussion))) {
797             throw new moodle_exception('notpartofdiscussion', 'forum');
798         }
800         // Request and permission validation.
801         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
802         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
804         $context = context_module::instance($cm->id);
805         self::validate_context($context);
807         // Validate options.
808         $options = array(
809             'discussionsubscribe' => true,
810             'inlineattachmentsid' => 0,
811             'attachmentsid' => null
812         );
813         foreach ($params['options'] as $option) {
814             $name = trim($option['name']);
815             switch ($name) {
816                 case 'discussionsubscribe':
817                     $value = clean_param($option['value'], PARAM_BOOL);
818                     break;
819                 case 'inlineattachmentsid':
820                     $value = clean_param($option['value'], PARAM_INT);
821                     break;
822                 case 'attachmentsid':
823                     $value = clean_param($option['value'], PARAM_INT);
824                     // Ensure that the user has permissions to create attachments.
825                     if (!has_capability('mod/forum:createattachment', $context)) {
826                         $value = 0;
827                     }
828                     break;
829                 default:
830                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
831             }
832             $options[$name] = $value;
833         }
835         if (!forum_user_can_post($forum, $discussion, $USER, $cm, $course, $context)) {
836             throw new moodle_exception('nopostforum', 'forum');
837         }
839         $thresholdwarning = forum_check_throttling($forum, $cm);
840         forum_check_blocking_threshold($thresholdwarning);
842         // Create the post.
843         $post = new stdClass();
844         $post->discussion = $discussion->id;
845         $post->parent = $parent->id;
846         $post->subject = $params['subject'];
847         $post->message = $params['message'];
848         $post->messageformat = FORMAT_HTML;   // Force formatting for now.
849         $post->messagetrust = trusttext_trusted($context);
850         $post->itemid = $options['inlineattachmentsid'];
851         $post->attachments   = $options['attachmentsid'];
852         $fakemform = $post->attachments;
853         if ($postid = forum_add_new_post($post, $fakemform)) {
855             $post->id = $postid;
857             // Trigger events and completion.
858             $params = array(
859                 'context' => $context,
860                 'objectid' => $post->id,
861                 'other' => array(
862                     'discussionid' => $discussion->id,
863                     'forumid' => $forum->id,
864                     'forumtype' => $forum->type,
865                 )
866             );
867             $event = \mod_forum\event\post_created::create($params);
868             $event->add_record_snapshot('forum_posts', $post);
869             $event->add_record_snapshot('forum_discussions', $discussion);
870             $event->trigger();
872             // Update completion state.
873             $completion = new completion_info($course);
874             if ($completion->is_enabled($cm) &&
875                     ($forum->completionreplies || $forum->completionposts)) {
876                 $completion->update_state($cm, COMPLETION_COMPLETE);
877             }
879             $settings = new stdClass();
880             $settings->discussionsubscribe = $options['discussionsubscribe'];
881             forum_post_subscription($settings, $forum, $discussion);
882         } else {
883             throw new moodle_exception('couldnotadd', 'forum');
884         }
886         $result = array();
887         $result['postid'] = $postid;
888         $result['warnings'] = $warnings;
889         return $result;
890     }
892     /**
893      * Returns description of method result value
894      *
895      * @return external_description
896      * @since Moodle 3.0
897      */
898     public static function add_discussion_post_returns() {
899         return new external_single_structure(
900             array(
901                 'postid' => new external_value(PARAM_INT, 'new post id'),
902                 'warnings' => new external_warnings()
903             )
904         );
905     }
907     /**
908      * Returns description of method parameters
909      *
910      * @return external_function_parameters
911      * @since Moodle 3.0
912      */
913     public static function add_discussion_parameters() {
914         return new external_function_parameters(
915             array(
916                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
917                 'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
918                 'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
919                 'groupid' => new external_value(PARAM_INT, 'The group, default to 0', VALUE_DEFAULT, 0),
920                 'options' => new external_multiple_structure (
921                     new external_single_structure(
922                         array(
923                             'name' => new external_value(PARAM_ALPHANUM,
924                                         'The allowed keys (value format) are:
925                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
926                                         discussionpinned    (bool); is the discussion pinned, default to false
927                                         inlineattachmentsid              (int); the draft file area id for inline attachments
928                                         attachmentsid       (int); the draft file area id for attachments
929                             '),
930                             'value' => new external_value(PARAM_RAW, 'The value of the option,
931                                                             This param is validated in the external function.'
932                         )
933                     )
934                 ), 'Options', VALUE_DEFAULT, array())
935             )
936         );
937     }
939     /**
940      * Add a new discussion into an existing forum.
941      *
942      * @param int $forumid the forum instance id
943      * @param string $subject new discussion subject
944      * @param string $message new discussion message (only html format allowed)
945      * @param int $groupid the user course group
946      * @param array $options optional settings
947      * @return array of warnings and the new discussion id
948      * @since Moodle 3.0
949      * @throws moodle_exception
950      */
951     public static function add_discussion($forumid, $subject, $message, $groupid = 0, $options = array()) {
952         global $DB, $CFG;
953         require_once($CFG->dirroot . "/mod/forum/lib.php");
955         $params = self::validate_parameters(self::add_discussion_parameters(),
956                                             array(
957                                                 'forumid' => $forumid,
958                                                 'subject' => $subject,
959                                                 'message' => $message,
960                                                 'groupid' => $groupid,
961                                                 'options' => $options
962                                             ));
964         $warnings = array();
966         // Request and permission validation.
967         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
968         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
970         $context = context_module::instance($cm->id);
971         self::validate_context($context);
973         // Validate options.
974         $options = array(
975             'discussionsubscribe' => true,
976             'discussionpinned' => false,
977             'inlineattachmentsid' => 0,
978             'attachmentsid' => null
979         );
980         foreach ($params['options'] as $option) {
981             $name = trim($option['name']);
982             switch ($name) {
983                 case 'discussionsubscribe':
984                     $value = clean_param($option['value'], PARAM_BOOL);
985                     break;
986                 case 'discussionpinned':
987                     $value = clean_param($option['value'], PARAM_BOOL);
988                     break;
989                 case 'inlineattachmentsid':
990                     $value = clean_param($option['value'], PARAM_INT);
991                     break;
992                 case 'attachmentsid':
993                     $value = clean_param($option['value'], PARAM_INT);
994                     // Ensure that the user has permissions to create attachments.
995                     if (!has_capability('mod/forum:createattachment', $context)) {
996                         $value = 0;
997                     }
998                     break;
999                 default:
1000                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1001             }
1002             $options[$name] = $value;
1003         }
1005         // Normalize group.
1006         if (!groups_get_activity_groupmode($cm)) {
1007             // Groups not supported, force to -1.
1008             $groupid = -1;
1009         } else {
1010             // Check if we receive the default or and empty value for groupid,
1011             // in this case, get the group for the user in the activity.
1012             if (empty($params['groupid'])) {
1013                 $groupid = groups_get_activity_group($cm);
1014             } else {
1015                 // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
1016                 $groupid = $params['groupid'];
1017             }
1018         }
1020         if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
1021             throw new moodle_exception('cannotcreatediscussion', 'forum');
1022         }
1024         $thresholdwarning = forum_check_throttling($forum, $cm);
1025         forum_check_blocking_threshold($thresholdwarning);
1027         // Create the discussion.
1028         $discussion = new stdClass();
1029         $discussion->course = $course->id;
1030         $discussion->forum = $forum->id;
1031         $discussion->message = $params['message'];
1032         $discussion->messageformat = FORMAT_HTML;   // Force formatting for now.
1033         $discussion->messagetrust = trusttext_trusted($context);
1034         $discussion->itemid = $options['inlineattachmentsid'];
1035         $discussion->groupid = $groupid;
1036         $discussion->mailnow = 0;
1037         $discussion->subject = $params['subject'];
1038         $discussion->name = $discussion->subject;
1039         $discussion->timestart = 0;
1040         $discussion->timeend = 0;
1041         $discussion->attachments = $options['attachmentsid'];
1043         if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
1044             $discussion->pinned = FORUM_DISCUSSION_PINNED;
1045         } else {
1046             $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
1047         }
1048         $fakemform = $options['attachmentsid'];
1049         if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
1051             $discussion->id = $discussionid;
1053             // Trigger events and completion.
1055             $params = array(
1056                 'context' => $context,
1057                 'objectid' => $discussion->id,
1058                 'other' => array(
1059                     'forumid' => $forum->id,
1060                 )
1061             );
1062             $event = \mod_forum\event\discussion_created::create($params);
1063             $event->add_record_snapshot('forum_discussions', $discussion);
1064             $event->trigger();
1066             $completion = new completion_info($course);
1067             if ($completion->is_enabled($cm) &&
1068                     ($forum->completiondiscussions || $forum->completionposts)) {
1069                 $completion->update_state($cm, COMPLETION_COMPLETE);
1070             }
1072             $settings = new stdClass();
1073             $settings->discussionsubscribe = $options['discussionsubscribe'];
1074             forum_post_subscription($settings, $forum, $discussion);
1075         } else {
1076             throw new moodle_exception('couldnotadd', 'forum');
1077         }
1079         $result = array();
1080         $result['discussionid'] = $discussionid;
1081         $result['warnings'] = $warnings;
1082         return $result;
1083     }
1085     /**
1086      * Returns description of method result value
1087      *
1088      * @return external_description
1089      * @since Moodle 3.0
1090      */
1091     public static function add_discussion_returns() {
1092         return new external_single_structure(
1093             array(
1094                 'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
1095                 'warnings' => new external_warnings()
1096             )
1097         );
1098     }
1100     /**
1101      * Returns description of method parameters
1102      *
1103      * @return external_function_parameters
1104      * @since Moodle 3.1
1105      */
1106     public static function can_add_discussion_parameters() {
1107         return new external_function_parameters(
1108             array(
1109                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1110                 'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1111                                                 Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1112             )
1113         );
1114     }
1116     /**
1117      * Check if the current user can add discussions in the given forum (and optionally for the given group).
1118      *
1119      * @param int $forumid the forum instance id
1120      * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1121      * @return array of warnings and the status (true if the user can add discussions)
1122      * @since Moodle 3.1
1123      * @throws moodle_exception
1124      */
1125     public static function can_add_discussion($forumid, $groupid = null) {
1126         global $DB, $CFG;
1127         require_once($CFG->dirroot . "/mod/forum/lib.php");
1129         $params = self::validate_parameters(self::can_add_discussion_parameters(),
1130                                             array(
1131                                                 'forumid' => $forumid,
1132                                                 'groupid' => $groupid,
1133                                             ));
1134         $warnings = array();
1136         // Request and permission validation.
1137         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1138         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1140         $context = context_module::instance($cm->id);
1141         self::validate_context($context);
1143         $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1145         $result = array();
1146         $result['status'] = $status;
1147         $result['canpindiscussions'] = has_capability('mod/forum:pindiscussions', $context);
1148         $result['cancreateattachment'] = forum_can_create_attachment($forum, $context);
1149         $result['warnings'] = $warnings;
1150         return $result;
1151     }
1153     /**
1154      * Returns description of method result value
1155      *
1156      * @return external_description
1157      * @since Moodle 3.1
1158      */
1159     public static function can_add_discussion_returns() {
1160         return new external_single_structure(
1161             array(
1162                 'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
1163                 'canpindiscussions' => new external_value(PARAM_BOOL, 'True if the user can pin discussions, false otherwise.',
1164                     VALUE_OPTIONAL),
1165                 'cancreateattachment' => new external_value(PARAM_BOOL, 'True if the user can add attachments, false otherwise.',
1166                     VALUE_OPTIONAL),
1167                 'warnings' => new external_warnings()
1168             )
1169         );
1170     }