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