Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [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 $exporterfactory = mod_forum\local\container::get_exporter_factory(); 1751 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion); 1752 return $exporter->export($PAGE->get_renderer('mod_forum')); 1753 } 1754 1755 /** 1756 * Returns description of method parameters. 1757 * 1758 * @return external_function_parameters 1759 */ 1760 public static function set_lock_state_parameters() { 1761 return new external_function_parameters( 1762 [ 1763 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'), 1764 'discussionid' => new external_value(PARAM_INT, 'The discussion to lock / unlock'), 1765 'targetstate' => new external_value(PARAM_INT, 'The timestamp for the lock state') 1766 ] 1767 ); 1768 } 1769 1770 /** 1771 * Returns description of method result value. 1772 * 1773 * @return \core_external\external_description 1774 */ 1775 public static function set_lock_state_returns() { 1776 return new external_single_structure([ 1777 'id' => new external_value(PARAM_INT, 'The discussion we are locking.'), 1778 'locked' => new external_value(PARAM_BOOL, 'The locked state of the discussion.'), 1779 'times' => new external_single_structure([ 1780 'locked' => new external_value(PARAM_INT, 'The locked time of the discussion.'), 1781 ]) 1782 ]); 1783 } 1784 1785 /** 1786 * Set the pin state. 1787 * 1788 * @param int $discussionid 1789 * @param bool $targetstate 1790 * @return \stdClass 1791 */ 1792 public static function set_pin_state($discussionid, $targetstate) { 1793 global $PAGE, $USER; 1794 $params = self::validate_parameters(self::set_pin_state_parameters(), [ 1795 'discussionid' => $discussionid, 1796 'targetstate' => $targetstate, 1797 ]); 1798 $vaultfactory = mod_forum\local\container::get_vault_factory(); 1799 $managerfactory = mod_forum\local\container::get_manager_factory(); 1800 $forumvault = $vaultfactory->get_forum_vault(); 1801 $discussionvault = $vaultfactory->get_discussion_vault(); 1802 $discussion = $discussionvault->get_from_id($params['discussionid']); 1803 $forum = $forumvault->get_from_id($discussion->get_forum_id()); 1804 $capabilitymanager = $managerfactory->get_capability_manager($forum); 1805 1806 self::validate_context($forum->get_context()); 1807 1808 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory(); 1809 if (!$capabilitymanager->can_pin_discussions($USER)) { 1810 // Nothing to do. We won't actually output any content here though. 1811 throw new \moodle_exception('cannotpindiscussions', 'mod_forum'); 1812 } 1813 1814 $discussion->set_pinned($targetstate); 1815 $discussionvault->update_discussion($discussion); 1816 1817 $exporterfactory = mod_forum\local\container::get_exporter_factory(); 1818 $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion); 1819 return $exporter->export($PAGE->get_renderer('mod_forum')); 1820 } 1821 1822 /** 1823 * Returns description of method parameters. 1824 * 1825 * @return external_function_parameters 1826 */ 1827 public static function set_pin_state_parameters() { 1828 return new external_function_parameters( 1829 [ 1830 'discussionid' => new external_value(PARAM_INT, 'The discussion to pin or unpin', VALUE_REQUIRED, 1831 null, NULL_NOT_ALLOWED), 1832 'targetstate' => new external_value(PARAM_INT, 'The target state', VALUE_REQUIRED, 1833 null, NULL_NOT_ALLOWED), 1834 ] 1835 ); 1836 } 1837 1838 /** 1839 * Returns description of method result value. 1840 * 1841 * @return external_single_structure 1842 */ 1843 public static function set_pin_state_returns() { 1844 return discussion_exporter::get_read_structure(); 1845 } 1846 1847 /** 1848 * Returns description of method parameters 1849 * 1850 * @return external_function_parameters 1851 * @since Moodle 3.8 1852 */ 1853 public static function delete_post_parameters() { 1854 return new external_function_parameters( 1855 array( 1856 'postid' => new external_value(PARAM_INT, 'Post to be deleted. It can be a discussion topic post.'), 1857 ) 1858 ); 1859 } 1860 1861 /** 1862 * Deletes a post or a discussion completely when the post is the discussion topic. 1863 * 1864 * @param int $postid post to be deleted, it can be a discussion topic post. 1865 * @return array of warnings and the status (true if the post/discussion was deleted) 1866 * @since Moodle 3.8 1867 * @throws moodle_exception 1868 */ 1869 public static function delete_post($postid) { 1870 global $USER, $CFG; 1871 require_once($CFG->dirroot . "/mod/forum/lib.php"); 1872 1873 $params = self::validate_parameters(self::delete_post_parameters(), 1874 array( 1875 'postid' => $postid, 1876 ) 1877 ); 1878 $warnings = array(); 1879 $vaultfactory = mod_forum\local\container::get_vault_factory(); 1880 $forumvault = $vaultfactory->get_forum_vault(); 1881 $discussionvault = $vaultfactory->get_discussion_vault(); 1882 $postvault = $vaultfactory->get_post_vault(); 1883 $postentity = $postvault->get_from_id($params['postid']); 1884 1885 if (empty($postentity)) { 1886 throw new moodle_exception('invalidpostid', 'forum'); 1887 } 1888 1889 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id()); 1890 1891 if (empty($discussionentity)) { 1892 throw new moodle_exception('notpartofdiscussion', 'forum'); 1893 } 1894 1895 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id()); 1896 if (empty($forumentity)) { 1897 throw new moodle_exception('invalidforumid', 'forum'); 1898 } 1899 1900 $context = $forumentity->get_context(); 1901 1902 self::validate_context($context); 1903 1904 $managerfactory = mod_forum\local\container::get_manager_factory(); 1905 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory(); 1906 $capabilitymanager = $managerfactory->get_capability_manager($forumentity); 1907 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper(); 1908 $discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper(); 1909 $postdatamapper = $legacydatamapperfactory->get_post_data_mapper(); 1910 1911 $replycount = $postvault->get_reply_count_for_post_id_in_discussion_id($USER, $postentity->get_id(), 1912 $discussionentity->get_id(), true); 1913 $hasreplies = $replycount > 0; 1914 1915 $capabilitymanager->validate_delete_post($USER, $discussionentity, $postentity, $hasreplies); 1916 1917 if (!$postentity->has_parent()) { 1918 $status = forum_delete_discussion( 1919 $discussiondatamapper->to_legacy_object($discussionentity), 1920 false, 1921 $forumentity->get_course_record(), 1922 $forumentity->get_course_module_record(), 1923 $forumdatamapper->to_legacy_object($forumentity) 1924 ); 1925 } else { 1926 $status = forum_delete_post( 1927 $postdatamapper->to_legacy_object($postentity), 1928 has_capability('mod/forum:deleteanypost', $context), 1929 $forumentity->get_course_record(), 1930 $forumentity->get_course_module_record(), 1931 $forumdatamapper->to_legacy_object($forumentity) 1932 ); 1933 } 1934 1935 $result = array(); 1936 $result['status'] = $status; 1937 $result['warnings'] = $warnings; 1938 return $result; 1939 } 1940 1941 /** 1942 * Returns description of method result value 1943 * 1944 * @return \core_external\external_description 1945 * @since Moodle 3.8 1946 */ 1947 public static function delete_post_returns() { 1948 return new external_single_structure( 1949 array( 1950 'status' => new external_value(PARAM_BOOL, 'True if the post/discussion was deleted, false otherwise.'), 1951 'warnings' => new external_warnings() 1952 ) 1953 ); 1954 } 1955 1956 /** 1957 * Get the forum posts in the specified forum instance. 1958 * 1959 * @param int $userid 1960 * @param int $cmid 1961 * @param string $sortby 1962 * @param string $sortdirection 1963 * @return array 1964 */ 1965 public static function get_discussion_posts_by_userid(int $userid, int $cmid, ?string $sortby, ?string $sortdirection) { 1966 global $USER, $DB; 1967 // Validate the parameter. 1968 $params = self::validate_parameters(self::get_discussion_posts_by_userid_parameters(), [ 1969 'userid' => $userid, 1970 'cmid' => $cmid, 1971 'sortby' => $sortby, 1972 'sortdirection' => $sortdirection, 1973 ]); 1974 $warnings = []; 1975 1976 $user = core_user::get_user($params['userid']); 1977 1978 $vaultfactory = mod_forum\local\container::get_vault_factory(); 1979 1980 $forumvault = $vaultfactory->get_forum_vault(); 1981 $forum = $forumvault->get_from_course_module_id($params['cmid']); 1982 1983 // Validate the module context. It checks everything that affects the module visibility (including groupings, etc..). 1984 self::validate_context($forum->get_context()); 1985 1986 $sortby = $params['sortby']; 1987 $sortdirection = $params['sortdirection']; 1988 $sortallowedvalues = ['id', 'created', 'modified']; 1989 $directionallowedvalues = ['ASC', 'DESC']; 1990 1991 if (!in_array(strtolower($sortby), $sortallowedvalues)) { 1992 throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' . 1993 'allowed values are: ' . implode(', ', $sortallowedvalues)); 1994 } 1995 1996 $sortdirection = strtoupper($sortdirection); 1997 if (!in_array($sortdirection, $directionallowedvalues)) { 1998 throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' . 1999 'allowed values are: ' . implode(',', $directionallowedvalues)); 2000 } 2001 2002 $managerfactory = mod_forum\local\container::get_manager_factory(); 2003 $capabilitymanager = $managerfactory->get_capability_manager($forum); 2004 2005 $discussionsummariesvault = $vaultfactory->get_discussions_in_forum_vault(); 2006 $discussionsummaries = $discussionsummariesvault->get_from_forum_id( 2007 $forum->get_id(), 2008 true, 2009 null, 2010 $discussionsummariesvault::SORTORDER_CREATED_ASC, 2011 0, 2012 0 2013 ); 2014 2015 $postvault = $vaultfactory->get_post_vault(); 2016 2017 $builderfactory = mod_forum\local\container::get_builder_factory(); 2018 $postbuilder = $builderfactory->get_exported_posts_builder(); 2019 2020 $builtdiscussions = []; 2021 foreach ($discussionsummaries as $discussionsummary) { 2022 $discussion = $discussionsummary->get_discussion(); 2023 if (!$capabilitymanager->can_view_discussion($USER, $discussion)) { 2024 continue; 2025 } 2026 $posts = $postvault->get_posts_in_discussion_for_user_id( 2027 $discussion->get_id(), 2028 $user->id, 2029 $capabilitymanager->can_view_any_private_reply($USER), 2030 "{$sortby} {$sortdirection}" 2031 ); 2032 if (empty($posts)) { 2033 continue; 2034 } 2035 2036 $parentids = array_filter(array_map(function($post) { 2037 return $post->has_parent() ? $post->get_parent_id() : null; 2038 }, $posts)); 2039 2040 $parentposts = []; 2041 if ($parentids) { 2042 $parentposts = $postbuilder->build( 2043 $USER, 2044 [$forum], 2045 [$discussion], 2046 $postvault->get_from_ids(array_values($parentids)) 2047 ); 2048 } 2049 2050 $discussionauthor = $discussionsummary->get_first_post_author(); 2051 $firstpost = $discussionsummary->get_first_post(); 2052 2053 $builtdiscussions[] = [ 2054 'name' => $discussion->get_name(), 2055 'id' => $discussion->get_id(), 2056 'timecreated' => $firstpost->get_time_created(), 2057 'authorfullname' => $discussionauthor->get_full_name(), 2058 'posts' => [ 2059 'userposts' => $postbuilder->build($USER, [$forum], [$discussion], $posts), 2060 'parentposts' => $parentposts, 2061 ], 2062 ]; 2063 } 2064 2065 return [ 2066 'discussions' => $builtdiscussions, 2067 'warnings' => $warnings, 2068 ]; 2069 } 2070 2071 /** 2072 * Describe the post parameters. 2073 * 2074 * @return external_function_parameters 2075 */ 2076 public static function get_discussion_posts_by_userid_parameters() { 2077 return new external_function_parameters ([ 2078 'userid' => new external_value( 2079 PARAM_INT, 'The ID of the user of whom to fetch posts.', VALUE_REQUIRED), 2080 'cmid' => new external_value( 2081 PARAM_INT, 'The ID of the module of which to fetch items.', VALUE_REQUIRED), 2082 'sortby' => new external_value( 2083 PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'), 2084 'sortdirection' => new external_value( 2085 PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC') 2086 ]); 2087 } 2088 2089 /** 2090 * Describe the post return format. 2091 * 2092 * @return external_single_structure 2093 */ 2094 public static function get_discussion_posts_by_userid_returns() { 2095 return new external_single_structure([ 2096 'discussions' => new external_multiple_structure( 2097 new external_single_structure([ 2098 'name' => new external_value(PARAM_RAW, 'Name of the discussion'), 2099 'id' => new external_value(PARAM_INT, 'ID of the discussion'), 2100 'timecreated' => new external_value(PARAM_INT, 'Timestamp of the discussion start'), 2101 'authorfullname' => new external_value(PARAM_RAW, 'Full name of the user that started the discussion'), 2102 'posts' => new external_single_structure([ 2103 'userposts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()), 2104 'parentposts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()), 2105 ]), 2106 ])), 2107 'warnings' => new external_warnings(), 2108 ]); 2109 } 2110 2111 /** 2112 * Returns description of method parameters 2113 * 2114 * @return external_function_parameters 2115 * @since Moodle 3.8 2116 */ 2117 public static function get_discussion_post_parameters() { 2118 return new external_function_parameters( 2119 array( 2120 'postid' => new external_value(PARAM_INT, 'Post to fetch.'), 2121 ) 2122 ); 2123 } 2124 2125 /** 2126 * Get a particular discussion post. 2127 * 2128 * @param int $postid post to fetch 2129 * @return array of post and warnings (if any) 2130 * @since Moodle 3.8 2131 * @throws moodle_exception 2132 */ 2133 public static function get_discussion_post($postid) { 2134 global $USER, $CFG; 2135 2136 $params = self::validate_parameters(self::get_discussion_post_parameters(), 2137 array( 2138 'postid' => $postid, 2139 )); 2140 $warnings = array(); 2141 $vaultfactory = mod_forum\local\container::get_vault_factory(); 2142 $forumvault = $vaultfactory->get_forum_vault(); 2143 $discussionvault = $vaultfactory->get_discussion_vault(); 2144 $postvault = $vaultfactory->get_post_vault(); 2145 2146 $postentity = $postvault->get_from_id($params['postid']); 2147 if (empty($postentity)) { 2148 throw new moodle_exception('invalidpostid', 'forum'); 2149 } 2150 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id()); 2151 if (empty($discussionentity)) { 2152 throw new moodle_exception('notpartofdiscussion', 'forum'); 2153 } 2154 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id()); 2155 if (empty($forumentity)) { 2156 throw new moodle_exception('invalidforumid', 'forum'); 2157 } 2158 self::validate_context($forumentity->get_context()); 2159 2160 $managerfactory = mod_forum\local\container::get_manager_factory(); 2161 $capabilitymanager = $managerfactory->get_capability_manager($forumentity); 2162 2163 if (!$capabilitymanager->can_view_post($USER, $discussionentity, $postentity)) { 2164 throw new moodle_exception('noviewdiscussionspermission', 'forum'); 2165 } 2166 2167 $builderfactory = mod_forum\local\container::get_builder_factory(); 2168 $postbuilder = $builderfactory->get_exported_posts_builder(); 2169 $posts = $postbuilder->build($USER, [$forumentity], [$discussionentity], [$postentity]); 2170 $post = empty($posts) ? array() : reset($posts); 2171 2172 $result = array(); 2173 $result['post'] = $post; 2174 $result['warnings'] = $warnings; 2175 return $result; 2176 } 2177 2178 /** 2179 * Returns description of method result value 2180 * 2181 * @return \core_external\external_description 2182 * @since Moodle 3.8 2183 */ 2184 public static function get_discussion_post_returns() { 2185 return new external_single_structure( 2186 array( 2187 'post' => \mod_forum\local\exporters\post::get_read_structure(), 2188 'warnings' => new external_warnings(), 2189 ) 2190 ); 2191 } 2192 2193 /** 2194 * Returns description of method parameters 2195 * 2196 * @return external_function_parameters 2197 * @since Moodle 3.8 2198 */ 2199 public static function prepare_draft_area_for_post_parameters() { 2200 return new external_function_parameters( 2201 array( 2202 'postid' => new external_value(PARAM_INT, 'Post to prepare the draft area for.'), 2203 'area' => new external_value(PARAM_ALPHA, 'Area to prepare: attachment or post.'), 2204 'draftitemid' => new external_value(PARAM_INT, 'The draft item id to use. 0 to generate one.', 2205 VALUE_DEFAULT, 0), 2206 'filestokeep' => new external_multiple_structure( 2207 new external_single_structure( 2208 array( 2209 'filename' => new external_value(PARAM_FILE, 'File name.'), 2210 'filepath' => new external_value(PARAM_PATH, 'File path.'), 2211 ) 2212 ), 'Only keep these files in the draft file area. Empty for keeping all.', VALUE_DEFAULT, [] 2213 ), 2214 ) 2215 ); 2216 } 2217 2218 /** 2219 * Prepares a draft area for editing a post. 2220 * 2221 * @param int $postid post to prepare the draft area for 2222 * @param string $area area to prepare attachment or post 2223 * @param int $draftitemid the draft item id to use. 0 to generate a new one. 2224 * @param array $filestokeep only keep these files in the draft file area. Empty for keeping all. 2225 * @return array of files in the area, the area options and the draft item id 2226 * @since Moodle 3.8 2227 * @throws moodle_exception 2228 */ 2229 public static function prepare_draft_area_for_post($postid, $area, $draftitemid = 0, $filestokeep = []) { 2230 global $USER; 2231 2232 $params = self::validate_parameters( 2233 self::prepare_draft_area_for_post_parameters(), 2234 array( 2235 'postid' => $postid, 2236 'area' => $area, 2237 'draftitemid' => $draftitemid, 2238 'filestokeep' => $filestokeep, 2239 ) 2240 ); 2241 $directionallowedvalues = ['ASC', 'DESC']; 2242 2243 $allowedareas = ['attachment', 'post']; 2244 if (!in_array($params['area'], $allowedareas)) { 2245 throw new invalid_parameter_exception('Invalid value for area parameter 2246 (value: ' . $params['area'] . '),' . 'allowed values are: ' . implode(', ', $allowedareas)); 2247 } 2248 2249 $warnings = array(); 2250 $vaultfactory = mod_forum\local\container::get_vault_factory(); 2251 $forumvault = $vaultfactory->get_forum_vault(); 2252 $discussionvault = $vaultfactory->get_discussion_vault(); 2253 $postvault = $vaultfactory->get_post_vault(); 2254 2255 $postentity = $postvault->get_from_id($params['postid']); 2256 if (empty($postentity)) { 2257 throw new moodle_exception('invalidpostid', 'forum'); 2258 } 2259 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id()); 2260 if (empty($discussionentity)) { 2261 throw new moodle_exception('notpartofdiscussion', 'forum'); 2262 } 2263 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id()); 2264 if (empty($forumentity)) { 2265 throw new moodle_exception('invalidforumid', 'forum'); 2266 } 2267 2268 $context = $forumentity->get_context(); 2269 self::validate_context($context); 2270 2271 $managerfactory = mod_forum\local\container::get_manager_factory(); 2272 $capabilitymanager = $managerfactory->get_capability_manager($forumentity); 2273 2274 if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) { 2275 throw new moodle_exception('noviewdiscussionspermission', 'forum'); 2276 } 2277 2278 if ($params['area'] == 'attachment') { 2279 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory(); 2280 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper(); 2281 $forum = $forumdatamapper->to_legacy_object($forumentity); 2282 2283 $areaoptions = mod_forum_post_form::attachment_options($forum); 2284 $messagetext = null; 2285 } else { 2286 $areaoptions = mod_forum_post_form::editor_options($context, $postentity->get_id()); 2287 $messagetext = $postentity->get_message(); 2288 } 2289 2290 $draftitemid = empty($params['draftitemid']) ? 0 : $params['draftitemid']; 2291 $messagetext = file_prepare_draft_area($draftitemid, $context->id, 'mod_forum', $params['area'], 2292 $postentity->get_id(), $areaoptions, $messagetext); 2293 2294 // Just get a structure compatible with external API. 2295 array_walk($areaoptions, function(&$item, $key) { 2296 $item = ['name' => $key, 'value' => $item]; 2297 }); 2298 2299 // Do we need to keep only the given files? 2300 $usercontext = context_user::instance($USER->id); 2301 if (!empty($params['filestokeep'])) { 2302 $fs = get_file_storage(); 2303 2304 if ($areafiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid)) { 2305 $filestokeep = []; 2306 foreach ($params['filestokeep'] as $ftokeep) { 2307 $filestokeep[$ftokeep['filepath']][$ftokeep['filename']] = $ftokeep; 2308 } 2309 2310 foreach ($areafiles as $file) { 2311 if ($file->is_directory()) { 2312 continue; 2313 } 2314 if (!isset($filestokeep[$file->get_filepath()][$file->get_filename()])) { 2315 $file->delete(); // Not in the list to be kept. 2316 } 2317 } 2318 } 2319 } 2320 2321 $result = array( 2322 'draftitemid' => $draftitemid, 2323 'files' => external_util::get_area_files($usercontext->id, 'user', 'draft', 2324 $draftitemid), 2325 'areaoptions' => $areaoptions, 2326 'messagetext' => $messagetext, 2327 'warnings' => $warnings, 2328 ); 2329 return $result; 2330 } 2331 2332 /** 2333 * Returns description of method result value 2334 * 2335 * @return \core_external\external_description 2336 * @since Moodle 3.8 2337 */ 2338 public static function prepare_draft_area_for_post_returns() { 2339 return new external_single_structure( 2340 array( 2341 'draftitemid' => new external_value(PARAM_INT, 'Draft item id for the file area.'), 2342 'files' => new external_files('Draft area files.', VALUE_OPTIONAL), 2343 'areaoptions' => new external_multiple_structure( 2344 new external_single_structure( 2345 array( 2346 'name' => new external_value(PARAM_RAW, 'Name of option.'), 2347 'value' => new external_value(PARAM_RAW, 'Value of option.'), 2348 ) 2349 ), 'Draft file area options.' 2350 ), 2351 'messagetext' => new external_value(PARAM_RAW, 'Message text with URLs rewritten.'), 2352 'warnings' => new external_warnings(), 2353 ) 2354 ); 2355 } 2356 2357 /** 2358 * Returns description of method parameters 2359 * 2360 * @return external_function_parameters 2361 * @since Moodle 3.8 2362 */ 2363 public static function update_discussion_post_parameters() { 2364 return new external_function_parameters( 2365 [ 2366 'postid' => new external_value(PARAM_INT, 'Post to be updated. It can be a discussion topic post.'), 2367 'subject' => new external_value(PARAM_TEXT, 'Updated post subject', VALUE_DEFAULT, ''), 2368 'message' => new external_value(PARAM_RAW, 'Updated post message (HTML assumed if messageformat is not provided)', 2369 VALUE_DEFAULT, ''), 2370 'messageformat' => new external_format_value('message', VALUE_DEFAULT), 2371 'options' => new external_multiple_structure ( 2372 new external_single_structure( 2373 [ 2374 'name' => new external_value( 2375 PARAM_ALPHANUM, 2376 'The allowed keys (value format) are: 2377 pinned (bool); (only for discussions) whether to pin this discussion or not 2378 discussionsubscribe (bool); whether to subscribe to the post or not 2379 inlineattachmentsid (int); the draft file area id for inline attachments in the text 2380 attachmentsid (int); the draft file area id for attachments' 2381 ), 2382 'value' => new external_value(PARAM_RAW, 'The value of the option.') 2383 ] 2384 ), 2385 'Configuration options for the post.', 2386 VALUE_DEFAULT, 2387 [] 2388 ), 2389 ] 2390 ); 2391 } 2392 2393 /** 2394 * Updates a post or a discussion post topic. 2395 * 2396 * @param int $postid post to be updated, it can be a discussion topic post. 2397 * @param string $subject updated post subject 2398 * @param string $message updated post message (HTML assumed if messageformat is not provided) 2399 * @param int $messageformat The format of the message, defaults to FORMAT_HTML 2400 * @param array $options different configuration options for the post to be updated. 2401 * @return array of warnings and the status (true if the post/discussion was deleted) 2402 * @since Moodle 3.8 2403 * @throws moodle_exception 2404 * @todo support more options: timed posts, groups change and tags. 2405 */ 2406 public static function update_discussion_post($postid, $subject = '', $message = '', $messageformat = FORMAT_HTML, 2407 $options = []) { 2408 global $CFG, $USER; 2409 require_once($CFG->dirroot . "/mod/forum/lib.php"); 2410 2411 $params = self::validate_parameters(self::add_discussion_post_parameters(), 2412 [ 2413 'postid' => $postid, 2414 'subject' => $subject, 2415 'message' => $message, 2416 'options' => $options, 2417 'messageformat' => $messageformat, 2418 ] 2419 ); 2420 $warnings = []; 2421 2422 // Validate options. 2423 $options = []; 2424 foreach ($params['options'] as $option) { 2425 $name = trim($option['name']); 2426 switch ($name) { 2427 case 'pinned': 2428 $value = clean_param($option['value'], PARAM_BOOL); 2429 break; 2430 case 'discussionsubscribe': 2431 $value = clean_param($option['value'], PARAM_BOOL); 2432 break; 2433 case 'inlineattachmentsid': 2434 $value = clean_param($option['value'], PARAM_INT); 2435 break; 2436 case 'attachmentsid': 2437 $value = clean_param($option['value'], PARAM_INT); 2438 break; 2439 default: 2440 throw new moodle_exception('errorinvalidparam', 'webservice', '', $name); 2441 } 2442 $options[$name] = $value; 2443 } 2444 2445 $managerfactory = mod_forum\local\container::get_manager_factory(); 2446 $vaultfactory = mod_forum\local\container::get_vault_factory(); 2447 $forumvault = $vaultfactory->get_forum_vault(); 2448 $discussionvault = $vaultfactory->get_discussion_vault(); 2449 $postvault = $vaultfactory->get_post_vault(); 2450 $legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory(); 2451 $forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper(); 2452 $discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper(); 2453 $postdatamapper = $legacydatamapperfactory->get_post_data_mapper(); 2454 2455 $postentity = $postvault->get_from_id($params['postid']); 2456 if (empty($postentity)) { 2457 throw new moodle_exception('invalidpostid', 'forum'); 2458 } 2459 $discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id()); 2460 if (empty($discussionentity)) { 2461 throw new moodle_exception('notpartofdiscussion', 'forum'); 2462 } 2463 $forumentity = $forumvault->get_from_id($discussionentity->get_forum_id()); 2464 if (empty($forumentity)) { 2465 throw new moodle_exception('invalidforumid', 'forum'); 2466 } 2467 $forum = $forumdatamapper->to_legacy_object($forumentity); 2468 $capabilitymanager = $managerfactory->get_capability_manager($forumentity); 2469 2470 $modcontext = $forumentity->get_context(); 2471 self::validate_context($modcontext); 2472 2473 if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) { 2474 throw new moodle_exception('cannotupdatepost', 'forum'); 2475 } 2476 2477 // Get the original post. 2478 $updatepost = $postdatamapper->to_legacy_object($postentity); 2479 $updatepost->itemid = IGNORE_FILE_MERGE; 2480 $updatepost->attachments = IGNORE_FILE_MERGE; 2481 2482 // Prepare the post to be updated. 2483 if ($params['subject'] !== '') { 2484 $updatepost->subject = $params['subject']; 2485 } 2486 2487 if ($params['message'] !== '' && isset($params['messageformat'])) { 2488 $updatepost->message = $params['message']; 2489 $updatepost->messageformat = $params['messageformat']; 2490 $updatepost->messagetrust = trusttext_trusted($modcontext); 2491 // Clean message text. 2492 $updatepost = trusttext_pre_edit($updatepost, 'message', $modcontext); 2493 } 2494 2495 if (isset($options['discussionsubscribe'])) { 2496 // No need to validate anything here, forum_post_subscription will do. 2497 $updatepost->discussionsubscribe = $options['discussionsubscribe']; 2498 } 2499 2500 // When editing first post/discussion. 2501 if (!$postentity->has_parent()) { 2502 // Defaults for discussion topic posts. 2503 $updatepost->name = $discussionentity->get_name(); 2504 $updatepost->timestart = $discussionentity->get_time_start(); 2505 $updatepost->timeend = $discussionentity->get_time_end(); 2506 2507 if (isset($options['pinned'])) { 2508 if ($capabilitymanager->can_pin_discussions($USER)) { 2509 // Can change pinned if we have capability. 2510 $updatepost->pinned = !empty($options['pinned']) ? FORUM_DISCUSSION_PINNED : FORUM_DISCUSSION_UNPINNED; 2511 } 2512 } 2513 } 2514 2515 if (isset($options['inlineattachmentsid'])) { 2516 $updatepost->itemid = $options['inlineattachmentsid']; 2517 } 2518 2519 if (isset($options['attachmentsid']) && forum_can_create_attachment($forum, $modcontext)) { 2520 $updatepost->attachments = $options['attachmentsid']; 2521 } 2522 2523 // Update the post. 2524 $fakemform = $updatepost->id; 2525 if (forum_update_post($updatepost, $fakemform)) { 2526 $discussion = $discussiondatamapper->to_legacy_object($discussionentity); 2527 2528 forum_trigger_post_updated_event($updatepost, $discussion, $modcontext, $forum); 2529 2530 forum_post_subscription( 2531 $updatepost, 2532 $forum, 2533 $discussion 2534 ); 2535 $status = true; 2536 } else { 2537 $status = false; 2538 } 2539 2540 return [ 2541 'status' => $status, 2542 'warnings' => $warnings, 2543 ]; 2544 } 2545 2546 /** 2547 * Returns description of method result value 2548 * 2549 * @return \core_external\external_description 2550 * @since Moodle 3.8 2551 */ 2552 public static function update_discussion_post_returns() { 2553 return new external_single_structure( 2554 [ 2555 'status' => new external_value(PARAM_BOOL, 'True if the post/discussion was updated, false otherwise.'), 2556 'warnings' => new external_warnings() 2557 ] 2558 ); 2559 } 2560 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body