Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
use mod_forum\local\exporters\post as post_exporter;
19
use mod_forum\local\exporters\discussion as discussion_exporter;
20
use core_external\external_api;
21
use core_external\external_files;
22
use core_external\external_format_value;
23
use core_external\external_function_parameters;
24
use core_external\external_multiple_structure;
25
use core_external\external_single_structure;
26
use core_external\external_value;
27
use core_external\external_warnings;
28
use core_external\util as external_util;
29
 
30
/**
31
 * External forum API
32
 *
33
 * @package    mod_forum
34
 * @copyright  2012 Mark Nelson <markn@moodle.com>
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class mod_forum_external extends external_api {
38
 
39
    /**
40
     * Describes the parameters for get_forum.
41
     *
42
     * @return external_function_parameters
43
     * @since Moodle 2.5
44
     */
45
    public static function get_forums_by_courses_parameters() {
46
        return new external_function_parameters (
47
            array(
48
                'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID',
49
                        VALUE_REQUIRED, '', NULL_NOT_ALLOWED), 'Array of Course IDs', VALUE_DEFAULT, array()),
50
            )
51
        );
52
    }
53
 
54
    /**
55
     * Returns a list of forums in a provided list of courses,
56
     * if no list is provided all forums that the user can view
57
     * will be returned.
58
     *
59
     * @param array $courseids the course ids
60
     * @return array the forum details
61
     * @since Moodle 2.5
62
     */
63
    public static function get_forums_by_courses($courseids = array()) {
64
        global $CFG;
65
 
66
        require_once($CFG->dirroot . "/mod/forum/lib.php");
67
 
68
        $params = self::validate_parameters(self::get_forums_by_courses_parameters(), array('courseids' => $courseids));
69
 
70
        $courses = array();
71
        if (empty($params['courseids'])) {
72
            $courses = enrol_get_my_courses();
73
            $params['courseids'] = array_keys($courses);
74
        }
75
 
76
        // Array to store the forums to return.
77
        $arrforums = array();
78
        $warnings = array();
79
 
80
        // Ensure there are courseids to loop through.
81
        if (!empty($params['courseids'])) {
82
 
83
            list($courses, $warnings) = external_util::validate_courses($params['courseids'], $courses);
84
 
85
            // Get the forums in this course. This function checks users visibility permissions.
86
            $forums = get_all_instances_in_courses("forum", $courses);
87
            foreach ($forums as $forum) {
88
 
89
                $course = $courses[$forum->course];
90
                $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
91
                $context = context_module::instance($cm->id);
92
 
93
                // Skip forums we are not allowed to see discussions.
94
                if (!has_capability('mod/forum:viewdiscussion', $context)) {
95
                    continue;
96
                }
97
 
98
                $forum->name = \core_external\util::format_string($forum->name, $context);
99
                // Format the intro before being returning using the format setting.
100
                $options = array('noclean' => true);
101
                [$forum->intro, $forum->introformat] = \core_external\util::format_text(
102
                    $forum->intro,
103
                    $forum->introformat,
104
                    $context,
105
                    'mod_forum',
106
                    'intro',
107
                    null,
108
                    $options
109
                );
110
                $forum->introfiles = external_util::get_area_files($context->id, 'mod_forum', 'intro', false, false);
111
                $forum->lang = clean_param($forum->lang, PARAM_LANG);
112
 
113
                // Discussions count. This function does static request cache.
114
                $forum->numdiscussions = forum_count_discussions($forum, $cm, $course);
115
                $forum->cmid = $forum->coursemodule;
116
                $forum->cancreatediscussions = forum_user_can_post_discussion($forum, null, -1, $cm, $context);
117
                $forum->istracked = forum_tp_is_tracked($forum);
118
                if ($forum->istracked) {
119
                    $forum->unreadpostscount = forum_tp_count_forum_unread_posts($cm, $course);
120
                }
121
 
122
                // Add the forum to the array to return.
123
                $arrforums[$forum->id] = $forum;
124
            }
125
        }
126
 
127
        return $arrforums;
128
    }
129
 
130
    /**
131
     * Describes the get_forum return value.
132
     *
133
     * @return external_single_structure
134
     * @since Moodle 2.5
135
     */
136
    public static function get_forums_by_courses_returns() {
137
        // This should be using helper_for_get_mods_by_courses::standard_coursemodule_elements_returns, but it is so horribly
138
        // inconsistent with all similar web serviecs in other modules that we just can't.
139
        // Also, the return type declaration is wrong, but I am not changing it now because I don't want ot break things.
140
        return new external_multiple_structure(
141
            new external_single_structure(
142
                array(
143
                    'id' => new external_value(PARAM_INT, 'Forum id'),
144
                    'course' => new external_value(PARAM_INT, 'Course id'),
145
                    'type' => new external_value(PARAM_TEXT, 'The forum type'),
146
                    'name' => new external_value(PARAM_RAW, 'Forum name'),
147
                    'intro' => new external_value(PARAM_RAW, 'The forum intro'),
148
                    'introformat' => new external_format_value('intro'),
149
                    'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL),
150
                    'lang' => new external_value(PARAM_SAFEDIR, 'Forced activity language', VALUE_OPTIONAL),
151
                    'duedate' => new external_value(PARAM_INT, 'duedate for the user', VALUE_OPTIONAL),
152
                    'cutoffdate' => new external_value(PARAM_INT, 'cutoffdate for the user', VALUE_OPTIONAL),
153
                    'assessed' => new external_value(PARAM_INT, 'Aggregate type'),
154
                    'assesstimestart' => new external_value(PARAM_INT, 'Assess start time'),
155
                    'assesstimefinish' => new external_value(PARAM_INT, 'Assess finish time'),
156
                    'scale' => new external_value(PARAM_INT, 'Scale'),
157
                    'grade_forum' => new external_value(PARAM_INT, 'Whole forum grade'),
158
                    'grade_forum_notify' => new external_value(PARAM_INT, 'Whether to send notifications to students upon grading by default'),
159
                    'maxbytes' => new external_value(PARAM_INT, 'Maximum attachment size'),
160
                    'maxattachments' => new external_value(PARAM_INT, 'Maximum number of attachments'),
161
                    'forcesubscribe' => new external_value(PARAM_INT, 'Force users to subscribe'),
162
                    'trackingtype' => new external_value(PARAM_INT, 'Subscription mode'),
163
                    'rsstype' => new external_value(PARAM_INT, 'RSS feed for this activity'),
164
                    'rssarticles' => new external_value(PARAM_INT, 'Number of RSS recent articles'),
165
                    'timemodified' => new external_value(PARAM_INT, 'Time modified'),
166
                    'warnafter' => new external_value(PARAM_INT, 'Post threshold for warning'),
167
                    'blockafter' => new external_value(PARAM_INT, 'Post threshold for blocking'),
168
                    'blockperiod' => new external_value(PARAM_INT, 'Time period for blocking'),
169
                    'completiondiscussions' => new external_value(PARAM_INT, 'Student must create discussions'),
170
                    'completionreplies' => new external_value(PARAM_INT, 'Student must post replies'),
171
                    'completionposts' => new external_value(PARAM_INT, 'Student must post discussions or replies'),
172
                    'cmid' => new external_value(PARAM_INT, 'Course module id'),
173
                    'numdiscussions' => new external_value(PARAM_INT, 'Number of discussions in the forum', VALUE_OPTIONAL),
174
                    'cancreatediscussions' => new external_value(PARAM_BOOL, 'If the user can create discussions', VALUE_OPTIONAL),
175
                    'lockdiscussionafter' => new external_value(PARAM_INT, 'After what period a discussion is locked', VALUE_OPTIONAL),
176
                    'istracked' => new external_value(PARAM_BOOL, 'If the user is tracking the forum', VALUE_OPTIONAL),
177
                    'unreadpostscount' => new external_value(PARAM_INT, 'The number of unread posts for tracked forums',
178
                        VALUE_OPTIONAL),
179
                ), 'forum'
180
            )
181
        );
182
    }
183
 
184
    /**
185
     * Get the forum posts in the specified discussion.
186
     *
187
     * @param   int $discussionid
188
     * @param   string $sortby
189
     * @param   string $sortdirection
190
     * @param   bool $includeinlineattachments Whether inline attachments should be included or not.
191
     * @return  array
192
     */
193
    public static function get_discussion_posts(int $discussionid, ?string $sortby, ?string $sortdirection, bool $includeinlineattachments = false) {
194
        global $USER;
195
        // Validate the parameter.
196
        $params = self::validate_parameters(self::get_discussion_posts_parameters(), [
197
                'discussionid' => $discussionid,
198
                'sortby' => $sortby,
199
                'sortdirection' => $sortdirection,
200
            ]);
201
        $warnings = [];
202
 
203
        $vaultfactory = mod_forum\local\container::get_vault_factory();
204
 
205
        $discussionvault = $vaultfactory->get_discussion_vault();
206
        $discussion = $discussionvault->get_from_id($params['discussionid']);
207
 
208
        $forumvault = $vaultfactory->get_forum_vault();
209
        $forum = $forumvault->get_from_id($discussion->get_forum_id());
210
        $context = $forum->get_context();
211
        self::validate_context($context);
212
 
213
        $sortby = $params['sortby'];
214
        $sortdirection = $params['sortdirection'];
215
        $sortallowedvalues = ['id', 'created', 'modified'];
216
        $directionallowedvalues = ['ASC', 'DESC'];
217
 
218
        if (!in_array(strtolower($sortby), $sortallowedvalues)) {
219
            throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
220
                'allowed values are: ' . implode(', ', $sortallowedvalues));
221
        }
222
 
223
        $sortdirection = strtoupper($sortdirection);
224
        if (!in_array($sortdirection, $directionallowedvalues)) {
225
            throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
226
                'allowed values are: ' . implode(',', $directionallowedvalues));
227
        }
228
 
229
        $managerfactory = mod_forum\local\container::get_manager_factory();
230
        $capabilitymanager = $managerfactory->get_capability_manager($forum);
231
 
232
        $postvault = $vaultfactory->get_post_vault();
233
        $posts = $postvault->get_from_discussion_id(
234
                $USER,
235
                $discussion->get_id(),
236
                $capabilitymanager->can_view_any_private_reply($USER),
237
                "{$sortby} {$sortdirection}"
238
            );
239
 
240
        $builderfactory = mod_forum\local\container::get_builder_factory();
241
        $postbuilder = $builderfactory->get_exported_posts_builder();
242
 
243
        $legacydatamapper = mod_forum\local\container::get_legacy_data_mapper_factory();
244
 
245
        return [
246
            'posts' => $postbuilder->build($USER, [$forum], [$discussion], $posts, $includeinlineattachments),
247
            'forumid' => $discussion->get_forum_id(),
248
            'courseid' => $discussion->get_course_id(),
249
            'ratinginfo' => \core_rating\external\util::get_rating_info(
250
                $legacydatamapper->get_forum_data_mapper()->to_legacy_object($forum),
251
                $forum->get_context(),
252
                'mod_forum',
253
                'post',
254
                $legacydatamapper->get_post_data_mapper()->to_legacy_objects($posts)
255
            ),
256
            'warnings' => $warnings,
257
        ];
258
    }
259
 
260
    /**
261
     * Describe the post parameters.
262
     *
263
     * @return external_function_parameters
264
     */
