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