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