Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

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