265
    public static function get_discussion_posts_parameters() {
266
        return new external_function_parameters ([
267
            'discussionid' => new external_value(PARAM_INT, 'The ID of the discussion from which to fetch posts.', VALUE_REQUIRED),
268
            'sortby' => new external_value(PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
269
            'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
270
            'includeinlineattachments' => new external_value(PARAM_BOOL, 'Whether inline attachments should be included or not', VALUE_DEFAULT,
271
                false),
272
        ]);
273
    }
274
 
275
    /**
276
     * Describe the post return format.
277
     *
278
     * @return external_single_structure
279
     */
280
    public static function get_discussion_posts_returns() {
281
        return new external_single_structure([
282
            'posts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
283
            'forumid' => new external_value(PARAM_INT, 'The forum id'),
284
            'courseid' => new external_value(PARAM_INT, 'The forum course id'),
285
            'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
286
            'warnings' => new external_warnings()
287
        ]);
288
    }
289
 
290
    /**
291
     * Describes the parameters for get_forum_discussions.
292
     *
293
     * @return external_function_parameters
294
     * @since Moodle 3.7
295
     */
296
    public static function get_forum_discussions_parameters() {
297
        return new external_function_parameters (
298
            array(
299
                'forumid' => new external_value(PARAM_INT, 'forum instance id', VALUE_REQUIRED),
300
                'sortorder' => new external_value(PARAM_INT,
301
                    'sort by this element: numreplies, , created or timemodified', VALUE_DEFAULT, -1),
302
                'page' => new external_value(PARAM_INT, 'current page', VALUE_DEFAULT, -1),
303
                'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
304
                'groupid' => new external_value(PARAM_INT, 'group id', VALUE_DEFAULT, 0),
305
            )
306
        );
307
    }
308
 
309
    /**
310
     * Returns a list of forum discussions optionally sorted and paginated.
311
     *
312
     * @param int $forumid the forum instance id
313
     * @param int $sortorder The sort order
314
     * @param int $page page number
315
     * @param int $perpage items per page
316
     * @param int $groupid the user course group
317
     *
318
     *
319
     * @return array the forum discussion details including warnings
320
     * @since Moodle 3.7
321
     */
322
    public static function get_forum_discussions(int $forumid, ?int $sortorder = -1, ?int $page = -1,
323
            ?int $perpage = 0, ?int $groupid = 0) {
324
 
325
        global $CFG, $DB, $USER;
326
 
327
        require_once($CFG->dirroot . "/mod/forum/lib.php");
328
 
329
        $warnings = array();
330
        $discussions = array();
331
 
332
        $params = self::validate_parameters(self::get_forum_discussions_parameters(),
333
            array(
334
                'forumid' => $forumid,
335
                'sortorder' => $sortorder,
336
                'page' => $page,
337
                'perpage' => $perpage,
338
                'groupid' => $groupid
339
            )
340
        );
341
 
342
        // Compact/extract functions are not recommended.
343
        $forumid        = $params['forumid'];
344
        $sortorder      = $params['sortorder'];
345
        $page           = $params['page'];
346
        $perpage        = $params['perpage'];
347
        $groupid        = $params['groupid'];
348
 
349
        $vaultfactory = \mod_forum\local\container::get_vault_factory();
350
        $discussionlistvault = $vaultfactory->get_discussions_in_forum_vault();
351
 
352
        $sortallowedvalues = array(
353
            $discussionlistvault::SORTORDER_LASTPOST_DESC,
354
            $discussionlistvault::SORTORDER_LASTPOST_ASC,
355
            $discussionlistvault::SORTORDER_CREATED_DESC,
356
            $discussionlistvault::SORTORDER_CREATED_ASC,
357
            $discussionlistvault::SORTORDER_REPLIES_DESC,
358
            $discussionlistvault::SORTORDER_REPLIES_ASC
359
        );
360
 
361
        // If sortorder not defined set a default one.
362
        if ($sortorder == -1) {
363
            $sortorder = $discussionlistvault::SORTORDER_LASTPOST_DESC;
364
        }
365
 
366
        if (!in_array($sortorder, $sortallowedvalues)) {
367
            throw new invalid_parameter_exception('Invalid value for sortorder parameter (value: ' . $sortorder . '),' .
368
                ' allowed values are: ' . implode(',', $sortallowedvalues));
369
        }
370
 
371
        $managerfactory = \mod_forum\local\container::get_manager_factory();
372
        $urlfactory = \mod_forum\local\container::get_url_factory();
373
        $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
374
 
375
        $forumvault = $vaultfactory->get_forum_vault();
376
        $forum = $forumvault->get_from_id($forumid);
377
        if (!$forum) {
378
            throw new \moodle_exception("Unable to find forum with id {$forumid}");
379
        }
380
        $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
381
        $forumrecord = $forumdatamapper->to_legacy_object($forum);
382
 
383
        $capabilitymanager = $managerfactory->get_capability_manager($forum);
384
 
385
        $course = $DB->get_record('course', array('id' => $forum->get_course_id()), '*', MUST_EXIST);
386
        $cm = get_coursemodule_from_instance('forum', $forum->get_id(), $course->id, false, MUST_EXIST);
387
 
388
        // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
389
        $modcontext = context_module::instance($cm->id);
390
        self::validate_context($modcontext);
391
 
392
        $canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($USER);
393
 
394
        // Check they have the view forum capability.
395
        if (!$capabilitymanager->can_view_discussions($USER)) {
396
            throw new moodle_exception('noviewdiscussionspermission', 'forum');
397
        }
398
 
399
        $alldiscussions = mod_forum_get_discussion_summaries($forum, $USER, $groupid, $sortorder, $page, $perpage);
400
 
401
        if ($alldiscussions) {
402
            $discussionids = array_keys($alldiscussions);
403
 
404
            $postvault = $vaultfactory->get_post_vault();
405
            $postdatamapper = $legacydatamapperfactory->get_post_data_mapper();
406
            // Return the reply count for each discussion in a given forum.
407
            $replies = $postvault->get_reply_count_for_discussion_ids($USER, $discussionids, $canseeanyprivatereply);
408
            // Return the first post for each discussion in a given forum.
409
            $firstposts = $postvault->get_first_post_for_discussion_ids($discussionids);
410
 
411
            // Get the unreads array, this takes a forum id and returns data for all discussions.
412
            $unreads = array();
413
            if ($cantrack = forum_tp_can_track_forums($forumrecord)) {
414
                if ($forumtracked = forum_tp_is_tracked($forumrecord)) {
415
                    $unreads = $postvault->get_unread_count_for_discussion_ids($USER, $discussionids, $canseeanyprivatereply);
416
                }
417
            }
418
 
419
            $canlock = $capabilitymanager->can_manage_forum($USER);
420
 
421
            $usercontext = context_user::instance($USER->id);
422
            $ufservice = core_favourites\service_factory::get_service_for_user_context($usercontext);
423
 
424
            $canfavourite = has_capability('mod/forum:cantogglefavourite', $modcontext, $USER);
425
 
426
            foreach ($alldiscussions as $discussionsummary) {
427
                $discussion = $discussionsummary->get_discussion();
428
                $firstpostauthor = $discussionsummary->get_first_post_author();
429
                $latestpostauthor = $discussionsummary->get_latest_post_author();
430
 
431
                // This function checks for qanda forums.
432
                $canviewdiscussion = $capabilitymanager->can_view_discussion($USER, $discussion);
433
                if (!$canviewdiscussion) {
434
                    $warning = array();
435
                    // Function forum_get_discussions returns forum_posts ids not forum_discussions ones.
436
                    $warning['item'] = 'post';
437
                    $warning['itemid'] = $discussion->get_id();
438
                    $warning['warningcode'] = '1';
439
                    $warning['message'] = 'You can\'t see this discussion';
440
                    $warnings[] = $warning;
441
                    continue;
442
                }
443
 
444
                $firstpost = $firstposts[$discussion->get_first_post_id()];
445
                $discussionobject = $postdatamapper->to_legacy_object($firstpost);
446
                // Fix up the types for these properties.
447
                $discussionobject->mailed = $discussionobject->mailed ? 1 : 0;
448
                $discussionobject->messagetrust = $discussionobject->messagetrust ? 1 : 0;
449
                $discussionobject->mailnow = $discussionobject->mailnow ? 1 : 0;
450
                $discussionobject->groupid = $discussion->get_group_id();
451
                $discussionobject->timemodified = $discussion->get_time_modified();
452
                $discussionobject->usermodified = $discussion->get_user_modified();
453
                $discussionobject->timestart = $discussion->get_time_start();
454
                $discussionobject->timeend = $discussion->get_time_end();
455
                $discussionobject->pinned = $discussion->is_pinned();
456
 
457
                $discussionobject->numunread = 0;
458
                if ($cantrack && $forumtracked) {
459
                    if (isset($unreads[$discussion->get_id()])) {
460
                        $discussionobject->numunread = (int) $unreads[$discussion->get_id()];
461
                    }
462
                }
463
 
464
                $discussionobject->numreplies = 0;
465
                if (!empty($replies[$discussion->get_id()])) {
466
                    $discussionobject->numreplies = (int) $replies[$discussion->get_id()];
467
                }
468
 
469
                $discussionobject->name = \core_external\util::format_string($discussion->get_name(), $modcontext);
470
                $discussionobject->subject = \core_external\util::format_string($discussionobject->subject, $modcontext);
471
                // Rewrite embedded images URLs.
472
                $options = array('trusted' => $discussionobject->messagetrust);
473
                list($discussionobject->message, $discussionobject->messageformat) =
474
                    \core_external\util::format_text($discussionobject->message, $discussionobject->messageformat,
475
                        $modcontext, 'mod_forum', 'post', $discussionobject->id, $options);
476
 
477
                // List attachments.
478
                if (!empty($discussionobject->attachment)) {
479
                    $discussionobject->attachments = external_util::get_area_files($modcontext->id, 'mod_forum',
480
                        'attachment', $discussionobject->id);
481
                }
482
                $messageinlinefiles = external_util::get_area_files($modcontext->id, 'mod_forum', 'post',
483
                    $discussionobject->id);
484
                if (!empty($messageinlinefiles)) {
485
                    $discussionobject->messageinlinefiles = $messageinlinefiles;
486
                }
487
 
488
                $discussionobject->locked = $forum->is_discussion_locked($discussion);
489
                $discussionobject->canlock = $canlock;
490
                $discussionobject->starred = !empty($ufservice) ? $ufservice->favourite_exists('mod_forum', 'discussions',
491
                    $discussion->get_id(), $modcontext) : false;
492
                $discussionobject->canreply = $capabilitymanager->can_post_in_discussion($USER, $discussion);
493
                $discussionobject->canfavourite = $canfavourite;
494
 
495
                if (forum_is_author_hidden($discussionobject, $forumrecord)) {
496
                    $discussionobject->userid = null;
497
                    $discussionobject->userfullname = null;
498
                    $discussionobject->userpictureurl = null;
499
 
500
                    $discussionobject->usermodified = null;
501
                    $discussionobject->usermodifiedfullname = null;
502
                    $discussionobject->usermodifiedpictureurl = null;
503
 
504
                } else {
505
                    $discussionobject->userfullname = $firstpostauthor->get_full_name();
506
                    $discussionobject->userpictureurl = $urlfactory->get_author_profile_image_url($firstpostauthor, null, 2)
507
                        ->out(false);
508
 
509
                    $discussionobject->usermodifiedfullname = $latestpostauthor->get_full_name();
510
                    $discussionobject->usermodifiedpictureurl = $urlfactory->get_author_profile_image_url(
511
                        $latestpostauthor, null, 2)->out(false);
512
                }
513
 
514
                $discussions[] = (array) $discussionobject;
515
            }
516
        }
517
        $result = array();
518
        $result['discussions'] = $discussions;
519
        $result['warnings'] = $warnings;
520
 
521
        return $result;
522
    }
523
 
524
    /**
525
     * Describes the get_forum_discussions return value.
526
     *
527
     * @return external_single_structure
528
     * @since Moodle 3.7
529
     */
530
    public static function get_forum_discussions_returns() {
531
        return new external_single_structure(
532
            array(
533
                'discussions' => new external_multiple_structure(
534
                    new external_single_structure(
535
                        array(
536
                            'id' => new external_value(PARAM_INT, 'Post id'),
537
                            'name' => new external_value(PARAM_RAW, 'Discussion name'),
538
                            'groupid' => new external_value(PARAM_INT, 'Group id'),
539
                            'timemodified' => new external_value(PARAM_INT, 'Time modified'),
540
                            'usermodified' => new external_value(PARAM_INT, 'The id of the user who last modified'),
541
                            'timestart' => new external_value(PARAM_INT, 'Time discussion can start'),
542
                            'timeend' => new external_value(PARAM_INT, 'Time discussion ends'),
543
                            'discussion' => new external_value(PARAM_INT, 'Discussion id'),
544
                            'parent' => new external_value(PARAM_INT, 'Parent id'),
545
                            'userid' => new external_value(PARAM_INT, 'User who started the discussion id'),
546
                            'created' => new external_value(PARAM_INT, 'Creation time'),
547
                            'modified' => new external_value(PARAM_INT, 'Time modified'),
548
                            'mailed' => new external_value(PARAM_INT, 'Mailed?'),
549
                            'subject' => new external_value(PARAM_RAW, 'The post subject'),
550
                            'message' => new external_value(PARAM_RAW, 'The post message'),
551
                            'messageformat' => new external_format_value('message'),
552
                            'messagetrust' => new external_value(PARAM_INT, 'Can we trust?'),
553
                            'messageinlinefiles' => new external_files('post message inline files', VALUE_OPTIONAL),
554
                            'attachment' => new external_value(PARAM_RAW, 'Has attachments?'),
555
                            'attachments' => new external_files('attachments', VALUE_OPTIONAL),
556
                            'totalscore' => new external_value(PARAM_INT, 'The post message total score'),
557
                            'mailnow' => new external_value(PARAM_INT, 'Mail now?'),
558
                            'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
559
                            'usermodifiedfullname' => new external_value(PARAM_TEXT, 'Post modifier full name'),
560
                            'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.'),
561
                            'usermodifiedpictureurl' => new external_value(PARAM_URL, 'Post modifier picture.'),
562
                            'numreplies' => new external_value(PARAM_INT, 'The number of replies in the discussion'),
563
                            'numunread' => new external_value(PARAM_INT, 'The number of unread discussions.'),
564
                            'pinned' => new external_value(PARAM_BOOL, 'Is the discussion pinned'),
565
                            'locked' => new external_value(PARAM_BOOL, 'Is the discussion locked'),
566
                            'starred' => new external_value(PARAM_BOOL, 'Is the discussion starred'),
567
                            'canreply' => new external_value(PARAM_BOOL, 'Can the user reply to the discussion'),
568
                            'canlock' => new external_value(PARAM_BOOL, 'Can the user lock the discussion'),
569
                            'canfavourite' => new external_value(PARAM_BOOL, 'Can the user star the discussion'),
570
                        ), 'post'
571
                    )
572
                ),
573
                'warnings' => new external_warnings()
574
            )
575
        );
576
    }
