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