See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Post exporter class. 19 * 20 * @package mod_forum 21 * @copyright 2019 Ryan Wyllie <ryan@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace mod_forum\local\exporters; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 use mod_forum\local\entities\post as post_entity; 30 use mod_forum\local\entities\discussion as discussion_entity; 31 use mod_forum\local\exporters\author as author_exporter; 32 use mod_forum\local\factories\exporter as exporter_factory; 33 use core\external\exporter; 34 use core_files\external\stored_file_exporter; 35 use context; 36 use core_tag_tag; 37 use renderer_base; 38 use stdClass; 39 40 require_once($CFG->dirroot . '/mod/forum/lib.php'); 41 42 /** 43 * Post exporter class. 44 * 45 * @copyright 2019 Ryan Wyllie <ryan@moodle.com> 46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 47 */ 48 class post extends exporter { 49 /** @var post_entity $post The post to export */ 50 private $post; 51 52 /** 53 * Constructor. 54 * 55 * @param post_entity $post The post to export 56 * @param array $related List of related data 57 */ 58 public function __construct(post_entity $post, array $related = []) { 59 $this->post = $post; 60 return parent::__construct([], $related); 61 } 62 63 /** 64 * Return the list of additional properties. 65 * 66 * @return array 67 */ 68 protected static function define_other_properties() { 69 $attachmentdefinition = stored_file_exporter::read_properties_definition(); 70 $attachmentdefinition['urls'] = [ 71 'type' => [ 72 'export' => [ 73 'type' => PARAM_URL, 74 'description' => 'The URL used to export the attachment', 75 'optional' => true, 76 'default' => null, 77 'null' => NULL_ALLOWED 78 ] 79 ] 80 ]; 81 $attachmentdefinition['html'] = [ 82 'type' => [ 83 'plagiarism' => [ 84 'type' => PARAM_RAW, 85 'description' => 'The HTML source for the Plagiarism Response', 86 'optional' => true, 87 'default' => null, 88 'null' => NULL_ALLOWED 89 ], 90 ] 91 ]; 92 93 return [ 94 'id' => ['type' => PARAM_INT], 95 'subject' => ['type' => PARAM_TEXT], 96 'replysubject' => ['type' => PARAM_TEXT], 97 'message' => ['type' => PARAM_RAW], 98 'messageformat' => ['type' => PARAM_INT], 99 'author' => ['type' => author_exporter::read_properties_definition()], 100 'discussionid' => ['type' => PARAM_INT], 101 'hasparent' => ['type' => PARAM_BOOL], 102 'parentid' => [ 103 'type' => PARAM_INT, 104 'optional' => true, 105 'default' => null, 106 'null' => NULL_ALLOWED 107 ], 108 'timecreated' => [ 109 'type' => PARAM_INT, 110 'default' => null, 111 'null' => NULL_ALLOWED 112 ], 113 'unread' => [ 114 'type' => PARAM_BOOL, 115 'optional' => true, 116 'default' => null, 117 'null' => NULL_ALLOWED 118 ], 119 'isdeleted' => ['type' => PARAM_BOOL], 120 'isprivatereply' => ['type' => PARAM_BOOL], 121 'haswordcount' => ['type' => PARAM_BOOL], 122 'wordcount' => [ 123 'type' => PARAM_INT, 124 'optional' => true, 125 'default' => null, 126 'null' => NULL_ALLOWED 127 ], 128 'charcount' => [ 129 'type' => PARAM_INT, 130 'optional' => true, 131 'default' => null, 132 'null' => NULL_ALLOWED 133 ], 134 'capabilities' => [ 135 'type' => [ 136 'view' => [ 137 'type' => PARAM_BOOL, 138 'null' => NULL_ALLOWED, 139 'description' => 'Whether the user can view the post', 140 ], 141 'edit' => [ 142 'type' => PARAM_BOOL, 143 'null' => NULL_ALLOWED, 144 'description' => 'Whether the user can edit the post', 145 ], 146 'delete' => [ 147 'type' => PARAM_BOOL, 148 'null' => NULL_ALLOWED, 149 'description' => 'Whether the user can delete the post', 150 ], 151 'split' => [ 152 'type' => PARAM_BOOL, 153 'null' => NULL_ALLOWED, 154 'description' => 'Whether the user can split the post', 155 ], 156 'reply' => [ 157 'type' => PARAM_BOOL, 158 'null' => NULL_ALLOWED, 159 'description' => 'Whether the user can reply to the post', 160 ], 161 'selfenrol' => [ 162 'type' => PARAM_BOOL, 163 'null' => NULL_ALLOWED, 164 'description' => 'Whether the user can self enrol into the course', 165 ], 166 'export' => [ 167 'type' => PARAM_BOOL, 168 'null' => NULL_ALLOWED, 169 'description' => 'Whether the user can export the post', 170 ], 171 'controlreadstatus' => [ 172 'type' => PARAM_BOOL, 173 'null' => NULL_ALLOWED, 174 'description' => 'Whether the user can control the read status of the post', 175 ], 176 'canreplyprivately' => [ 177 'type' => PARAM_BOOL, 178 'null' => NULL_ALLOWED, 179 'description' => 'Whether the user can post a private reply', 180 ] 181 ] 182 ], 183 'urls' => [ 184 'optional' => true, 185 'default' => null, 186 'null' => NULL_ALLOWED, 187 'type' => [ 188 'view' => [ 189 'description' => 'The URL used to view the post', 190 'type' => PARAM_URL, 191 'optional' => true, 192 'default' => null, 193 'null' => NULL_ALLOWED 194 ], 195 'viewisolated' => [ 196 'description' => 'The URL used to view the post in isolation', 197 'type' => PARAM_URL, 198 'optional' => true, 199 'default' => null, 200 'null' => NULL_ALLOWED 201 ], 202 'viewparent' => [ 203 'description' => 'The URL used to view the parent of the post', 204 'type' => PARAM_URL, 205 'optional' => true, 206 'default' => null, 207 'null' => NULL_ALLOWED 208 ], 209 'edit' => [ 210 'description' => 'The URL used to edit the post', 211 'type' => PARAM_URL, 212 'optional' => true, 213 'default' => null, 214 'null' => NULL_ALLOWED 215 ], 216 'delete' => [ 217 'description' => 'The URL used to delete the post', 218 'type' => PARAM_URL, 219 'optional' => true, 220 'default' => null, 221 'null' => NULL_ALLOWED 222 ], 223 'split' => [ 224 'description' => 'The URL used to split the discussion ' . 225 'with the selected post being the first post in the new discussion', 226 'type' => PARAM_URL, 227 'optional' => true, 228 'default' => null, 229 'null' => NULL_ALLOWED 230 ], 231 'reply' => [ 232 'description' => 'The URL used to reply to the post', 233 'type' => PARAM_URL, 234 'optional' => true, 235 'default' => null, 236 'null' => NULL_ALLOWED 237 ], 238 'export' => [ 239 'description' => 'The URL used to export the post', 240 'type' => PARAM_URL, 241 'optional' => true, 242 'default' => null, 243 'null' => NULL_ALLOWED 244 ], 245 'markasread' => [ 246 'description' => 'The URL used to mark the post as read', 247 'type' => PARAM_URL, 248 'optional' => true, 249 'default' => null, 250 'null' => NULL_ALLOWED 251 ], 252 'markasunread' => [ 253 'description' => 'The URL used to mark the post as unread', 254 'type' => PARAM_URL, 255 'optional' => true, 256 'default' => null, 257 'null' => NULL_ALLOWED 258 ], 259 'discuss' => [ 260 'type' => PARAM_URL, 261 'optional' => true, 262 'default' => null, 263 'null' => NULL_ALLOWED 264 ] 265 ] 266 ], 267 'attachments' => [ 268 'multiple' => true, 269 'type' => $attachmentdefinition 270 ], 271 'tags' => [ 272 'optional' => true, 273 'default' => null, 274 'null' => NULL_ALLOWED, 275 'multiple' => true, 276 'type' => [ 277 'id' => [ 278 'type' => PARAM_INT, 279 'description' => 'The ID of the Tag', 280 'null' => NULL_NOT_ALLOWED, 281 ], 282 'tagid' => [ 283 'type' => PARAM_INT, 284 'description' => 'The tagid', 285 'null' => NULL_NOT_ALLOWED, 286 ], 287 'isstandard' => [ 288 'type' => PARAM_BOOL, 289 'description' => 'Whether this is a standard tag', 290 'null' => NULL_NOT_ALLOWED, 291 ], 292 'displayname' => [ 293 'type' => PARAM_TEXT, 294 'description' => 'The display name of the tag', 295 'null' => NULL_NOT_ALLOWED, 296 ], 297 'flag' => [ 298 'type' => PARAM_BOOL, 299 'description' => 'Wehther this tag is flagged', 300 'null' => NULL_NOT_ALLOWED, 301 ], 302 'urls' => [ 303 'description' => 'URLs associated with the tag', 304 'null' => NULL_NOT_ALLOWED, 305 'type' => [ 306 'view' => [ 307 'type' => PARAM_URL, 308 'description' => 'The URL to view the tag', 309 'null' => NULL_NOT_ALLOWED, 310 ], 311 ] 312 ] 313 ] 314 ], 315 'html' => [ 316 'optional' => true, 317 'default' => null, 318 'null' => NULL_ALLOWED, 319 'type' => [ 320 'rating' => [ 321 'optional' => true, 322 'default' => null, 323 'null' => NULL_ALLOWED, 324 'type' => PARAM_RAW, 325 'description' => 'The HTML source to rate the post', 326 ], 327 'taglist' => [ 328 'optional' => true, 329 'default' => null, 330 'null' => NULL_ALLOWED, 331 'type' => PARAM_RAW, 332 'description' => 'The HTML source to view the list of tags', 333 ], 334 'authorsubheading' => [ 335 'optional' => true, 336 'default' => null, 337 'null' => NULL_ALLOWED, 338 'type' => PARAM_RAW, 339 'description' => 'The HTML source to view the author details', 340 ], 341 ] 342 ] 343 ]; 344 } 345 346 /** 347 * Get the additional values to inject while exporting. 348 * 349 * @param renderer_base $output The renderer. 350 * @return array Keys are the property names, values are their values. 351 */ 352 protected function get_other_values(renderer_base $output) { 353 $post = $this->post; 354 $authorgroups = $this->related['authorgroups']; 355 $forum = $this->related['forum']; 356 $discussion = $this->related['discussion']; 357 $author = $this->related['author']; 358 $authorcontextid = $this->related['authorcontextid']; 359 $user = $this->related['user']; 360 $readreceiptcollection = $this->related['readreceiptcollection']; 361 $rating = $this->related['rating']; 362 $tags = $this->related['tags']; 363 $attachments = $this->related['attachments']; 364 $includehtml = $this->related['includehtml']; 365 $isdeleted = $post->is_deleted(); 366 $isprivatereply = $post->is_private_reply(); 367 $hasrating = $rating != null; 368 $hastags = !empty($tags); 369 $discussionid = $post->get_discussion_id(); 370 $parentid = $post->get_parent_id(); 371 372 $capabilitymanager = $this->related['capabilitymanager']; 373 $canview = $capabilitymanager->can_view_post($user, $discussion, $post); 374 $canedit = $capabilitymanager->can_edit_post($user, $discussion, $post); 375 $candelete = $capabilitymanager->can_delete_post($user, $discussion, $post); 376 $cansplit = $capabilitymanager->can_split_post($user, $discussion, $post); 377 $canreply = $capabilitymanager->can_reply_to_post($user, $discussion, $post); 378 $canexport = $capabilitymanager->can_export_post($user, $post); 379 $cancontrolreadstatus = $capabilitymanager->can_manually_control_post_read_status($user); 380 $canselfenrol = $capabilitymanager->can_self_enrol($user); 381 $canreplyprivately = $capabilitymanager->can_reply_privately_to_post($user, $post); 382 383 $urlfactory = $this->related['urlfactory']; 384 $viewurl = $canview ? $urlfactory->get_view_post_url_from_post($post) : null; 385 $viewisolatedurl = $canview ? $urlfactory->get_view_isolated_post_url_from_post($post) : null; 386 $viewparenturl = $post->has_parent() ? $urlfactory->get_view_post_url_from_post_id($discussionid, $parentid) : null; 387 $editurl = $canedit ? $urlfactory->get_edit_post_url_from_post($forum, $post) : null; 388 $deleteurl = $candelete ? $urlfactory->get_delete_post_url_from_post($post) : null; 389 $spliturl = $cansplit ? $urlfactory->get_split_discussion_at_post_url_from_post($post) : null; 390 $replyurl = $canreply || $canselfenrol ? $urlfactory->get_reply_to_post_url_from_post($post) : null; 391 $exporturl = $canexport ? $urlfactory->get_export_post_url_from_post($post) : null; 392 $markasreadurl = $cancontrolreadstatus ? $urlfactory->get_mark_post_as_read_url_from_post($post) : null; 393 $markasunreadurl = $cancontrolreadstatus ? $urlfactory->get_mark_post_as_unread_url_from_post($post) : null; 394 $discussurl = $canview ? $urlfactory->get_discussion_view_url_from_post($post) : null; 395 396 $authorexporter = new author_exporter( 397 $author, 398 $authorcontextid, 399 $authorgroups, 400 $canview, 401 $this->related 402 ); 403 $exportedauthor = $authorexporter->export($output); 404 // Only bother loading the content if the user can see it. 405 $loadcontent = $canview && !$isdeleted; 406 $exportattachments = $loadcontent && !empty($attachments); 407 408 if ($loadcontent) { 409 $subject = $post->get_subject(); 410 $timecreated = $this->get_start_time($discussion, $post); 411 $message = $this->get_message($post); 412 } else { 413 $subject = $isdeleted ? get_string('forumsubjectdeleted', 'forum') : get_string('forumsubjecthidden', 'forum'); 414 $message = $isdeleted ? get_string('forumbodydeleted', 'forum') : get_string('forumbodyhidden', 'forum'); 415 $timecreated = null; 416 } 417 418 $replysubject = $subject; 419 $strre = get_string('re', 'forum'); 420 if (!(substr($replysubject, 0, strlen($strre)) == $strre)) { 421 $replysubject = "{$strre} {$replysubject}"; 422 } 423 424 $showwordcount = $forum->should_display_word_count(); 425 if ($showwordcount) { 426 $wordcount = $post->get_wordcount() ?? count_words($message); 427 $charcount = $post->get_charcount() ?? count_letters($message); 428 } else { 429 $wordcount = null; 430 $charcount = null; 431 } 432 433 return [ 434 'id' => $post->get_id(), 435 'subject' => $subject, 436 'replysubject' => $replysubject, 437 'message' => $message, 438 'messageformat' => $post->get_message_format(), 439 'author' => $exportedauthor, 440 'discussionid' => $post->get_discussion_id(), 441 'hasparent' => $post->has_parent(), 442 'parentid' => $post->has_parent() ? $post->get_parent_id() : null, 443 'timecreated' => $timecreated, 444 'unread' => ($loadcontent && $readreceiptcollection) ? !$readreceiptcollection->has_user_read_post($user, $post) : null, 445 'isdeleted' => $isdeleted, 446 'isprivatereply' => $isprivatereply, 447 'haswordcount' => $showwordcount, 448 'wordcount' => $wordcount, 449 'charcount' => $charcount, 450 'capabilities' => [ 451 'view' => $canview, 452 'edit' => $canedit, 453 'delete' => $candelete, 454 'split' => $cansplit, 455 'reply' => $canreply, 456 'export' => $canexport, 457 'controlreadstatus' => $cancontrolreadstatus, 458 'canreplyprivately' => $canreplyprivately, 459 'selfenrol' => $canselfenrol 460 ], 461 'urls' => [ 462 'view' => $viewurl ? $viewurl->out(false) : null, 463 'viewisolated' => $viewisolatedurl ? $viewisolatedurl->out(false) : null, 464 'viewparent' => $viewparenturl ? $viewparenturl->out(false) : null, 465 'edit' => $editurl ? $editurl->out(false) : null, 466 'delete' => $deleteurl ? $deleteurl->out(false) : null, 467 'split' => $spliturl ? $spliturl->out(false) : null, 468 'reply' => $replyurl ? $replyurl->out(false) : null, 469 'export' => $exporturl && $exporturl ? $exporturl->out(false) : null, 470 'markasread' => $markasreadurl ? $markasreadurl->out(false) : null, 471 'markasunread' => $markasunreadurl ? $markasunreadurl->out(false) : null, 472 'discuss' => $discussurl ? $discussurl->out(false) : null, 473 ], 474 'attachments' => ($exportattachments) ? $this->export_attachments($attachments, $post, $output, $canexport) : [], 475 'tags' => ($loadcontent && $hastags) ? $this->export_tags($tags) : [], 476 'html' => $includehtml ? [ 477 'rating' => ($loadcontent && $hasrating) ? $output->render($rating) : null, 478 'taglist' => ($loadcontent && $hastags) ? $output->tag_list($tags) : null, 479 'authorsubheading' => ($loadcontent) ? $this->get_author_subheading_html($exportedauthor, $timecreated) : null 480 ] : null 481 ]; 482 } 483 484 /** 485 * Returns a list of objects that are related. 486 * 487 * @return array 488 */ 489 protected static function define_related() { 490 return [ 491 'capabilitymanager' => 'mod_forum\local\managers\capability', 492 'readreceiptcollection' => 'mod_forum\local\entities\post_read_receipt_collection?', 493 'urlfactory' => 'mod_forum\local\factories\url', 494 'forum' => 'mod_forum\local\entities\forum', 495 'discussion' => 'mod_forum\local\entities\discussion', 496 'author' => 'mod_forum\local\entities\author', 497 'authorcontextid' => 'int?', 498 'user' => 'stdClass', 499 'context' => 'context', 500 'authorgroups' => 'stdClass[]', 501 'attachments' => '\stored_file[]?', 502 'tags' => '\core_tag_tag[]?', 503 'rating' => 'rating?', 504 'includehtml' => 'bool' 505 ]; 506 } 507 508 /** 509 * This method returns the parameters for the post's message to 510 * use with the function external_format_text(). 511 * 512 * @return array 513 */ 514 protected function get_format_parameters_for_message() { 515 return [ 516 'component' => 'mod_forum', 517 'filearea' => 'post', 518 'itemid' => $this->post->get_id(), 519 'options' => [ 520 'para' => false, 521 'trusted' => $this->post->is_message_trusted() 522 ] 523 ]; 524 } 525 526 /** 527 * Get the message text from a post. 528 * 529 * @param post_entity $post The post 530 * @return string 531 */ 532 private function get_message(post_entity $post) : string { 533 global $CFG; 534 535 $message = $post->get_message(); 536 537 if (!empty($CFG->enableplagiarism)) { 538 require_once($CFG->libdir . '/plagiarismlib.php'); 539 $forum = $this->related['forum']; 540 $message .= plagiarism_get_links([ 541 'userid' => $post->get_author_id(), 542 'content' => $message, 543 'cmid' => $forum->get_course_module_record()->id, 544 'course' => $forum->get_course_id(), 545 'forum' => $forum->get_id() 546 ]); 547 } 548 549 return $message; 550 } 551 552 /** 553 * Get the exported attachments for a post. 554 * 555 * @param stored_file[] $attachments The list of attachments for the post 556 * @param post_entity $post The post being exported 557 * @param renderer_base $output Renderer base 558 * @param bool $canexport If the user can export the post (relates to portfolios not exporters like this class) 559 * @return array 560 */ 561 private function export_attachments(array $attachments, post_entity $post, renderer_base $output, bool $canexport) : array { 562 global $CFG; 563 564 $urlfactory = $this->related['urlfactory']; 565 $enableplagiarism = $CFG->enableplagiarism; 566 $forum = $this->related['forum']; 567 $context = $this->related['context']; 568 569 if ($enableplagiarism) { 570 require_once($CFG->libdir . '/plagiarismlib.php' ); 571 } 572 573 return array_map(function($attachment) use ( 574 $output, 575 $enableplagiarism, 576 $canexport, 577 $context, 578 $forum, 579 $post, 580 $urlfactory 581 ) { 582 $exporter = new stored_file_exporter($attachment, ['context' => $context]); 583 $exportedattachment = $exporter->export($output); 584 $exporturl = $canexport ? $urlfactory->get_export_attachment_url_from_post_and_attachment($post, $attachment) : null; 585 586 if ($enableplagiarism) { 587 $plagiarismhtml = plagiarism_get_links([ 588 'userid' => $post->get_author_id(), 589 'file' => $attachment, 590 'cmid' => $forum->get_course_module_record()->id, 591 'course' => $forum->get_course_id(), 592 'forum' => $forum->get_id() 593 ]); 594 } else { 595 $plagiarismhtml = null; 596 } 597 598 $exportedattachment->urls = [ 599 'export' => $exporturl ? $exporturl->out(false) : null 600 ]; 601 $exportedattachment->html = [ 602 'plagiarism' => $plagiarismhtml 603 ]; 604 605 return $exportedattachment; 606 }, $attachments); 607 } 608 609 /** 610 * Export the list of tags. 611 * 612 * @param core_tag_tag[] $tags List of tags to export 613 * @return array 614 */ 615 private function export_tags(array $tags) : array { 616 $user = $this->related['user']; 617 $context = $this->related['context']; 618 $capabilitymanager = $this->related['capabilitymanager']; 619 $canmanagetags = $capabilitymanager->can_manage_tags($user); 620 621 return array_values(array_map(function($tag) use ($context, $canmanagetags) { 622 $viewurl = core_tag_tag::make_url($tag->tagcollid, $tag->rawname, 0, $context->id); 623 return [ 624 'id' => $tag->taginstanceid, 625 'tagid' => $tag->id, 626 'isstandard' => $tag->isstandard, 627 'displayname' => $tag->get_display_name(), 628 'flag' => $canmanagetags && !empty($tag->flag), 629 'urls' => [ 630 'view' => $viewurl->out(false) 631 ] 632 ]; 633 }, $tags)); 634 } 635 636 /** 637 * Get the HTML to display as a subheading in a post. 638 * 639 * @param stdClass $exportedauthor The exported author object 640 * @param int $timecreated The post time created timestamp if it's to be displayed 641 * @return string 642 */ 643 private function get_author_subheading_html(stdClass $exportedauthor, int $timecreated) : string { 644 $fullname = $exportedauthor->fullname; 645 $profileurl = $exportedauthor->urls['profile'] ?? null; 646 $name = $profileurl ? "<a href=\"{$profileurl}\">{$fullname}</a>" : $fullname; 647 $date = userdate_htmltime($timecreated, get_string('strftimedaydatetime', 'core_langconfig')); 648 return get_string('bynameondate', 'mod_forum', ['name' => $name, 'date' => $date]); 649 } 650 651 /** 652 * Get the start time for a post. 653 * 654 * @param discussion_entity $discussion entity 655 * @param post_entity $post entity 656 * @return int The start time (timestamp) for a post 657 */ 658 private function get_start_time(discussion_entity $discussion, post_entity $post) { 659 global $CFG; 660 661 $posttime = $post->get_time_created(); 662 $discussiontime = $discussion->get_time_start(); 663 if (!empty($CFG->forum_enabletimedposts) && ($discussiontime > $posttime)) { 664 return $discussiontime; 665 } 666 return $posttime; 667 } 668 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body