577
 
578
    /**
579
     * Returns description of method parameters
580
     *
581
     * @return external_function_parameters
582
     * @since Moodle 2.9
583
     */
584
    public static function view_forum_parameters() {
585
        return new external_function_parameters(
586
            array(
587
                'forumid' => new external_value(PARAM_INT, 'forum instance id')
588
            )
589
        );
590
    }
591
 
592
    /**
593
     * Trigger the course module viewed event and update the module completion status.
594
     *
595
     * @param int $forumid the forum instance id
596
     * @return array of warnings and status result
597
     * @since Moodle 2.9
598
     * @throws moodle_exception
599
     */
600
    public static function view_forum($forumid) {
601
        global $DB, $CFG;
602
        require_once($CFG->dirroot . "/mod/forum/lib.php");
603
 
604
        $params = self::validate_parameters(self::view_forum_parameters(),
605
                                            array(
606
                                                'forumid' => $forumid
607
                                            ));
608
        $warnings = array();
609
 
610
        // Request and permission validation.
611
        $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
612
        list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
613
 
614
        $context = context_module::instance($cm->id);
615
        self::validate_context($context);
616
 
617
        require_capability('mod/forum:viewdiscussion', $context, null, true, 'noviewdiscussionspermission', 'forum');
618
 
619
        // Call the forum/lib API.
620
        forum_view($forum, $course, $cm, $context);
621
 
622
        $result = array();
623
        $result['status'] = true;
624
        $result['warnings'] = $warnings;
625
        return $result;
626
    }
627
 
628
    /**
629
     * Returns description of method result value
630
     *
631
     * @return \core_external\external_description
632
     * @since Moodle 2.9
633
     */
634
    public static function view_forum_returns() {
635
        return new external_single_structure(
636
            array(
637
                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
638
                'warnings' => new external_warnings()
639
            )
640
        );
641
    }
642
 
643
    /**
644
     * Returns description of method parameters
645
     *
646
     * @return external_function_parameters
647
     * @since Moodle 2.9
648
     */
649
    public static function view_forum_discussion_parameters() {
650
        return new external_function_parameters(
651
            array(
652
                'discussionid' => new external_value(PARAM_INT, 'discussion id')
653
            )
654
        );
655
    }
656
 
657
    /**
658
     * Trigger the discussion viewed event.
659
     *
660
     * @param int $discussionid the discussion id
661
     * @return array of warnings and status result
662
     * @since Moodle 2.9
663
     * @throws moodle_exception
664
     */
665
    public static function view_forum_discussion($discussionid) {
666
        global $DB, $CFG, $USER;
667
        require_once($CFG->dirroot . "/mod/forum/lib.php");
668
 
669
        $params = self::validate_parameters(self::view_forum_discussion_parameters(),
670
                                            array(
671
                                                'discussionid' => $discussionid
672
                                            ));
673
        $warnings = array();
674
 
675
        $discussion = $DB->get_record('forum_discussions', array('id' => $params['discussionid']), '*', MUST_EXIST);
676
        $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
677
        list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
678
 
679
        // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
680
        $modcontext = context_module::instance($cm->id);
681
        self::validate_context($modcontext);
682
 
683
        require_capability('mod/forum:viewdiscussion', $modcontext, null, true, 'noviewdiscussionspermission', 'forum');
684
 
685
        // Call the forum/lib API.
686
        forum_discussion_view($modcontext, $forum, $discussion);
687
 
688
        // Mark as read if required.
689
        if (!$CFG->forum_usermarksread && forum_tp_is_tracked($forum)) {
690
            forum_tp_mark_discussion_read($USER, $discussion->id);
691
        }
692
 
693
        $result = array();
694
        $result['status'] = true;
695
        $result['warnings'] = $warnings;
696
        return $result;
697
    }
698
 
699
    /**
700
     * Returns description of method result value
701
     *
702
     * @return \core_external\external_description
703
     * @since Moodle 2.9
704
     */
705
    public static function view_forum_discussion_returns() {
706
        return new external_single_structure(
707
            array(
708
                'status' => new external_value(PARAM_BOOL, 'status: true if success'),
709
                'warnings' => new external_warnings()
710
            )
711
        );
712
    }
713
 
714
    /**
715
     * Returns description of method parameters
716
     *
717
     * @return external_function_parameters
718
     * @since Moodle 3.0
719
     */
