Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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