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