720
    public static function add_discussion_post_parameters() {
721
        return new external_function_parameters(
722
            array(
723
                'postid' => new external_value(PARAM_INT, 'the post id we are going to reply to
724
                                                (can be the initial discussion post'),
725
                'subject' => new external_value(PARAM_TEXT, 'new post subject'),
726
                'message' => new external_value(PARAM_RAW, 'new post message (html assumed if messageformat is not provided)'),
727
                'options' => new external_multiple_structure (
728
                    new external_single_structure(
729
                        array(
730
                            'name' => new external_value(PARAM_ALPHANUM,
731
                                        'The allowed keys (value format) are:
732
                                        discussionsubscribe (bool); subscribe to the discussion?, default to true
733
                                        private (bool); make this reply private to the author of the parent post, default to false.
734
                                        inlineattachmentsid              (int); the draft file area id for inline attachments
735
                                        attachmentsid       (int); the draft file area id for attachments
736
                                        topreferredformat (bool); convert the message & messageformat to FORMAT_HTML, defaults to false
737
                            '),
738
                            'value' => new external_value(PARAM_RAW, 'the value of the option,
739
                                                            this param is validated in the external function.'
740
                        )
741
                    )
742
                ), 'Options', VALUE_DEFAULT, array()),
743
                'messageformat' => new external_format_value('message', VALUE_DEFAULT)
744
            )
745
        );
746
    }
747
 
748
    /**
749
     * Create new posts into an existing discussion.
750
     *
751
     * @param int $postid the post id we are going to reply to
752
     * @param string $subject new post subject
753
     * @param string $message new post message (html assumed if messageformat is not provided)
754
     * @param array $options optional settings
755
     * @param string $messageformat The format of the message, defaults to FORMAT_HTML for BC
756
     * @return array of warnings and the new post id
757
     * @since Moodle 3.0
758
     * @throws moodle_exception
759
     */
760
    public static function add_discussion_post($postid, $subject, $message, $options = array(), $messageformat = FORMAT_HTML) {
761
        global $CFG, $USER;
762
        require_once($CFG->dirroot . "/mod/forum/lib.php");
763
 
764
        // Get all the factories that are required.
765
        $vaultfactory = mod_forum\local\container::get_vault_factory();
766
        $entityfactory = mod_forum\local\container::get_entity_factory();
767
        $datamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
768
        $managerfactory = mod_forum\local\container::get_manager_factory();
769
        $discussionvault = $vaultfactory->get_discussion_vault();
770
        $forumvault = $vaultfactory->get_forum_vault();
771
        $discussiondatamapper = $datamapperfactory->get_discussion_data_mapper();
772
        $forumdatamapper = $datamapperfactory->get_forum_data_mapper();
773
 
774
        $params = self::validate_parameters(self::add_discussion_post_parameters(),
775
            array(
776
                'postid' => $postid,
777
                'subject' => $subject,
778
                'message' => $message,
779
                'options' => $options,
780
                'messageformat' => $messageformat,
781
            )
782
        );
783
 
784
        $warnings = array();
785
 
786
        if (!$parent = forum_get_post_full($params['postid'])) {
787
            throw new moodle_exception('invalidparentpostid', 'forum');
788
        }
789
 
790
        if (!$discussion = $discussionvault->get_from_id($parent->discussion)) {
791
            throw new moodle_exception('notpartofdiscussion', 'forum');
792
        }
793
 
794
        // Request and permission validation.
795
        $forum = $forumvault->get_from_id($discussion->get_forum_id());
796
        $capabilitymanager = $managerfactory->get_capability_manager($forum);
797
        $course = $forum->get_course_record();
798
        $cm = $forum->get_course_module_record();
799
 
800
        $discussionrecord = $discussiondatamapper->to_legacy_object($discussion);
801
        $forumrecord = $forumdatamapper->to_legacy_object($forum);
802
        $context = context_module::instance($cm->id);
803
        self::validate_context($context);
804
 
805
        $coursecontext = \context_course::instance($forum->get_course_id());
806
        $discussionsubscribe = \mod_forum\subscriptions::get_user_default_subscription($forumrecord, $coursecontext,
807
            $cm, null);
808
 
809
        // Validate options.
810
        $options = array(
811
            'discussionsubscribe' => $discussionsubscribe,
812
            'private'             => false,
813
            'inlineattachmentsid' => 0,
814
            'attachmentsid' => null,
815
            'topreferredformat'   => false
816
        );
817
        foreach ($params['options'] as $option) {
818
            $name = trim($option['name']);
819
            switch ($name) {
820
                case 'discussionsubscribe':
821
                    $value = clean_param($option['value'], PARAM_BOOL);
822
                    break;
823
                case 'private':
824
                    $value = clean_param($option['value'], PARAM_BOOL);
825
                    break;
826
                case 'inlineattachmentsid':
827
                    $value = clean_param($option['value'], PARAM_INT);
828
                    break;
829
                case 'attachmentsid':
830
                    $value = clean_param($option['value'], PARAM_INT);
831
                    // Ensure that the user has permissions to create attachments.
832
                    if (!has_capability('mod/forum:createattachment', $context)) {
833
                        $value = 0;
834
                    }
835
                    break;
836
                case 'topreferredformat':
837
                    $value = clean_param($option['value'], PARAM_BOOL);
838
                    break;
839
                default:
840
                    throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
841
            }
842
            $options[$name] = $value;
843
        }
844
 
845
        if (!$capabilitymanager->can_post_in_discussion($USER, $discussion)) {
846
            throw new moodle_exception('nopostforum', 'forum');
847
        }
848
 
849
        $thresholdwarning = forum_check_throttling($forumrecord, $cm);
850
        forum_check_blocking_threshold($thresholdwarning);
851
 
852
        // If we want to force a conversion to the preferred format, let's do it now.
853
        if ($options['topreferredformat']) {
854
            // We always are going to honor the preferred format. We are creating a new post.
855
            $preferredformat = editors_get_preferred_format();
856
            // If the post is not HTML and the preferred format is HTML, convert to it.
857
            if ($params['messageformat'] != FORMAT_HTML and $preferredformat == FORMAT_HTML) {
858
                $params['message'] = format_text($params['message'], $params['messageformat'], ['filter' => false]);
859
            }
860
            $params['messageformat'] = $preferredformat;
861
        }
862
 
863
        // Create the post.
864
        $post = new stdClass();
865
        $post->discussion = $discussion->get_id();
866
        $post->parent = $parent->id;
867
        $post->subject = $params['subject'];
868
        $post->message = $params['message'];
869
        $post->messageformat = $params['messageformat'];
870
        $post->messagetrust = trusttext_trusted($context);
871
        $post->itemid = $options['inlineattachmentsid'];
872
        $post->attachments = $options['attachmentsid'];
873
        $post->isprivatereply = $options['private'];
874
        $post->deleted = 0;
875
        $fakemform = $post->attachments;
876
        if ($postid = forum_add_new_post($post, $fakemform)) {
877
 
878
            $post->id = $postid;
879
 
880
            // Trigger events and completion.
881
            $params = array(
882
                'context' => $context,
883
                'objectid' => $post->id,
884
                'other' => array(
885
                    'discussionid' => $discussion->get_id(),
886
                    'forumid' => $forum->get_id(),
887
                    'forumtype' => $forum->get_type(),
888
                )
889
            );
890
            $event = \mod_forum\event\post_created::create($params);
891
            $event->add_record_snapshot('forum_posts', $post);
892
            $event->add_record_snapshot('forum_discussions', $discussionrecord);
893
            $event->trigger();
894
 
895
            // Update completion state.
896
            $completion = new completion_info($course);
897
            if ($completion->is_enabled($cm) &&
898
                    ($forum->get_completion_replies() || $forum->get_completion_posts())) {
899
                $completion->update_state($cm, COMPLETION_COMPLETE);
900
            }
901
 
902
            if ($options['discussionsubscribe']) {
903
                $settings = new stdClass();
904
                $settings->discussionsubscribe = $options['discussionsubscribe'];
905
                forum_post_subscription($settings, $forumrecord, $discussionrecord);
906
            }
907
        } else {
908
            throw new moodle_exception('couldnotadd', 'forum');
909
        }
910
 
911
        $builderfactory = \mod_forum\local\container::get_builder_factory();
912
        $exportedpostsbuilder = $builderfactory->get_exported_posts_builder();
913
        $postentity = $entityfactory->get_post_from_stdClass($post);
914
        $exportedposts = $exportedpostsbuilder->build($USER, [$forum], [$discussion], [$postentity]);
915
        $exportedpost = $exportedposts[0];
916
 
917
        $message = [];
918
        $message[] = [
919
            'type' => 'success',
920
            'message' => get_string("postaddedsuccess", "forum")
921
        ];
922
 
923
        $message[] = [
924
            'type' => 'success',
925
            'message' => get_string("postaddedtimeleft", "forum", format_time($CFG->maxeditingtime))
926
        ];
927
 
928
        $result = array();
929
        $result['postid'] = $postid;
930
        $result['warnings'] = $warnings;
931
        $result['post'] = $exportedpost;
932
        $result['messages'] = $message;
933
        return $result;
934
    }
935
 
936
    /**
937
     * Returns description of method result value
938
     *
939
     * @return \core_external\external_description
940
     * @since Moodle 3.0
941
     */
942
    public static function add_discussion_post_returns() {
943
        return new external_single_structure(
944
            array(
945
                'postid' => new external_value(PARAM_INT, 'new post id'),
946
                'warnings' => new external_warnings(),
947
                'post' => post_exporter::get_read_structure(),
948
                'messages' => new external_multiple_structure(
949
                    new external_single_structure(
950
                        array(
951
                            'type' => new external_value(PARAM_TEXT, "The classification to be used in the client side", VALUE_REQUIRED),
952
                            'message' => new external_value(PARAM_TEXT,'untranslated english message to explain the warning', VALUE_REQUIRED)
953
                        ), 'Messages'), 'list of warnings', VALUE_OPTIONAL
954
                ),
955
                //'alertmessage' => new external_value(PARAM_RAW, 'Success message to be displayed to the user.'),
956
            )
957
        );
958
    }
959
 
960
    /**
961
     * Toggle the favouriting value for the discussion provided
962
     *
963
     * @param int $discussionid The discussion we need to favourite
964
     * @param bool $targetstate The state of the favourite value
965
     * @return array The exported discussion
966
     */
967
    public static function toggle_favourite_state($discussionid, $targetstate) {
968
        global $DB, $PAGE, $USER;
969
 
970
        $params = self::validate_parameters(self::toggle_favourite_state_parameters(), [
971
            'discussionid' => $discussionid,
972
            'targetstate' => $targetstate
973
        ]);
974
 
975
        $vaultfactory = mod_forum\local\container::get_vault_factory();
976
        // Get the discussion vault and the corresponding discussion entity.
977
        $discussionvault = $vaultfactory->get_discussion_vault();
978
        $discussion = $discussionvault->get_from_id($params['discussionid']);
979
 
980
        $forumvault = $vaultfactory->get_forum_vault();
981
        $forum = $forumvault->get_from_id($discussion->get_forum_id());
982
        $forumcontext = $forum->get_context();
983
        self::validate_context($forumcontext);
984
 
985
        $managerfactory = mod_forum\local\container::get_manager_factory();
986
        $capabilitymanager = $managerfactory->get_capability_manager($forum);
987
 
988
        // Does the user have the ability to favourite the discussion?
989
        if (!$capabilitymanager->can_favourite_discussion($USER)) {
990
            throw new moodle_exception('cannotfavourite', 'forum');
991
        }
992
        $usercontext = context_user::instance($USER->id);
993
        $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
994
        $isfavourited = $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
995
 
996
        $favouritefunction = $targetstate ? 'create_favourite' : 'delete_favourite';
997
        if ($isfavourited != (bool) $params['targetstate']) {
998
            $ufservice->{$favouritefunction}('mod_forum', 'discussions', $discussion->get_id(), $forumcontext);
999
        }
1000
 
1001
        $exporterfactory = mod_forum\local\container::get_exporter_factory();
1002
        $builder = mod_forum\local\container::get_builder_factory()->get_exported_discussion_builder();
1003
        $favourited = ($builder->is_favourited($discussion, $forumcontext, $USER) ? [$discussion->get_id()] : []);
1004
        $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion, [], $favourited);
1005
        return $exporter->export($PAGE->get_renderer('mod_forum'));
1006
    }
1007
 
1008
    /**
1009
     * Returns description of method result value
1010
     *
1011
     * @return \core_external\external_description
1012
     * @since Moodle 3.0
1013
     */
1014
    public static function toggle_favourite_state_returns() {
1015
        return discussion_exporter::get_read_structure();
1016
    }
1017
 
1018
    /**
1019
     * Defines the parameters for the toggle_favourite_state method
1020
     *
1021
     * @return external_function_parameters
1022
     */
1023
    public static function toggle_favourite_state_parameters() {
1024
        return new external_function_parameters(
1025
            [
1026
                'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1027
                'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1028
            ]
1029
        );
1030
    }
1031
 
1032
    /**
1033
     * Returns description of method parameters
1034
     *
1035
     * @return external_function_parameters
1036
     * @since Moodle 3.0
1037
     */
1038
    public static function add_discussion_parameters() {
1039
        return new external_function_parameters(
1040
            array(
1041
                'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1042
                'subject' => new external_value(PARAM_TEXT, 'New Discussion subject'),
1043
                'message' => new external_value(PARAM_RAW, 'New Discussion message (only html format allowed)'),
1044
                'groupid' => new external_value(PARAM_INT, 'The group, default to 0', VALUE_DEFAULT, 0),
1045
                'options' => new external_multiple_structure (
1046
                    new external_single_structure(
1047
                        array(
1048
                            'name' => new external_value(PARAM_ALPHANUM,
1049
                                        'The allowed keys (value format) are:
1050
                                        discussionsubscribe (bool); subscribe to the discussion?, default to true
1051
                                        discussionpinned    (bool); is the discussion pinned, default to false
1052
                                        inlineattachmentsid              (int); the draft file area id for inline attachments
1053
                                        attachmentsid       (int); the draft file area id for attachments
1054
                            '),
1055
                            'value' => new external_value(PARAM_RAW, 'The value of the option,
1056
                                                            This param is validated in the external function.'
1057
                        )
1058
                    )
1059
                ), 'Options', VALUE_DEFAULT, array())
1060
            )
1061
        );
1062
    }
1063
 
1064
    /**
1065
     * Add a new discussion into an existing forum.
1066
     *
1067
     * @param int $forumid the forum instance id
1068
     * @param string $subject new discussion subject
1069
     * @param string $message new discussion message (only html format allowed)
1070
     * @param int $groupid the user course group
1071
     * @param array $options optional settings
1072
     * @return array of warnings and the new discussion id
1073
     * @since Moodle 3.0
1074
     * @throws moodle_exception
1075
     */
1076
    public static function add_discussion($forumid, $subject, $message, $groupid = 0, $options = array()) {
1077
        global $DB, $CFG;
1078
        require_once($CFG->dirroot . "/mod/forum/lib.php");
1079
 
1080
        $params = self::validate_parameters(self::add_discussion_parameters(),
1081
                                            array(
1082
                                                'forumid' => $forumid,
1083
                                                'subject' => $subject,
1084
                                                'message' => $message,
1085
                                                'groupid' => $groupid,
1086
                                                'options' => $options
1087
                                            ));
1088
 
1089
        $warnings = array();
1090
 
1091
        // Request and permission validation.
1092
        $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1093
        list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1094
 
1095
        $context = context_module::instance($cm->id);
1096
        self::validate_context($context);
1097
 
1098
        // Validate options.
1099
        $options = array(
1100
            'discussionsubscribe' => true,
1101
            'discussionpinned' => false,
1102
            'inlineattachmentsid' => 0,
1103
            'attachmentsid' => null
1104
        );
1105
        foreach ($params['options'] as $option) {
1106
            $name = trim($option['name']);
1107
            switch ($name) {
1108
                case 'discussionsubscribe':
1109
                    $value = clean_param($option['value'], PARAM_BOOL);
1110
                    break;
1111
                case 'discussionpinned':
1112
                    $value = clean_param($option['value'], PARAM_BOOL);
1113
                    break;
1114
                case 'inlineattachmentsid':
1115
                    $value = clean_param($option['value'], PARAM_INT);
1116
                    break;
1117
                case 'attachmentsid':
1118
                    $value = clean_param($option['value'], PARAM_INT);
1119
                    // Ensure that the user has permissions to create attachments.
1120
                    if (!has_capability('mod/forum:createattachment', $context)) {
1121
                        $value = 0;
1122
                    }
1123
                    break;
1124
                default:
1125
                    throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
1126
            }
1127
            $options[$name] = $value;
1128
        }
1129
 
1130
        // Normalize group.
1131
        if (!groups_get_activity_groupmode($cm)) {
1132
            // Groups not supported, force to -1.
1133
            $groupid = -1;
1134
        } else {
1135
            // Check if we receive the default or and empty value for groupid,
1136
            // in this case, get the group for the user in the activity.
1137
            if (empty($params['groupid'])) {
1138
                $groupid = groups_get_activity_group($cm);
1139
            } else {
1140
                // Here we rely in the group passed, forum_user_can_post_discussion will validate the group.
1141
                $groupid = $params['groupid'];
1142
            }
1143
        }
1144
 
1145
        if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
1146
            throw new moodle_exception('cannotcreatediscussion', 'forum');
1147
        }
1148
 
1149
        $thresholdwarning = forum_check_throttling($forum, $cm);
1150
        forum_check_blocking_threshold($thresholdwarning);
1151
 
1152
        // Create the discussion.
1153
        $discussion = new stdClass();
1154
        $discussion->course = $course->id;
1155
        $discussion->forum = $forum->id;
1156
        $discussion->message = $params['message'];
1157
        $discussion->messageformat = FORMAT_HTML;   // Force formatting for now.
1158
        $discussion->messagetrust = trusttext_trusted($context);
1159
        $discussion->itemid = $options['inlineattachmentsid'];
1160
        $discussion->groupid = $groupid;
1161
        $discussion->mailnow = 0;
1162
        $discussion->subject = $params['subject'];
1163
        $discussion->name = $discussion->subject;
1164
        $discussion->timestart = 0;
1165
        $discussion->timeend = 0;
1166
        $discussion->timelocked = 0;
1167
        $discussion->attachments = $options['attachmentsid'];
1168
 
1169
        if (has_capability('mod/forum:pindiscussions', $context) && $options['discussionpinned']) {
1170
            $discussion->pinned = FORUM_DISCUSSION_PINNED;
1171
        } else {
1172
            $discussion->pinned = FORUM_DISCUSSION_UNPINNED;
1173
        }
1174
        $fakemform = $options['attachmentsid'];
1175
        if ($discussionid = forum_add_discussion($discussion, $fakemform)) {
1176
 
1177
            $discussion->id = $discussionid;
1178
 
1179
            // Trigger events and completion.
1180
 
1181
            $params = array(
1182
                'context' => $context,
1183
                'objectid' => $discussion->id,
1184
                'other' => array(
1185
                    'forumid' => $forum->id,
1186
                )
1187
            );
1188
            $event = \mod_forum\event\discussion_created::create($params);
1189
            $event->add_record_snapshot('forum_discussions', $discussion);
1190
            $event->trigger();
1191
 
1192
            $completion = new completion_info($course);
1193
            if ($completion->is_enabled($cm) &&
1194
                    ($forum->completiondiscussions || $forum->completionposts)) {
1195
                $completion->update_state($cm, COMPLETION_COMPLETE);
1196
            }
1197
 
1198
            $settings = new stdClass();
1199
            $settings->discussionsubscribe = $options['discussionsubscribe'];
1200
            forum_post_subscription($settings, $forum, $discussion);
1201
        } else {
1202
            throw new moodle_exception('couldnotadd', 'forum');
1203
        }
1204
 
1205
        $result = array();
1206
        $result['discussionid'] = $discussionid;
1207
        $result['warnings'] = $warnings;
1208
        return $result;
1209
    }
1210
 
1211
    /**
1212
     * Returns description of method result value
1213
     *
1214
     * @return \core_external\external_description
1215
     * @since Moodle 3.0
1216
     */
1217
    public static function add_discussion_returns() {
1218
        return new external_single_structure(
1219
            array(
1220
                'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
1221
                'warnings' => new external_warnings()
1222
            )
1223
        );
1224
    }
1225
 
1226
    /**
1227
     * Returns description of method parameters
1228
     *
1229
     * @return external_function_parameters
1230
     * @since Moodle 3.1
1231
     */
1232
    public static function can_add_discussion_parameters() {
1233
        return new external_function_parameters(
1234
            array(
1235
                'forumid' => new external_value(PARAM_INT, 'Forum instance ID'),
1236
                'groupid' => new external_value(PARAM_INT, 'The group to check, default to active group.
1237
                                                Use -1 to check if the user can post in all the groups.', VALUE_DEFAULT, null)
1238
            )
1239
        );
1240
    }
1241
 
1242
    /**
1243
     * Check if the current user can add discussions in the given forum (and optionally for the given group).
1244
     *
1245
     * @param int $forumid the forum instance id
1246
     * @param int $groupid the group to check, default to active group. Use -1 to check if the user can post in all the groups.
1247
     * @return array of warnings and the status (true if the user can add discussions)
1248
     * @since Moodle 3.1
1249
     * @throws moodle_exception
1250
     */
1251
    public static function can_add_discussion($forumid, $groupid = null) {
1252
        global $DB, $CFG;
1253
        require_once($CFG->dirroot . "/mod/forum/lib.php");
1254
 
1255
        $params = self::validate_parameters(self::can_add_discussion_parameters(),
1256
                                            array(
1257
                                                'forumid' => $forumid,
1258
                                                'groupid' => $groupid,
1259
                                            ));
1260
        $warnings = array();
1261
 
1262
        // Request and permission validation.
1263
        $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1264
        list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
1265
 
1266
        $context = context_module::instance($cm->id);
1267
        self::validate_context($context);
1268
 
1269
        $status = forum_user_can_post_discussion($forum, $params['groupid'], -1, $cm, $context);
1270
 
1271
        $result = array();
1272
        $result['status'] = $status;
1273
        $result['canpindiscussions'] = has_capability('mod/forum:pindiscussions', $context);
1274
        $result['cancreateattachment'] = forum_can_create_attachment($forum, $context);
1275
        $result['warnings'] = $warnings;
1276
        return $result;
1277
    }
1278
 
1279
    /**
1280
     * Returns description of method result value
1281
     *
1282
     * @return \core_external\external_description
1283
     * @since Moodle 3.1
1284
     */
1285
    public static function can_add_discussion_returns() {
1286
        return new external_single_structure(
1287
            array(
1288
                'status' => new external_value(PARAM_BOOL, 'True if the user can add discussions, false otherwise.'),
1289
                'canpindiscussions' => new external_value(PARAM_BOOL, 'True if the user can pin discussions, false otherwise.',
1290
                    VALUE_OPTIONAL),
1291
                'cancreateattachment' => new external_value(PARAM_BOOL, 'True if the user can add attachments, false otherwise.',
1292
                    VALUE_OPTIONAL),
1293
                'warnings' => new external_warnings()
1294
            )
1295
        );
1296
    }
1297
 
1298
    /**
1299
     * Describes the parameters for get_forum_access_information.
1300
     *
1301
     * @return external_external_function_parameters
1302
     * @since Moodle 3.7
1303
     */
1304
    public static function get_forum_access_information_parameters() {
1305
        return new external_function_parameters (
1306
            array(
1307
                'forumid' => new external_value(PARAM_INT, 'Forum instance id.')
1308
            )
1309
        );
1310
    }
1311
 
1312
    /**
1313
     * Return access information for a given forum.
1314
     *
1315
     * @param int $forumid forum instance id
1316
     * @return array of warnings and the access information
1317
     * @since Moodle 3.7
1318
     * @throws  moodle_exception
1319
     */
1320
    public static function get_forum_access_information($forumid) {
1321
        global $DB;
1322
 
1323
        $params = self::validate_parameters(self::get_forum_access_information_parameters(), array('forumid' => $forumid));
1324
 
1325
        // Request and permission validation.
1326
        $forum = $DB->get_record('forum', array('id' => $params['forumid']), '*', MUST_EXIST);
1327
        $cm = get_coursemodule_from_instance('forum', $forum->id);
1328
 
1329
        $context = context_module::instance($cm->id);
1330
        self::validate_context($context);
1331
 
1332
        $result = array();
1333
        // Return all the available capabilities.
1334
        $capabilities = load_capability_def('mod_forum');
1335
        foreach ($capabilities as $capname => $capdata) {
1336
            // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1337
            $field = 'can' . str_replace('mod/forum:', '', $capname);
1338
            $result[$field] = has_capability($capname, $context);
1339
        }
1340
 
1341
        $result['warnings'] = array();
1342
        return $result;
1343
    }
1344
 
1345
    /**
1346
     * Describes the get_forum_access_information return value.
1347
     *
1348
     * @return external_single_structure
1349
     * @since Moodle 3.7
1350
     */
1351
    public static function get_forum_access_information_returns() {
1352
 
1353
        $structure = array(
1354
            'warnings' => new external_warnings()
1355
        );
1356
 
1357
        $capabilities = load_capability_def('mod_forum');
1358
        foreach ($capabilities as $capname => $capdata) {
1359
            // Get fields like cansubmit so it is consistent with the access_information function implemented in other modules.
1360
            $field = 'can' . str_replace('mod/forum:', '', $capname);
1361
            $structure[$field] = new external_value(PARAM_BOOL, 'Whether the user has the capability ' . $capname . ' allowed.',
1362
                VALUE_OPTIONAL);
1363
        }
1364
 
1365
        return new external_single_structure($structure);
1366
    }
1367
 
1368
    /**
1369
     * Set the subscription state.
1370
     *
1371
     * @param   int     $forumid
1372
     * @param   int     $discussionid
1373
     * @param   bool    $targetstate
1374
     * @return  \stdClass
1375
     */
1376
    public static function set_subscription_state($forumid, $discussionid, $targetstate) {
1377
        global $PAGE, $USER;
1378
 
1379
        $params = self::validate_parameters(self::set_subscription_state_parameters(), [
1380
            'forumid' => $forumid,
1381
            'discussionid' => $discussionid,
1382
            'targetstate' => $targetstate
1383
        ]);
1384
 
1385
        $vaultfactory = mod_forum\local\container::get_vault_factory();
1386
        $forumvault = $vaultfactory->get_forum_vault();
1387
        $forum = $forumvault->get_from_id($params['forumid']);
1388
        $coursemodule = $forum->get_course_module_record();
1389
        $context = $forum->get_context();
1390
 
1391
        self::validate_context($context);
1392
 
1393
        $discussionvault = $vaultfactory->get_discussion_vault();
1394
        $discussion = $discussionvault->get_from_id($params['discussionid']);
1395
        $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1396
 
1397
        $forumrecord = $legacydatamapperfactory->get_forum_data_mapper()->to_legacy_object($forum);
1398
        $discussionrecord = $legacydatamapperfactory->get_discussion_data_mapper()->to_legacy_object($discussion);
1399
 
1400
        if (!\mod_forum\subscriptions::is_subscribable($forumrecord)) {
1401
            // Nothing to do. We won't actually output any content here though.
1402
            throw new \moodle_exception('cannotsubscribe', 'mod_forum');
1403
        }
1404
 
1405
        $issubscribed = \mod_forum\subscriptions::is_subscribed(
1406
            $USER->id,
1407
            $forumrecord,
1408
            $discussion->get_id(),
1409
            $coursemodule
1410
        );
1411
 
1412
        // If the current state doesn't equal the desired state then update the current
1413
        // state to the desired state.
1414
        if ($issubscribed != (bool) $params['targetstate']) {
1415
            if ($params['targetstate']) {
1416
                \mod_forum\subscriptions::subscribe_user_to_discussion($USER->id, $discussionrecord, $context);
1417
            } else {
1418
                \mod_forum\subscriptions::unsubscribe_user_from_discussion($USER->id, $discussionrecord, $context);
1419
            }
1420
        }
1421
 
1422
        $exporterfactory = mod_forum\local\container::get_exporter_factory();
1423
        $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1424
        return $exporter->export($PAGE->get_renderer('mod_forum'));
1425
    }
1426
 
1427
    /**
1428
     * Returns description of method parameters.
1429
     *
1430
     * @return external_function_parameters
1431
     */
1432
    public static function set_subscription_state_parameters() {
1433
        return new external_function_parameters(
1434
            [
1435
                'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1436
                'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'),
1437
                'targetstate' => new external_value(PARAM_BOOL, 'The target state')
1438
            ]
1439
        );
1440
    }
1441
 
1442
    /**
1443
     * Returns description of method result value.
1444
     *
1445
     * @return \core_external\external_description
1446
     */
1447
    public static function set_subscription_state_returns() {
1448
        return discussion_exporter::get_read_structure();
1449
    }
1450
 
1451
    /**
1452
     * Set the lock state.
1453
     *
1454
     * @param   int     $forumid
1455
     * @param   int     $discussionid
1456
     * @param   string    $targetstate
1457
     * @return  \stdClass
1458
     */
1459
    public static function set_lock_state($forumid, $discussionid, $targetstate) {
1460
        global $DB, $PAGE, $USER;
1461
 
1462
        $params = self::validate_parameters(self::set_lock_state_parameters(), [
1463
            'forumid' => $forumid,
1464
            'discussionid' => $discussionid,
1465
            'targetstate' => $targetstate
1466
        ]);
1467
 
1468
        $vaultfactory = mod_forum\local\container::get_vault_factory();
1469
        $forumvault = $vaultfactory->get_forum_vault();
1470
        $forum = $forumvault->get_from_id($params['forumid']);
1471
 
1472
        $managerfactory = mod_forum\local\container::get_manager_factory();
1473
        $capabilitymanager = $managerfactory->get_capability_manager($forum);
1474
        if (!$capabilitymanager->can_manage_forum($USER)) {
1475
            throw new moodle_exception('errorcannotlock', 'forum');
1476
        }
1477
 
1478
        // If the targetstate(currentstate) is not 0 then it should be set to the current time.
1479
        $lockedvalue = $targetstate ? 0 : time();
1480
        self::validate_context($forum->get_context());
1481
 
1482
        $discussionvault = $vaultfactory->get_discussion_vault();
1483
        $discussion = $discussionvault->get_from_id($params['discussionid']);
1484
 
1485
        // If the current state doesn't equal the desired state then update the current.
1486
        // state to the desired state.
1487
        $discussion->toggle_locked_state($lockedvalue);
1488
        $response = $discussionvault->update_discussion($discussion);
1489
        $discussion = !$response ? $response : $discussion;
1490
 
1491
        // Trigger an event.
1492
        $params = [
1493
            'context' => $forum->get_context(),
1494
            'objectid' => $discussion->get_id(),
1495
            'other' => ['forumid' => $forum->get_id(), 'status' => $lockedvalue ? 'locked' : 'unlocked'],
1496
        ];
1497
        $event = \mod_forum\event\discussion_lock_updated::create($params);
1498
        $discussionrecord = $DB->get_record('forum_discussions', ['id' => $discussion->get_id()]);
1499
        $event->add_record_snapshot('forum_discussions', $discussionrecord);
1500
        $event->trigger();
1501
 
1502
        $exporterfactory = mod_forum\local\container::get_exporter_factory();
1503
        $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1504
        return $exporter->export($PAGE->get_renderer('mod_forum'));
1505
    }
1506
 
1507
    /**
1508
     * Returns description of method parameters.
1509
     *
1510
     * @return external_function_parameters
1511
     */
1512
    public static function set_lock_state_parameters() {
1513
        return new external_function_parameters(
1514
            [
1515
                'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'),
1516
                'discussionid' => new external_value(PARAM_INT, 'The discussion to lock / unlock'),
1517
                'targetstate' => new external_value(PARAM_INT, 'The timestamp for the lock state')
1518
            ]
1519
        );
1520
    }
1521
 
1522
    /**
1523
     * Returns description of method result value.
1524
     *
1525
     * @return \core_external\external_description
1526
     */
1527
    public static function set_lock_state_returns() {
1528
        return new external_single_structure([
1529
            'id' => new external_value(PARAM_INT, 'The discussion we are locking.'),
1530
            'locked' => new external_value(PARAM_BOOL, 'The locked state of the discussion.'),
1531
            'times' => new external_single_structure([
1532
                'locked' => new external_value(PARAM_INT, 'The locked time of the discussion.'),
1533
            ])
1534
        ]);
1535
    }
1536
 
1537
    /**
1538
     * Set the pin state.
1539
     *
1540
     * @param   int     $discussionid
1541
     * @param   bool    $targetstate
1542
     * @return  \stdClass
1543
     */
1544
    public static function set_pin_state($discussionid, $targetstate) {
1545
        global $PAGE, $USER;
1546
        $params = self::validate_parameters(self::set_pin_state_parameters(), [
1547
            'discussionid' => $discussionid,
1548
            'targetstate' => $targetstate,
1549
        ]);
1550
        $vaultfactory = mod_forum\local\container::get_vault_factory();
1551
        $managerfactory = mod_forum\local\container::get_manager_factory();
1552
        $forumvault = $vaultfactory->get_forum_vault();
1553
        $discussionvault = $vaultfactory->get_discussion_vault();
1554
        $discussion = $discussionvault->get_from_id($params['discussionid']);
1555
        $forum = $forumvault->get_from_id($discussion->get_forum_id());
1556
        $capabilitymanager = $managerfactory->get_capability_manager($forum);
1557
 
1558
        self::validate_context($forum->get_context());
1559
 
1560
        $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1561
        if (!$capabilitymanager->can_pin_discussions($USER)) {
1562
            // Nothing to do. We won't actually output any content here though.
1563
            throw new \moodle_exception('cannotpindiscussions', 'mod_forum');
1564
        }
1565
 
1566
        $discussion->set_pinned($targetstate);
1567
        $discussionvault->update_discussion($discussion);
1568
 
1569
        $exporterfactory = mod_forum\local\container::get_exporter_factory();
1570
        $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion);
1571
        return $exporter->export($PAGE->get_renderer('mod_forum'));
1572
    }
