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