PageRenderTime 64ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/forum/externallib.php

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