1573
 
1574
    /**
1575
     * Returns description of method parameters.
1576
     *
1577
     * @return external_function_parameters
1578
     */
1579
    public static function set_pin_state_parameters() {
1580
        return new external_function_parameters(
1581
            [
1582
                'discussionid' => new external_value(PARAM_INT, 'The discussion to pin or unpin', VALUE_REQUIRED,
1583
                    null, NULL_NOT_ALLOWED),
1584
                'targetstate' => new external_value(PARAM_INT, 'The target state', VALUE_REQUIRED,
1585
                    null, NULL_NOT_ALLOWED),
1586
            ]
1587
        );
1588
    }
1589
 
1590
    /**
1591
     * Returns description of method result value.
1592
     *
1593
     * @return external_single_structure
1594
     */
1595
    public static function set_pin_state_returns() {
1596
        return discussion_exporter::get_read_structure();
1597
    }
1598
 
1599
    /**
1600
     * Returns description of method parameters
1601
     *
1602
     * @return external_function_parameters
1603
     * @since Moodle 3.8
1604
     */
1605
    public static function delete_post_parameters() {
1606
        return new external_function_parameters(
1607
            array(
1608
                'postid' => new external_value(PARAM_INT, 'Post to be deleted. It can be a discussion topic post.'),
1609
            )
1610
        );
1611
    }
