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