PageRenderTime 60ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/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

Large files files are truncated, but you can click here to view the full file

  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'),

Large files files are truncated, but you can click here to view the full file