1612
 
1613
    /**
1614
     * Deletes a post or a discussion completely when the post is the discussion topic.
1615
     *
1616
     * @param int $postid post to be deleted, it can be a discussion topic post.
1617
     * @return array of warnings and the status (true if the post/discussion was deleted)
1618
     * @since Moodle 3.8
1619
     * @throws moodle_exception
1620
     */
1621
    public static function delete_post($postid) {
1622
        global $USER, $CFG;
1623
        require_once($CFG->dirroot . "/mod/forum/lib.php");
1624
 
1625
        $params = self::validate_parameters(self::delete_post_parameters(),
1626
            array(
1627
                'postid' => $postid,
1628
            )
1629
        );
1630
        $warnings = array();
1631
        $vaultfactory = mod_forum\local\container::get_vault_factory();
1632
        $forumvault = $vaultfactory->get_forum_vault();
1633
        $discussionvault = $vaultfactory->get_discussion_vault();
1634
        $postvault = $vaultfactory->get_post_vault();
1635
        $postentity = $postvault->get_from_id($params['postid']);
1636
 
1637
        if (empty($postentity)) {
1638
            throw new moodle_exception('invalidpostid', 'forum');
1639
        }
1640
 
1641
        $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
1642
 
1643
        if (empty($discussionentity)) {
1644
            throw new moodle_exception('notpartofdiscussion', 'forum');
1645
        }
1646
 
1647
        $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
1648
        if (empty($forumentity)) {
1649
            throw new moodle_exception('invalidforumid', 'forum');
1650
        }
1651
 
1652
        $context = $forumentity->get_context();
1653
 
1654
        self::validate_context($context);
1655
 
1656
        $managerfactory = mod_forum\local\container::get_manager_factory();
1657
        $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
1658
        $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
1659
        $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
1660
        $discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper();
1661
        $postdatamapper = $legacydatamapperfactory->get_post_data_mapper();
1662
 
1663
        $replycount = $postvault->get_reply_count_for_post_id_in_discussion_id($USER, $postentity->get_id(),
1664
            $discussionentity->get_id(), true);
1665
        $hasreplies = $replycount > 0;
1666
 
1667
        $capabilitymanager->validate_delete_post($USER, $discussionentity, $postentity, $hasreplies);
1668
 
1669
        if (!$postentity->has_parent()) {
1670
            $status = forum_delete_discussion(
1671
                $discussiondatamapper->to_legacy_object($discussionentity),
1672
                false,
1673
                $forumentity->get_course_record(),
1674
                $forumentity->get_course_module_record(),
1675
                $forumdatamapper->to_legacy_object($forumentity)
1676
            );
1677
        } else {
1678
            $status = forum_delete_post(
1679
                $postdatamapper->to_legacy_object($postentity),
1680
                has_capability('mod/forum:deleteanypost', $context),
1681
                $forumentity->get_course_record(),
1682
                $forumentity->get_course_module_record(),
1683
                $forumdatamapper->to_legacy_object($forumentity)
1684
            );
1685
        }
1686
 
1687
        $result = array();
1688
        $result['status'] = $status;
1689
        $result['warnings'] = $warnings;
1690
        return $result;
1691
    }
1692
 
1693
    /**
1694
     * Returns description of method result value
1695
     *
1696
     * @return \core_external\external_description
1697
     * @since Moodle 3.8
1698
     */
1699
    public static function delete_post_returns() {
1700
        return new external_single_structure(
1701
            array(
1702
                'status' => new external_value(PARAM_BOOL, 'True if the post/discussion was deleted, false otherwise.'),
1703
                'warnings' => new external_warnings()
1704
            )
1705
        );
1706
    }
1707
 
1708
    /**
1709
     * Get the forum posts in the specified forum instance.
1710
     *
1711
     * @param   int $userid
1712
     * @param   int $cmid
1713
     * @param   string $sortby
1714
     * @param   string $sortdirection
1715
     * @return  array
1716
     */
