Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

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