c0db8fed07ed6e56baedca63eaff5d9bb766d04f
[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                 ), 'forum'
147             )
148         );
149     }
151     /**
152      * Describes the parameters for get_forum_discussion_posts.
153      *
154      * @return external_external_function_parameters
155      * @since Moodle 2.7
156      */
157     public static function get_forum_discussion_posts_parameters() {
158         return new external_function_parameters (
159             array(
160                 'discussionid' => new external_value(PARAM_INT, 'discussion ID', VALUE_REQUIRED),
161                 'sortby' => new external_value(PARAM_ALPHA,
162                     'sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
163                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
164             )
165         );
166     }
168     /**
169      * Returns a list of forum posts for a discussion
170      *
171      * @param int $discussionid the post ids
172      * @param string $sortby sort by this element (id, created or modified)
173      * @param string $sortdirection sort direction: ASC or DESC
174      *
175      * @return array the forum post details
176      * @since Moodle 2.7
177      */
178     public static function get_forum_discussion_posts($discussionid, $sortby = "created", $sortdirection = "DESC") {
179         global $CFG, $DB, $USER, $PAGE;
181         $posts = array();
182         $warnings = array();
184         // Validate the parameter.
185         $params = self::validate_parameters(self::get_forum_discussion_posts_parameters(),
186             array(
187                 'discussionid' => $discussionid,
188                 'sortby' => $sortby,
189                 'sortdirection' => $sortdirection));
191         // Compact/extract functions are not recommended.
192         $discussionid   = $params['discussionid'];
193         $sortby         = $params['sortby'];
194         $sortdirection  = $params['sortdirection'];
196         $sortallowedvalues = array('id', 'created', 'modified');
197         if (!in_array($sortby, $sortallowedvalues)) {
198             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
199                 'allowed values are: ' . implode(',', $sortallowedvalues));
200         }
202         $sortdirection = strtoupper($sortdirection);
203         $directionallowedvalues = array('ASC', 'DESC');
204         if (!in_array($sortdirection, $directionallowedvalues)) {
205             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
206                 'allowed values are: ' . implode(',', $directionallowedvalues));
207         }
209         $discussion = $DB->get_record('forum_discussions', array('id' => $discussionid), '*', MUST_EXIST);
210         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
211         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
212         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
214         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
215         $modcontext = context_module::instance($cm->id);
216         self::validate_context($modcontext);
218         // This require must be here, see mod/forum/discuss.php.
219         require_once($CFG->dirroot . "/mod/forum/lib.php");
221         // Check they have the view forum capability.
222         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
224         if (! $post = forum_get_post_full($discussion->firstpost)) {
225             throw new moodle_exception('notexists', 'forum');
226         }
228         // This function check groups, qanda, timed discussions, etc.
229         if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
230             throw new moodle_exception('noviewdiscussionspermission', 'forum');
231         }
233         $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
235         // We will add this field in the response.
236         $canreply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
238         $forumtracked = forum_tp_is_tracked($forum);
240         $sort = 'p.' . $sortby . ' ' . $sortdirection;
241         $allposts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
243         foreach ($allposts as $post) {
245             if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
246                 $warning = array();
247                 $warning['item'] = 'post';
248                 $warning['itemid'] = $post->id;
249                 $warning['warningcode'] = '1';
250                 $warning['message'] = 'You can\'t see this post';
251                 $warnings[] = $warning;
252                 continue;
253             }
255             // Function forum_get_all_discussion_posts adds postread field.
256             // Note that the value returned can be a boolean or an integer. The WS expects a boolean.
257             if (empty($post->postread)) {
258                 $post->postread = false;
259             } else {
260                 $post->postread = true;
261             }
263             $post->canreply = $canreply;
264             if (!empty($post->children)) {
265                 $post->children = array_keys($post->children);
266             } else {
267                 $post->children = array();
268             }
270             $user = new stdclass();
271             $user->id = $post->userid;
272             $user = username_load_fields_from_object($user, $post, null, array('picture', 'imagealt', 'email'));
273             $post->userfullname = fullname($user, $canviewfullname);
275             $userpicture = new user_picture($user);
276             $userpicture->size = 1; // Size f1.
277             $post->userpictureurl = $userpicture->get_url($PAGE)->out(false);
279             $post->subject = external_format_string($post->subject, $modcontext->id);
280             // Rewrite embedded images URLs.
281             list($post->message, $post->messageformat) =
282                 external_format_text($post->message, $post->messageformat, $modcontext->id, 'mod_forum', 'post', $post->id);
284             // List attachments.
285             if (!empty($post->attachment)) {
286                 $post->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment', $post->id);
287             }
289             $posts[] = $post;
290         }
292         $result = array();
293         $result['posts'] = $posts;
294         $result['warnings'] = $warnings;
295         return $result;
296     }
298     /**
299      * Describes the get_forum_discussion_posts return value.
300      *
301      * @return external_single_structure
302      * @since Moodle 2.7
303      */
304     public static function get_forum_discussion_posts_returns() {
305         return new external_single_structure(
306             array(
307                 'posts' => new external_multiple_structure(
308                         new external_single_structure(
309                             array(
310                                 'id' => new external_value(PARAM_INT, 'Post id'),
311                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
312                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
313                                 'userid' => new external_value(PARAM_INT, 'User id'),
314                                 'created' => new external_value(PARAM_INT, 'Creation time'),
315                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
316                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
317                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
318                                 'message' => new external_value(PARAM_RAW, 'The post message'),
319                                 'messageformat' => new external_format_value('message'),
320                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
321                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
322                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
323                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
324                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
325                                 'children' => new external_multiple_structure(new external_value(PARAM_INT, 'children post id')),
326                                 'canreply' => new external_value(PARAM_BOOL, 'The user can reply to posts?'),
327                                 'postread' => new external_value(PARAM_BOOL, 'The post was read'),
328                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
329                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL)
330                             ), 'post'
331                         )
332                     ),
333                 'warnings' => new external_warnings()
334             )
335         );
336     }
338     /**
339      * Describes the parameters for get_forum_discussions_paginated.
340      *
341      * @return external_external_function_parameters
342      * @since Moodle 2.8
343      */
344     public static function get_forum_discussions_paginated_parameters() {
345         return new external_function_parameters (
346             array(
347                 'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
348                 'sortby' => new external_value(PARAM_ALPHA,
349                     'sort by this element: id, timemodified, timestart or timeend', VALUE_DEFAULT, 'timemodified'),
350                 'sortdirection' => new external_value(PARAM_ALPHA, 'sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
351                 'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
352                 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
353             )
354         );
355     }
357     /**
358      * Returns a list of forum discussions optionally sorted and paginated.
359      *
360      * @param int $forumid the forum instance id
361      * @param string $sortby sort by this element (id, timemodified, timestart or timeend)
362      * @param string $sortdirection sort direction: ASC or DESC
363      * @param int $page page number
364      * @param int $perpage items per page
365      *
366      * @return array the forum discussion details including warnings
367      * @since Moodle 2.8
368      */
369     public static function get_forum_discussions_paginated($forumid, $sortby = 'timemodified', $sortdirection = 'DESC',
370                                                     $page = -1, $perpage = 0) {
371         global $CFG, $DB, $USER, $PAGE;
373         require_once($CFG->dirroot . "/mod/forum/lib.php");
375         $warnings = array();
376         $discussions = array();
378         $params = self::validate_parameters(self::get_forum_discussions_paginated_parameters(),
379             array(
380                 'forumid' => $forumid,
381                 'sortby' => $sortby,
382                 'sortdirection' => $sortdirection,
383                 'page' => $page,
384                 'perpage' => $perpage
385             )
386         );
388         // Compact/extract functions are not recommended.
389         $forumid        = $params['forumid'];
390         $sortby         = $params['sortby'];
391         $sortdirection  = $params['sortdirection'];
392         $page           = $params['page'];
393         $perpage        = $params['perpage'];
395         $sortallowedvalues = array('id', 'timemodified', 'timestart', 'timeend');
396         if (!in_array($sortby, $sortallowedvalues)) {
397             throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
398                 'allowed values are: ' . implode(',', $sortallowedvalues));
399         }
401         $sortdirection = strtoupper($sortdirection);
402         $directionallowedvalues = array('ASC', 'DESC');
403         if (!in_array($sortdirection, $directionallowedvalues)) {
404             throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
405                 'allowed values are: ' . implode(',', $directionallowedvalues));
406         }
408         $forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
409         $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
410         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
412         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
413         $modcontext = context_module::instance($cm->id);
414         self::validate_context($modcontext);
416         // Check they have the view forum capability.
417         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
419         $sort = 'd.pinned DESC, d.' . $sortby . ' ' . $sortdirection;
420         $alldiscussions = forum_get_discussions($cm, $sort, true, -1, -1, true, $page, $perpage, FORUM_POSTS_ALL_USER_GROUPS);
422         if ($alldiscussions) {
423             $canviewfullname = has_capability('moodle/site:viewfullnames', $modcontext);
425             // Get the unreads array, this takes a forum id and returns data for all discussions.
426             $unreads = array();
427             if ($cantrack = forum_tp_can_track_forums($forum)) {
428                 if ($forumtracked = forum_tp_is_tracked($forum)) {
429                     $unreads = forum_get_discussions_unread($cm);
430                 }
431             }
432             // The forum function returns the replies for all the discussions in a given forum.
433             $replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage);
435             foreach ($alldiscussions as $discussion) {
437                 // This function checks for qanda forums.
438                 // Note that the forum_get_discussions returns as id the post id, not the discussion id so we need to do this.
439                 $discussionrec = clone $discussion;
440                 $discussionrec->id = $discussion->discussion;
441                 if (!forum_user_can_see_discussion($forum, $discussionrec, $modcontext)) {
442                     $warning = array();
443                     // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
444                     $warning['item'] = 'post';
445                     $warning['itemid'] = $discussion->id;
446                     $warning['warningcode'] = '1';
447                     $warning['message'] = 'You can\'t see this discussion';
448                     $warnings[] = $warning;
449                     continue;
450                 }
452                 $discussion->numunread = 0;
453                 if ($cantrack && $forumtracked) {
454                     if (isset($unreads[$discussion->discussion])) {
455                         $discussion->numunread = (int) $unreads[$discussion->discussion];
456                     }
457                 }
459                 $discussion->numreplies = 0;
460                 if (!empty($replies[$discussion->discussion])) {
461                     $discussion->numreplies = (int) $replies[$discussion->discussion]->replies;
462                 }
464                 $picturefields = explode(',', user_picture::fields());
466                 // Load user objects from the results of the query.
467                 $user = new stdclass();
468                 $user->id = $discussion->userid;
469                 $user = username_load_fields_from_object($user, $discussion, null, $picturefields);
470                 // Preserve the id, it can be modified by username_load_fields_from_object.
471                 $user->id = $discussion->userid;
472                 $discussion->userfullname = fullname($user, $canviewfullname);
474                 $userpicture = new user_picture($user);
475                 $userpicture->size = 1; // Size f1.
476                 $discussion->userpictureurl = $userpicture->get_url($PAGE)->out(false);
478                 $usermodified = new stdclass();
479                 $usermodified->id = $discussion->usermodified;
480                 $usermodified = username_load_fields_from_object($usermodified, $discussion, 'um', $picturefields);
481                 // Preserve the id (it can be overwritten due to the prefixed $picturefields).
482                 $usermodified->id = $discussion->usermodified;
483                 $discussion->usermodifiedfullname = fullname($usermodified, $canviewfullname);
485                 $userpicture = new user_picture($usermodified);
486                 $userpicture->size = 1; // Size f1.
487                 $discussion->usermodifiedpictureurl = $userpicture->get_url($PAGE)->out(false);
489                 $discussion->name = external_format_string($discussion->name, $modcontext->id);
490                 $discussion->subject = external_format_string($discussion->subject, $modcontext->id);
491                 // Rewrite embedded images URLs.
492                 list($discussion->message, $discussion->messageformat) =
493                     external_format_text($discussion->message, $discussion->messageformat,
494                                             $modcontext->id, 'mod_forum', 'post', $discussion->id);
496                 // List attachments.
497                 if (!empty($discussion->attachment)) {
498                     $discussion->attachments = external_util::get_area_files($modcontext->id, 'mod_forum', 'attachment',
499                                                                                 $discussion->id);
500                 }
502                 $discussions[] = $discussion;
503             }
504         }
506         $result = array();
507         $result['discussions'] = $discussions;
508         $result['warnings'] = $warnings;
509         return $result;
511     }
513     /**
514      * Describes the get_forum_discussions_paginated return value.
515      *
516      * @return external_single_structure
517      * @since Moodle 2.8
518      */
519     public static function get_forum_discussions_paginated_returns() {
520         return new external_single_structure(
521             array(
522                 'discussions' => new external_multiple_structure(
523                         new external_single_structure(
524                             array(
525                                 'id' => new external_value(PARAM_INT, 'Post id'),
526                                 'name' => new external_value(PARAM_TEXT, 'Discussion name'),
527                                 'groupid' => new external_value(PARAM_INT, 'Group id'),
528                                 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
529                                 'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
530                                 'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
531                                 'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
532                                 'discussion' => new external_value(PARAM_INT, 'Discussion id'),
533                                 'parent' => new external_value(PARAM_INT, 'Parent id'),
534                                 'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
535                                 'created' => new external_value(PARAM_INT, 'Creation time'),
536                                 'modified' => new external_value(PARAM_INT, 'Time modified'),
537                                 'mailed' => new external_value(PARAM_INT, 'Mailed?'),
538                                 'subject' => new external_value(PARAM_TEXT, 'The post subject'),
539                                 'message' => new external_value(PARAM_RAW, 'The post message'),
540                                 'messageformat' => new external_format_value('message'),
541                                 'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
542                                 'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
543                                 'attachments' => new external_files('attachments', VALUE_OPTIONAL),
544                                 'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
545                                 'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
546                                 'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
547                                 'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
548                                 'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
549                                 'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
550                                 'numreplies' => new external_value(PARAM_TEXT, 'The number of replies in the discussion'),
551                                 'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
552                                 'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned')
553                             ), 'post'
554                         )
555                     ),
556                 'warnings' => new external_warnings()
557             )
558         );
559     }
561     /**
562      * Returns description of method parameters
563      *
564      * @return external_function_parameters
565      * @since Moodle 2.9
566      */
567     public static function view_forum_parameters() {
568         return new external_function_parameters(
569             array(
570                 'forumid' => new external_value(PARAM_INT, 'forum instance id')
571             )
572         );
573     }
575     /**
576      * Trigger the course module viewed event and update the module completion status.
577      *
578      * @param int $forumid the forum instance id
579      * @return array of warnings and status result
580      * @since Moodle 2.9
581      * @throws moodle_exception
582      */
583     public static function view_forum($forumid) {
584         global $DB, $CFG;
585         require_once($CFG->dirroot . "/mod/forum/lib.php");
587         $params = self::validate_parameters(self::view_forum_parameters(),
588                                             array(
589                                                 'forumid' => $forumid
590                                             ));
591         $warnings = array();
593         // Request and permission validation.
594         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
595         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
597         $context = context_module::instance($cm->id);
598         self::validate_context($context);
600         require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
602         // Call the forum/lib API.
603         forum_view($forum, $course, $cm, $context);
605         $result = array();
606         $result['status'] = true;
607         $result['warnings'] = $warnings;
608         return $result;
609     }
611     /**
612      * Returns description of method result value
613      *
614      * @return external_description
615      * @since Moodle 2.9
616      */
617     public static function view_forum_returns() {
618         return new external_single_structure(
619             array(
620                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
621                 'warnings' => new external_warnings()
622             )
623         );
624     }
626     /**
627      * Returns description of method parameters
628      *
629      * @return external_function_parameters
630      * @since Moodle 2.9
631      */
632     public static function view_forum_discussion_parameters() {
633         return new external_function_parameters(
634             array(
635                 'discussionid' => new external_value(PARAM_INT, 'discussion id')
636             )
637         );
638     }
640     /**
641      * Trigger the discussion viewed event.
642      *
643      * @param int $discussionid the discussion id
644      * @return array of warnings and status result
645      * @since Moodle 2.9
646      * @throws moodle_exception
647      */
648     public static function view_forum_discussion($discussionid) {
649         global $DB, $CFG;
650         require_once($CFG->dirroot . "/mod/forum/lib.php");
652         $params = self::validate_parameters(self::view_forum_discussion_parameters(),
653                                             array(
654                                                 'discussionid' => $discussionid
655                                             ));
656         $warnings = array();
658         $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
659         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
660         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
662         // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
663         $modcontext = context_module::instance($cm->id);
664         self::validate_context($modcontext);
666         require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
668         // Call the forum/lib API.
669         forum_discussion_view($modcontext, $forum, $discussion);
671         $result = array();
672         $result['status'] = true;
673         $result['warnings'] = $warnings;
674         return $result;
675     }
677     /**
678      * Returns description of method result value
679      *
680      * @return external_description
681      * @since Moodle 2.9
682      */
683     public static function view_forum_discussion_returns() {
684         return new external_single_structure(
685             array(
686                 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
687                 'warnings' => new external_warnings()
688             )
689         );
690     }
692     /**
693      * Returns description of method parameters
694      *
695      * @return external_function_parameters
696      * @since Moodle 3.0
697      */
698     public static function add_discussion_post_parameters() {
699         return new external_function_parameters(
700             array(
701                 'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
702                                                 (can be the initial discussion post'),
703                 'subject' => new external_value(PARAM_TEXT, 'new post subject'),
704                 'message' => new external_value(PARAM_RAW, 'new post message (only html format allowed)'),
705                 'options' => new external_multiple_structure (
706                     new external_single_structure(
707                         array(
708                             'name' => new external_value(PARAM_ALPHANUM,
709                                         'The allowed keys (value format) are:
710                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
711                                         inlineattachmentsid              (int); the draft file area id for inline attachments
712                                         attachmentsid       (int); the draft file area id for attachments
713                             '),
714                             'value' => new external_value(PARAM_RAW, 'the value of the option,
715                                                             this param is validated in the external function.'
716                         )
717                     )
718                 ), 'Options', VALUE_DEFAULT, array())
719             )
720         );
721     }
723     /**
724      * Create new posts into an existing discussion.
725      *
726      * @param int $postid the post id we are going to reply to
727      * @param string $subject new post subject
728      * @param string $message new post message (only html format allowed)
729      * @param array $options optional settings
730      * @return array of warnings and the new post id
731      * @since Moodle 3.0
732      * @throws moodle_exception
733      */
734     public static function add_discussion_post($postid, $subject, $message, $options = array()) {
735         global $DB, $CFG, $USER;
736         require_once($CFG->dirroot . "/mod/forum/lib.php");
738         $params = self::validate_parameters(self::add_discussion_post_parameters(),
739                                             array(
740                                                 'postid' => $postid,
741                                                 'subject' => $subject,
742                                                 'message' => $message,
743                                                 'options' => $options
744                                             ));
745         // Validate options.
746         $options = array(
747             'discussionsubscribe' => true,
748             'inlineattachmentsid' => 0,
749             'attachmentsid' => null
750         );
751         foreach ($params['options'] as $option) {
752             $name = trim($option['name']);
753             switch ($name) {
754                 case 'discussionsubscribe':
755                     $value = clean_param($option['value'], PARAM_BOOL);
756                     break;
757                 case 'inlineattachmentsid':
758                     $value = clean_param($option['value'], PARAM_INT);
759                     break;
760                 case 'attachmentsid':
761                     $value = clean_param($option['value'], PARAM_INT);
762                     break;
763                 default:
764                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
765             }
766             $options[$name] = $value;
767         }
769         $warnings = array();
771         if (!$parent = forum_get_post_full($params['postid'])) {
772             throw new moodle_exception('invalidparentpostid', 'forum');
773         }
775         if (!$discussion = $DB->get_record("forum_discussions", array("id" => $parent->discussion))) {
776             throw new moodle_exception('notpartofdiscussion', 'forum');
777         }
779         // Request and permission validation.
780         $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
781         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
783         $context = context_module::instance($cm->id);
784         self::validate_context($context);
786         if (!forum_user_can_post($forum, $discussion, $USER, $cm, $course, $context)) {
787             throw new moodle_exception('nopostforum', 'forum');
788         }
790         $thresholdwarning = forum_check_throttling($forum, $cm);
791         forum_check_blocking_threshold($thresholdwarning);
793         // Create the post.
794         $post = new stdClass();
795         $post->discussion = $discussion->id;
796         $post->parent = $parent->id;
797         $post->subject = $params['subject'];
798         $post->message = $params['message'];
799         $post->messageformat = FORMAT_HTML;   // Force formatting for now.
800         $post->messagetrust = trusttext_trusted($context);
801         $post->itemid = $options['inlineattachmentsid'];
802         $post->attachments   = $options['attachmentsid'];
803         $fakemform = $post->attachments;
804         if ($postid = forum_add_new_post($post, $fakemform)) {
806             $post->id = $postid;
808             // Trigger events and completion.
809             $params = array(
810                 'context' => $context,
811                 'objectid' => $post->id,
812                 'other' => array(
813                     'discussionid' => $discussion->id,
814                     'forumid' => $forum->id,
815                     'forumtype' => $forum->type,
816                 )
817             );
818             $event = \mod_forum\event\post_created::create($params);
819             $event->add_record_snapshot('forum_posts', $post);
820             $event->add_record_snapshot('forum_discussions', $discussion);
821             $event->trigger();
823             // Update completion state.
824             $completion = new completion_info($course);
825             if ($completion->is_enabled($cm) &&
826                     ($forum->completionreplies || $forum->completionposts)) {
827                 $completion->update_state($cm, COMPLETION_COMPLETE);
828             }
830             $settings = new stdClass();
831             $settings->discussionsubscribe = $options['discussionsubscribe'];
832             forum_post_subscription($settings, $forum, $discussion);
833         } else {
834             throw new moodle_exception('couldnotadd', 'forum');
835         }
837         $result = array();
838         $result['postid'] = $postid;
839         $result['warnings'] = $warnings;
840         return $result;
841     }
843     /**
844      * Returns description of method result value
845      *
846      * @return external_description
847      * @since Moodle 3.0
848      */
849     public static function add_discussion_post_returns() {
850         return new external_single_structure(
851             array(
852                 'postid' => new external_value(PARAM_INT, 'new post id'),
853                 'warnings' => new external_warnings()
854             )
855         );
856     }
858     /**
859      * Returns description of method parameters
860      *
861      * @return external_function_parameters
862      * @since Moodle 3.0
863      */
864     public static function add_discussion_parameters() {
865         return new external_function_parameters(
866             array(
867                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
868                 'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
869                 'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
870                 'groupid' => new external_value(PARAM_INT, 'The group, default to -1', VALUE_DEFAULT, -1),
871                 'options' => new external_multiple_structure (
872                     new external_single_structure(
873                         array(
874                             'name' => new external_value(PARAM_ALPHANUM,
875                                         'The allowed keys (value format) are:
876                                         discussionsubscribe (bool); subscribe to the discussion?, default to true
877                                         discussionpinned    (bool); is the discussion pinned, default to false
878                                         inlineattachmentsid              (int); the draft file area id for inline attachments
879                                         attachmentsid       (int); the draft file area id for attachments
880                             '),
881                             'value' => new external_value(PARAM_RAW, 'The value of the option,
882                                                             This param is validated in the external function.'
883                         )
884                     )
885                 ), 'Options', VALUE_DEFAULT, array())
886             )
887         );
888     }
890     /**
891      * Add a new discussion into an existing forum.
892      *
893      * @param int $forumid the forum instance id
894      * @param string $subject new discussion subject
895      * @param string $message new discussion message (only html format allowed)
896      * @param int $groupid the user course group
897      * @param array $options optional settings
898      * @return array of warnings and the new discussion id
899      * @since Moodle 3.0
900      * @throws moodle_exception
901      */
902     public static function add_discussion($forumid, $subject, $message, $groupid = -1, $options = array()) {
903         global $DB, $CFG;
904         require_once($CFG->dirroot . "/mod/forum/lib.php");
906         $params = self::validate_parameters(self::add_discussion_parameters(),
907                                             array(
908                                                 'forumid' => $forumid,
909                                                 'subject' => $subject,
910                                                 'message' => $message,
911                                                 'groupid' => $groupid,
912                                                 'options' => $options
913                                             ));
914         // Validate options.
915         $options = array(
916             'discussionsubscribe' => true,
917             'discussionpinned' => false,
918             'inlineattachmentsid' => 0,
919             'attachmentsid' => null
920         );
921         foreach ($params['options'] as $option) {
922             $name = trim($option['name']);
923             switch ($name) {
924                 case 'discussionsubscribe':
925                     $value = clean_param($option['value'], PARAM_BOOL);
926                     break;
927                 case 'discussionpinned':
928                     $value = clean_param($option['value'], PARAM_BOOL);
929                     break;
930                 case 'inlineattachmentsid':
931                     $value = clean_param($option['value'], PARAM_INT);
932                     break;
933                 case 'attachmentsid':
934                     $value = clean_param($option['value'], PARAM_INT);
935                     break;
936                 default:
937                     throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
938             }
939             $options[$name] = $value;
940         }
942         $warnings = array();
944         // Request and permission validation.
945         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
946         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
948         $context = context_module::instance($cm->id);
949         self::validate_context($context);
951         // Normalize group.
952         if (!groups_get_activity_groupmode($cm)) {
953             // Groups not supported, force to -1.
954             $groupid = -1;
955         } else {
956             // Check if we receive the default or and empty value for groupid,
957             // in this case, get the group for the user in the activity.
958             if ($groupid === -1 or empty($params['groupid'])) {
959                 $groupid = groups_get_activity_group($cm);
960             } else {
961                 // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
962                 $groupid = $params['groupid'];
963             }
964         }
966         if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
967             throw new moodle_exception('cannotcreatediscussion', 'forum');
968         }
970         $thresholdwarning = forum_check_throttling($forum, $cm);
971         forum_check_blocking_threshold($thresholdwarning);
973         // Create the discussion.
974         $discussion = new stdClass();
975         $discussion->course = $course->id;
976         $discussion->forum = $forum->id;
977         $discussion->message = $params['message'];
978         $discussion->messageformat = FORMAT_HTML;   // Force formatting for now.
979         $discussion->messagetrust = trusttext_trusted($context);
980         $discussion->itemid = $options['inlineattachmentsid'];
981         $discussion->groupid = $groupid;
982         $discussion->mailnow = 0;
983         $discussion->subject = $params['subject'];
984         $discussion->name = $discussion->subject;
985         $discussion->timestart = 0;
986         $discussion->timeend = 0;
987         $discussion->attachments = $options['attachmentsid'];
989         if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
990             $discussion->pinned = FORUM_DISCUSSION_PINNED;
991         } else {
992             $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
993         }
994         $fakemform = $options['attachmentsid'];
995         if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
997             $discussion->id = $discussionid;
999             // Trigger events and completion.
1001             $params = array(
1002                 'context' => $context,
1003                 'objectid' => $discussion->id,
1004                 'other' => array(
1005                     'forumid' => $forum->id,
1006                 )
1007             );
1008             $event = \mod_forum\event\discussion_created::create($params);
1009             $event->add_record_snapshot('forum_discussions', $discussion);
1010             $event->trigger();
1012             $completion = new completion_info($course);
1013             if ($completion->is_enabled($cm) &&
1014                     ($forum->completiondiscussions || $forum->completionposts)) {
1015                 $completion->update_state($cm, COMPLETION_COMPLETE);
1016             }
1018             $settings = new stdClass();
1019             $settings->discussionsubscribe = $options['discussionsubscribe'];
1020             forum_post_subscription($settings, $forum, $discussion);
1021         } else {
1022             throw new moodle_exception('couldnotadd', 'forum');
1023         }
1025         $result = array();
1026         $result['discussionid'] = $discussionid;
1027         $result['warnings'] = $warnings;
1028         return $result;
1029     }
1031     /**
1032      * Returns description of method result value
1033      *
1034      * @return external_description
1035      * @since Moodle 3.0
1036      */
1037     public static function add_discussion_returns() {
1038         return new external_single_structure(
1039             array(
1040                 'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
1041                 'warnings' => new external_warnings()
1042             )
1043         );
1044     }
1046     /**
1047      * Returns description of method parameters
1048      *
1049      * @return external_function_parameters
1050      * @since Moodle 3.1
1051      */
1052     public static function can_add_discussion_parameters() {
1053         return new external_function_parameters(
1054             array(
1055                 'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1056                 'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1057                                                 Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1058             )
1059         );
1060     }
1062     /**
1063      * Check if the current user can add discussions in the given forum (and optionally for the given group).
1064      *
1065      * @param int $forumid the forum instance id
1066      * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1067      * @return array of warnings and the status (true if the user can add discussions)
1068      * @since Moodle 3.1
1069      * @throws moodle_exception
1070      */
1071     public static function can_add_discussion($forumid, $groupid = null) {
1072         global $DB, $CFG;
1073         require_once($CFG->dirroot . "/mod/forum/lib.php");
1075         $params = self::validate_parameters(self::can_add_discussion_parameters(),
1076                                             array(
1077                                                 'forumid' => $forumid,
1078                                                 'groupid' => $groupid,
1079                                             ));
1080         $warnings = array();
1082         // Request and permission validation.
1083         $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1084         list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1086         $context = context_module::instance($cm->id);
1087         self::validate_context($context);
1089         $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1091         $result = array();
1092         $result['status'] = $status;
1093         $result['warnings'] = $warnings;
1094         return $result;
1095     }
1097     /**
1098      * Returns description of method result value
1099      *
1100      * @return external_description
1101      * @since Moodle 3.1
1102      */
1103     public static function can_add_discussion_returns() {
1104         return new external_single_structure(
1105             array(
1106                 'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
1107                 'warnings' => new external_warnings()
1108             )
1109         );
1110     }