1717
    public static function get_discussion_posts_by_userid(int $userid, int $cmid, ?string $sortby, ?string $sortdirection) {
1718
        global $USER, $DB;
1719
        // Validate the parameter.
1720
        $params = self::validate_parameters(self::get_discussion_posts_by_userid_parameters(), [
1721
                'userid' => $userid,
1722
                'cmid' => $cmid,
1723
                'sortby' => $sortby,
1724
                'sortdirection' => $sortdirection,
1725
        ]);
1726
        $warnings = [];
1727
 
1728
        $user = core_user::get_user($params['userid']);
1729
 
1730
        $vaultfactory = mod_forum\local\container::get_vault_factory();
1731
 
1732
        $forumvault = $vaultfactory->get_forum_vault();
1733
        $forum = $forumvault->get_from_course_module_id($params['cmid']);
1734
 
1735
        // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..).
1736
        self::validate_context($forum->get_context());
1737
 
1738
        $sortby = $params['sortby'];
1739
        $sortdirection = $params['sortdirection'];
1740
        $sortallowedvalues = ['id', 'created', 'modified'];
1741
        $directionallowedvalues = ['ASC', 'DESC'];
1742
 
1743
        if (!in_array(strtolower($sortby), $sortallowedvalues)) {
1744
            throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
1745
                    'allowed values are: ' . implode(', ', $sortallowedvalues));
1746
        }
1747
 
1748
        $sortdirection = strtoupper($sortdirection);
1749
        if (!in_array($sortdirection, $directionallowedvalues)) {
1750
            throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
1751
                    'allowed values are: ' . implode(',', $directionallowedvalues));
1752
        }
1753
 
1754
        $managerfactory = mod_forum\local\container::get_manager_factory();
1755
        $capabilitymanager = $managerfactory->get_capability_manager($forum);
1756
 
1757
        $discussionsummariesvault = $vaultfactory->get_discussions_in_forum_vault();
1758
        $discussionsummaries = $discussionsummariesvault->get_from_forum_id(
1759
            $forum->get_id(),
1760
            true,
1761
            null,
1762
            $discussionsummariesvault::SORTORDER_CREATED_ASC,
1763
            0,
1764
 
1765
        );
1766
 
1767
        $postvault = $vaultfactory->get_post_vault();
1768
 
1769
        $builderfactory = mod_forum\local\container::get_builder_factory();
1770
        $postbuilder = $builderfactory->get_exported_posts_builder();
1771
 
1772
        $builtdiscussions = [];
1773
        foreach ($discussionsummaries as $discussionsummary) {
1774
            $discussion = $discussionsummary->get_discussion();
1775
            if (!$capabilitymanager->can_view_discussion($USER, $discussion)) {
1776
                continue;
1777
            }
1778
            $posts = $postvault->get_posts_in_discussion_for_user_id(
1779
                    $discussion->get_id(),
1780
                    $user->id,
1781
                    $capabilitymanager->can_view_any_private_reply($USER),
1782
                    "{$sortby} {$sortdirection}"
1783
            );
1784
            if (empty($posts)) {
1785
                continue;
1786
            }
1787
 
1788
            $parentids = array_filter(array_map(function($post) {
1789
                return $post->has_parent() ? $post->get_parent_id() : null;
1790
            }, $posts));
1791
 
1792
            $parentposts = [];
1793
            if ($parentids) {
1794
                $parentposts = $postbuilder->build(
1795
                    $USER,
1796
                    [$forum],
1797
                    [$discussion],
1798
                    $postvault->get_from_ids(array_values($parentids))
1799
                );
1800
            }
1801
 
1802
            $discussionauthor = $discussionsummary->get_first_post_author();
1803
            $firstpost = $discussionsummary->get_first_post();
1804
 
1805
            $builtdiscussions[] = [
1806
                'name' => $discussion->get_name(),
1807
                'id' => $discussion->get_id(),
1808
                'timecreated' => $firstpost->get_time_created(),
1809
                'authorfullname' => $discussionauthor->get_full_name(),
1810
                'posts' => [
1811
                    'userposts' => $postbuilder->build($USER, [$forum], [$discussion], $posts),
1812
                    'parentposts' => $parentposts,
1813
                ],
1814
            ];
1815
        }
1816
 
1817
        return [
1818
                'discussions' => $builtdiscussions,
1819
                'warnings' => $warnings,
1820
        ];
1821
    }
1822
 
1823
    /**
1824
     * Describe the post parameters.
1825
     *
1826
     * @return external_function_parameters
1827
     */
