Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

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