PageRenderTime 41ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/forum/externallib.php

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