1828
    public static function get_discussion_posts_by_userid_parameters() {
1829
        return new external_function_parameters ([
1830
                'userid' => new external_value(
1831
                        PARAM_INT, 'The ID of the user of whom to fetch posts.', VALUE_REQUIRED),
1832
                'cmid' => new external_value(
1833
                        PARAM_INT, 'The ID of the module of which to fetch items.', VALUE_REQUIRED),
1834
                'sortby' => new external_value(
1835
                        PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
1836
                'sortdirection' => new external_value(
1837
                        PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
1838
        ]);
1839
    }
1840
 
1841
    /**
1842
     * Describe the post return format.
1843
     *
1844
     * @return external_single_structure
1845
     */
1846
    public static function get_discussion_posts_by_userid_returns() {
1847
        return new external_single_structure([
1848
                'discussions' => new external_multiple_structure(
1849
                    new external_single_structure([
1850
                        'name' => new external_value(PARAM_RAW, 'Name of the discussion'),
1851
                        'id' => new external_value(PARAM_INT, 'ID of the discussion'),
1852
                        'timecreated' => new external_value(PARAM_INT, 'Timestamp of the discussion start'),
1853
                        'authorfullname' => new external_value(PARAM_RAW, 'Full name of the user that started the discussion'),
1854
                        'posts' => new external_single_structure([
1855
                            'userposts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
1856
                            'parentposts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
1857
                        ]),
1858
                    ])),
1859
                'warnings' => new external_warnings(),
1860
        ]);
1861
    }
1862
 
1863
    /**
1864
     * Returns description of method parameters
1865
     *
1866
     * @return external_function_parameters
1867
     * @since Moodle 3.8
1868
     */
1869
    public static function get_discussion_post_parameters() {
1870
        return new external_function_parameters(
1871
            array(
1872
                'postid' => new external_value(PARAM_INT, 'Post to fetch.'),
1873
            )
1874
        );
1875
    }
1876
 
1877
    /**
1878
     * Get a particular discussion post.
1879
     *
1880
     * @param int $postid post to fetch
1881
     * @return array of post and warnings (if any)
1882
     * @since Moodle 3.8
1883
     * @throws moodle_exception
1884
     */
1885
    public static function get_discussion_post($postid) {
1886
        global $USER, $CFG;
1887
 
1888
        $params = self::validate_parameters(self::get_discussion_post_parameters(),
1889
                                            array(
1890
                                                'postid' => $postid,
1891
                                            ));
1892
        $warnings = array();
1893
        $vaultfactory = mod_forum\local\container::get_vault_factory();
1894
        $forumvault = $vaultfactory->get_forum_vault();
1895
        $discussionvault = $vaultfactory->get_discussion_vault();
1896
        $postvault = $vaultfactory->get_post_vault();
1897
 
1898
        $postentity = $postvault->get_from_id($params['postid']);
1899
        if (empty($postentity)) {
1900
            throw new moodle_exception('invalidpostid', 'forum');
1901
        }
1902
        $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
1903
        if (empty($discussionentity)) {
1904
            throw new moodle_exception('notpartofdiscussion', 'forum');
1905
        }
1906
        $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
1907
        if (empty($forumentity)) {
1908
            throw new moodle_exception('invalidforumid', 'forum');
1909
        }
1910
        self::validate_context($forumentity->get_context());
1911
 
1912
        $managerfactory = mod_forum\local\container::get_manager_factory();
1913
        $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
1914
 
1915
        if (!$capabilitymanager->can_view_post($USER, $discussionentity, $postentity)) {
1916
            throw new moodle_exception('noviewdiscussionspermission', 'forum');
1917
        }
1918
 
1919
        $builderfactory = mod_forum\local\container::get_builder_factory();
1920
        $postbuilder = $builderfactory->get_exported_posts_builder();
1921
        $posts = $postbuilder->build($USER, [$forumentity], [$discussionentity], [$postentity]);
1922
        $post = empty($posts) ? array() : reset($posts);
1923
 
1924
        $result = array();
1925
        $result['post'] = $post;
1926
        $result['warnings'] = $warnings;
1927
        return $result;
1928
    }
1929
 
1930
    /**
1931
     * Returns description of method result value
1932
     *
1933
     * @return \core_external\external_description
1934
     * @since Moodle 3.8
1935
     */
1936
    public static function get_discussion_post_returns() {
1937
        return new external_single_structure(
1938
            array(
1939
                'post' => \mod_forum\local\exporters\post::get_read_structure(),
1940
                'warnings' => new external_warnings(),
1941
            )
1942
        );
1943
    }
1944
 
1945
    /**
1946
     * Returns description of method parameters
1947
     *
1948
     * @return external_function_parameters
1949
     * @since Moodle 3.8
1950
     */
1951
    public static function prepare_draft_area_for_post_parameters() {
1952
        return new external_function_parameters(
1953
            array(
1954
                'postid' => new external_value(PARAM_INT, 'Post to prepare the draft area for.'),
1955
                'area' => new external_value(PARAM_ALPHA, 'Area to prepare: attachment or post.'),
1956
                'draftitemid' => new external_value(PARAM_INT, 'The draft item id to use. 0 to generate one.',
1957
                    VALUE_DEFAULT, 0),
1958
                'filestokeep' => new external_multiple_structure(
1959
                    new external_single_structure(
1960
                        array(
1961
                            'filename' => new external_value(PARAM_FILE, 'File name.'),
1962
                            'filepath' => new external_value(PARAM_PATH, 'File path.'),
1963
                        )
1964
                    ), 'Only keep these files in the draft file area. Empty for keeping all.', VALUE_DEFAULT, []
1965
                ),
1966
            )
1967
        );
1968
    }
1969
 
1970
    /**
1971
     * Prepares a draft area for editing a post.
1972
     *
1973
     * @param int $postid post to prepare the draft area for
1974
     * @param string $area area to prepare attachment or post
1975
     * @param int $draftitemid the draft item id to use. 0 to generate a new one.
1976
     * @param array $filestokeep only keep these files in the draft file area. Empty for keeping all.
1977
     * @return array of files in the area, the area options and the draft item id
1978
     * @since Moodle 3.8
1979
     * @throws moodle_exception
1980
     */
1981
    public static function prepare_draft_area_for_post($postid, $area, $draftitemid = 0, $filestokeep = []) {
1982
        global $USER;
1983
 
1984
        $params = self::validate_parameters(
1985
            self::prepare_draft_area_for_post_parameters(),
1986
            array(
1987
                'postid' => $postid,
1988
                'area' => $area,
1989
                'draftitemid' => $draftitemid,
1990
                'filestokeep' => $filestokeep,
1991
            )
1992
        );
1993
        $directionallowedvalues = ['ASC', 'DESC'];
1994
 
1995
        $allowedareas = ['attachment', 'post'];
1996
        if (!in_array($params['area'], $allowedareas)) {
1997
            throw new invalid_parameter_exception('Invalid value for area parameter
1998
                (value: ' . $params['area'] . '),' . 'allowed values are: ' . implode(', ', $allowedareas));
1999
        }
2000
 
2001
        $warnings = array();
2002
        $vaultfactory = mod_forum\local\container::get_vault_factory();
2003
        $forumvault = $vaultfactory->get_forum_vault();
2004
        $discussionvault = $vaultfactory->get_discussion_vault();
2005
        $postvault = $vaultfactory->get_post_vault();
2006
 
2007
        $postentity = $postvault->get_from_id($params['postid']);
2008
        if (empty($postentity)) {
2009
            throw new moodle_exception('invalidpostid', 'forum');
2010
        }
2011
        $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
2012
        if (empty($discussionentity)) {
2013
            throw new moodle_exception('notpartofdiscussion', 'forum');
2014
        }
2015
        $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
2016
        if (empty($forumentity)) {
2017
            throw new moodle_exception('invalidforumid', 'forum');
2018
        }
2019
 
2020
        $context = $forumentity->get_context();
2021
        self::validate_context($context);
2022
 
2023
        $managerfactory = mod_forum\local\container::get_manager_factory();
2024
        $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
2025
 
2026
        if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) {
2027
            throw new moodle_exception('noviewdiscussionspermission', 'forum');
2028
        }
2029
 
2030
        if ($params['area'] == 'attachment') {
2031
            $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
2032
            $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
2033
            $forum = $forumdatamapper->to_legacy_object($forumentity);
2034
 
2035
            $areaoptions = mod_forum_post_form::attachment_options($forum);
2036
            $messagetext = null;
2037
        } else {
2038
            $areaoptions = mod_forum_post_form::editor_options($context, $postentity->get_id());
2039
            $messagetext = $postentity->get_message();
2040
        }
2041
 
2042
        $draftitemid = empty($params['draftitemid']) ? 0 : $params['draftitemid'];
2043
        $messagetext = file_prepare_draft_area($draftitemid, $context->id, 'mod_forum', $params['area'],
2044
            $postentity->get_id(), $areaoptions, $messagetext);
2045
 
2046
        // Just get a structure compatible with external API.
2047
        array_walk($areaoptions, function(&$item, $key) {
2048
            $item = ['name' => $key, 'value' => $item];
2049
        });
2050
 
2051
        // Do we need to keep only the given files?
2052
        $usercontext = context_user::instance($USER->id);
2053
        if (!empty($params['filestokeep'])) {
2054
            $fs = get_file_storage();
2055
 
2056
            if ($areafiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid)) {
2057
                $filestokeep = [];
2058
                foreach ($params['filestokeep'] as $ftokeep) {
2059
                    $filestokeep[$ftokeep['filepath']][$ftokeep['filename']] = $ftokeep;
2060
                }
2061
 
2062
                foreach ($areafiles as $file) {
2063
                    if ($file->is_directory()) {
2064
                        continue;
2065
                    }
2066
                    if (!isset($filestokeep[$file->get_filepath()][$file->get_filename()])) {
2067
                        $file->delete();    // Not in the list to be kept.
2068
                    }
2069
                }
2070
            }
2071
        }
2072
 
2073
        $result = array(
2074
            'draftitemid' => $draftitemid,
2075
            'files' => external_util::get_area_files($usercontext->id, 'user', 'draft',
2076
                $draftitemid),
2077
            'areaoptions' => $areaoptions,
2078
            'messagetext' => $messagetext,
2079
            'warnings' => $warnings,
2080
        );
2081
        return $result;
2082
    }
2083
 
2084
    /**
2085
     * Returns description of method result value
2086
     *
2087
     * @return \core_external\external_description
2088
     * @since Moodle 3.8
2089
     */
2090
    public static function prepare_draft_area_for_post_returns() {
2091
        return new external_single_structure(
2092
            array(
2093
                'draftitemid' => new external_value(PARAM_INT, 'Draft item id for the file area.'),
2094
                'files' => new external_files('Draft area files.', VALUE_OPTIONAL),
2095
                'areaoptions' => new external_multiple_structure(
2096
                    new external_single_structure(
2097
                        array(
2098
                            'name' => new external_value(PARAM_RAW, 'Name of option.'),
2099
                            'value' => new external_value(PARAM_RAW, 'Value of option.'),
2100
                        )
2101
                    ), 'Draft file area options.'
2102
                ),
2103
                'messagetext' => new external_value(PARAM_RAW, 'Message text with URLs rewritten.'),
2104
                'warnings' => new external_warnings(),
2105
            )
2106
        );
2107
    }
2108
 
2109
    /**
2110
     * Returns description of method parameters
2111
     *
2112
     * @return external_function_parameters
2113
     * @since Moodle 3.8
2114
     */
2115
    public static function update_discussion_post_parameters() {
2116
        return new external_function_parameters(
2117
            [
2118
                'postid' => new external_value(PARAM_INT, 'Post to be updated. It can be a discussion topic post.'),
2119
                'subject' => new external_value(PARAM_TEXT, 'Updated post subject', VALUE_DEFAULT, ''),
2120
                'message' => new external_value(PARAM_RAW, 'Updated post message (HTML assumed if messageformat is not provided)',
2121
                    VALUE_DEFAULT, ''),
2122
                'messageformat' => new external_format_value('message', VALUE_DEFAULT),
2123
                'options' => new external_multiple_structure (
2124
                    new external_single_structure(
2125
                        [
2126
                            'name' => new external_value(
2127
                                PARAM_ALPHANUM,
2128
                                'The allowed keys (value format) are:
2129
                                pinned (bool); (only for discussions) whether to pin this discussion or not
2130
                                discussionsubscribe (bool); whether to subscribe to the post or not
2131
                                inlineattachmentsid (int); the draft file area id for inline attachments in the text
2132
                                attachmentsid (int); the draft file area id for attachments'
2133
                            ),
2134
                            'value' => new external_value(PARAM_RAW, 'The value of the option.')
2135
                        ]
2136
                    ),
2137
                    'Configuration options for the post.',
2138
                    VALUE_DEFAULT,
2139
                    []
2140
                ),
2141
            ]
2142
        );
2143
    }
2144
 
2145
    /**
2146
     * Updates a post or a discussion post topic.
2147
     *
2148
     * @param int $postid post to be updated, it can be a discussion topic post.
2149
     * @param string $subject updated post subject
2150
     * @param string $message updated post message (HTML assumed if messageformat is not provided)
2151
     * @param int $messageformat The format of the message, defaults to FORMAT_HTML
2152
     * @param array $options different configuration options for the post to be updated.
2153
     * @return array of warnings and the status (true if the post/discussion was deleted)
2154
     * @since Moodle 3.8
2155
     * @throws moodle_exception
2156
     * @todo support more options: timed posts, groups change and tags.
2157
     */
2158
    public static function update_discussion_post($postid, $subject = '', $message = '', $messageformat = FORMAT_HTML,
2159
            $options = []) {
2160
        global $CFG, $USER;
2161
        require_once($CFG->dirroot . "/mod/forum/lib.php");
2162
 
2163
        $params = self::validate_parameters(self::add_discussion_post_parameters(),
2164
            [
2165
                'postid' => $postid,
2166
                'subject' => $subject,
2167
                'message' => $message,
2168
                'options' => $options,
2169
                'messageformat' => $messageformat,
2170
            ]
2171
        );
2172
        $warnings = [];
2173
 
2174
        // Validate options.
2175
        $options = [];
2176
        foreach ($params['options'] as $option) {
2177
            $name = trim($option['name']);
2178
            switch ($name) {
2179
                case 'pinned':
2180
                    $value = clean_param($option['value'], PARAM_BOOL);
2181
                    break;
2182
                case 'discussionsubscribe':
2183
                    $value = clean_param($option['value'], PARAM_BOOL);
2184
                    break;
2185
                case 'inlineattachmentsid':
2186
                    $value = clean_param($option['value'], PARAM_INT);
2187
                    break;
2188
                case 'attachmentsid':
2189
                    $value = clean_param($option['value'], PARAM_INT);
2190
                    break;
2191
                default:
2192
                    throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
2193
            }
2194
            $options[$name] = $value;
2195
        }
2196
 
2197
        $managerfactory = mod_forum\local\container::get_manager_factory();
2198
        $vaultfactory = mod_forum\local\container::get_vault_factory();
2199
        $forumvault = $vaultfactory->get_forum_vault();
2200
        $discussionvault = $vaultfactory->get_discussion_vault();
2201
        $postvault = $vaultfactory->get_post_vault();
2202
        $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
2203
        $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
2204
        $discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper();
2205
        $postdatamapper = $legacydatamapperfactory->get_post_data_mapper();
2206
 
2207
        $postentity = $postvault->get_from_id($params['postid']);
2208
        if (empty($postentity)) {
2209
            throw new moodle_exception('invalidpostid', 'forum');
2210
        }
2211
        $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
2212
        if (empty($discussionentity)) {
2213
            throw new moodle_exception('notpartofdiscussion', 'forum');
2214
        }
2215
        $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
2216
        if (empty($forumentity)) {
2217
            throw new moodle_exception('invalidforumid', 'forum');
2218
        }
2219
        $forum = $forumdatamapper->to_legacy_object($forumentity);
2220
        $capabilitymanager = $managerfactory->get_capability_manager($forumentity);
2221
 
2222
        $modcontext = $forumentity->get_context();
2223
        self::validate_context($modcontext);
2224
 
2225
        if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) {
2226
            throw new moodle_exception('cannotupdatepost', 'forum');
2227
        }
2228
 
2229
        // Get the original post.
2230
        $updatepost = $postdatamapper->to_legacy_object($postentity);
2231
        $updatepost->itemid = IGNORE_FILE_MERGE;
2232
        $updatepost->attachments = IGNORE_FILE_MERGE;
2233
 
2234
        // Prepare the post to be updated.
2235
        if ($params['subject'] !== '') {
2236
            $updatepost->subject = $params['subject'];
2237
        }
2238
 
2239
        if ($params['message'] !== '' && isset($params['messageformat'])) {
2240
            $updatepost->message       = $params['message'];
2241
            $updatepost->messageformat = $params['messageformat'];
2242
            $updatepost->messagetrust  = trusttext_trusted($modcontext);
2243
            // Clean message text.
2244
            $updatepost = trusttext_pre_edit($updatepost, 'message', $modcontext);
2245
        }
2246
 
2247
        if (isset($options['discussionsubscribe'])) {
2248
            // No need to validate anything here, forum_post_subscription will do.
2249
            $updatepost->discussionsubscribe = $options['discussionsubscribe'];
2250
        }
2251
 
2252
        // When editing first post/discussion.
2253
        if (!$postentity->has_parent()) {
2254
            // Defaults for discussion topic posts.
2255
            $updatepost->name = $discussionentity->get_name();
2256
            $updatepost->timestart = $discussionentity->get_time_start();
2257
            $updatepost->timeend = $discussionentity->get_time_end();
2258
 
2259
            if (isset($options['pinned'])) {
2260
                if ($capabilitymanager->can_pin_discussions($USER)) {
2261
                    // Can change pinned if we have capability.
2262
                    $updatepost->pinned = !empty($options['pinned']) ? FORUM_DISCUSSION_PINNED : FORUM_DISCUSSION_UNPINNED;
2263
                }
2264
            }
2265
        }
2266
 
2267
        if (isset($options['inlineattachmentsid'])) {
2268
            $updatepost->itemid = $options['inlineattachmentsid'];
2269
        }
2270
 
2271
        if (isset($options['attachmentsid']) && forum_can_create_attachment($forum, $modcontext)) {
2272
            $updatepost->attachments = $options['attachmentsid'];
2273
        }
2274
 
2275
        // Update the post.
2276
        $fakemform = $updatepost->id;
2277
        if (forum_update_post($updatepost, $fakemform)) {
2278
            $discussion = $discussiondatamapper->to_legacy_object($discussionentity);
2279
 
2280
            forum_trigger_post_updated_event($updatepost, $discussion, $modcontext, $forum);
2281
 
2282
            forum_post_subscription(
2283
                $updatepost,
2284
                $forum,
2285
                $discussion
2286
            );
2287
            $status = true;
2288
        } else {
2289
            $status = false;
2290
        }
2291
 
2292
        return [
2293
            'status' => $status,
2294
            'warnings' => $warnings,
2295
        ];
2296
    }
2297
 
2298
    /**
2299
     * Returns description of method result value
2300
     *
2301
     * @return \core_external\external_description
2302
     * @since Moodle 3.8
2303
     */
2304
    public static function update_discussion_post_returns() {
2305
        return new external_single_structure(
2306
            [
2307
                'status' => new external_value(PARAM_BOOL, 'True if the post/discussion was updated, false otherwise.'),
2308
                'warnings' => new external_warnings()
2309
            ]
2310
        );
2311
    }
2312
}