Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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