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