Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

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