See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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 'timemodified' => [ 114 'type' => PARAM_INT, 115 'default' => null, 116 'null' => NULL_ALLOWED 117 ], 118 'unread' => [ 119 'type' => PARAM_BOOL, 120 'optional' => true, 121 'default' => null, 122 'null' => NULL_ALLOWED 123 ], 124 'isdeleted' => ['type' => PARAM_BOOL], 125 'isprivatereply' => ['type' => PARAM_BOOL], 126 'haswordcount' => ['type' => PARAM_BOOL], 127 'wordcount' => [ 128 'type' => PARAM_INT, 129 'optional' => true, 130 'default' => null, 131 'null' => NULL_ALLOWED 132 ], 133 'charcount' => [ 134 'type' => PARAM_INT, 135 'optional' => true, 136 'default' => null, 137 'null' => NULL_ALLOWED 138 ], 139 'capabilities' => [ 140 'type' => [ 141 'view' => [ 142 'type' => PARAM_BOOL, 143 'null' => NULL_ALLOWED, 144 'description' => 'Whether the user can view the post', 145 ], 146 'edit' => [ 147 'type' => PARAM_BOOL, 148 'null' => NULL_ALLOWED, 149 'description' => 'Whether the user can edit the post', 150 ], 151 'delete' => [ 152 'type' => PARAM_BOOL, 153 'null' => NULL_ALLOWED, 154 'description' => 'Whether the user can delete the post', 155 ], 156 'split' => [ 157 'type' => PARAM_BOOL, 158 'null' => NULL_ALLOWED, 159 'description' => 'Whether the user can split the post', 160 ], 161 'reply' => [ 162 'type' => PARAM_BOOL, 163 'null' => NULL_ALLOWED, 164 'description' => 'Whether the user can reply to the post', 165 ], 166 'selfenrol' => [ 167 'type' => PARAM_BOOL, 168 'null' => NULL_ALLOWED, 169 'description' => 'Whether the user can self enrol into the course', 170 ], 171 'export' => [ 172 'type' => PARAM_BOOL, 173 'null' => NULL_ALLOWED, 174 'description' => 'Whether the user can export the post', 175 ], 176 'controlreadstatus' => [ 177 'type' => PARAM_BOOL, 178 'null' => NULL_ALLOWED, 179 'description' => 'Whether the user can control the read status of the post', 180 ], 181 'canreplyprivately' => [ 182 'type' => PARAM_BOOL, 183 'null' => NULL_ALLOWED, 184 'description' => 'Whether the user can post a private reply', 185 ] 186 ] 187 ], 188 'urls' => [ 189 'optional' => true, 190 'default' => null, 191 'null' => NULL_ALLOWED, 192 'type' => [ 193 'view' => [ 194 'description' => 'The URL used to view the post', 195 'type' => PARAM_URL, 196 'optional' => true, 197 'default' => null, 198 'null' => NULL_ALLOWED 199 ], 200 'viewisolated' => [ 201 'description' => 'The URL used to view the post in isolation', 202 'type' => PARAM_URL, 203 'optional' => true, 204 'default' => null, 205 'null' => NULL_ALLOWED 206 ], 207 'viewparent' => [ 208 'description' => 'The URL used to view the parent of the post', 209 'type' => PARAM_URL, 210 'optional' => true, 211 'default' => null, 212 'null' => NULL_ALLOWED 213 ], 214 'edit' => [ 215 'description' => 'The URL used to edit the post', 216 'type' => PARAM_URL, 217 'optional' => true, 218 'default' => null, 219 'null' => NULL_ALLOWED 220 ], 221 'delete' => [ 222 'description' => 'The URL used to delete the post', 223 'type' => PARAM_URL, 224 'optional' => true, 225 'default' => null, 226 'null' => NULL_ALLOWED 227 ], 228 'split' => [ 229 'description' => 'The URL used to split the discussion ' . 230 'with the selected post being the first post in the new discussion', 231 'type' => PARAM_URL, 232 'optional' => true, 233 'default' => null, 234 'null' => NULL_ALLOWED 235 ], 236 'reply' => [ 237 'description' => 'The URL used to reply to the post', 238 'type' => PARAM_URL, 239 'optional' => true, 240 'default' => null, 241 'null' => NULL_ALLOWED 242 ], 243 'export' => [ 244 'description' => 'The URL used to export the post', 245 'type' => PARAM_URL, 246 'optional' => true, 247 'default' => null, 248 'null' => NULL_ALLOWED 249 ], 250 'markasread' => [ 251 'description' => 'The URL used to mark the post as read', 252 'type' => PARAM_URL, 253 'optional' => true, 254 'default' => null, 255 'null' => NULL_ALLOWED 256 ], 257 'markasunread' => [ 258 'description' => 'The URL used to mark the post as unread', 259 'type' => PARAM_URL, 260 'optional' => true, 261 'default' => null, 262 'null' => NULL_ALLOWED 263 ], 264 'discuss' => [ 265 'type' => PARAM_URL, 266 'optional' => true, 267 'default' => null, 268 'null' => NULL_ALLOWED 269 ] 270 ] 271 ], 272 'attachments' => [ 273 'multiple' => true, 274 'type' => $attachmentdefinition 275 ], 276 'messageinlinefiles' => [ 277 'optional' => true, 278 'multiple' => true, 279 'type' => stored_file_exporter::read_properties_definition(), 280 ], 281 'tags' => [ 282 'optional' => true, 283 'default' => null, 284 'null' => NULL_ALLOWED, 285 'multiple' => true, 286 'type' => [ 287 'id' => [ 288 'type' => PARAM_INT, 289 'description' => 'The ID of the Tag', 290 'null' => NULL_NOT_ALLOWED, 291 ], 292 'tagid' => [ 293 'type' => PARAM_INT, 294 'description' => 'The tagid', 295 'null' => NULL_NOT_ALLOWED, 296 ], 297 'isstandard' => [ 298 'type' => PARAM_BOOL, 299 'description' => 'Whether this is a standard tag', 300 'null' => NULL_NOT_ALLOWED, 301 ], 302 'displayname' => [ 303 'type' => PARAM_TEXT, 304 'description' => 'The display name of the tag', 305 'null' => NULL_NOT_ALLOWED, 306 ], 307 'flag' => [ 308 'type' => PARAM_BOOL, 309 'description' => 'Wehther this tag is flagged', 310 'null' => NULL_NOT_ALLOWED, 311 ], 312 'urls' => [ 313 'description' => 'URLs associated with the tag', 314 'null' => NULL_NOT_ALLOWED, 315 'type' => [ 316 'view' => [ 317 'type' => PARAM_URL, 318 'description' => 'The URL to view the tag', 319 'null' => NULL_NOT_ALLOWED, 320 ], 321 ] 322 ] 323 ] 324 ], 325 'html' => [ 326 'optional' => true, 327 'default' => null, 328 'null' => NULL_ALLOWED, 329 'type' => [ 330 'rating' => [ 331 'optional' => true, 332 'default' => null, 333 'null' => NULL_ALLOWED, 334 'type' => PARAM_RAW, 335 'description' => 'The HTML source to rate the post', 336 ], 337 'taglist' => [ 338 'optional' => true, 339 'default' => null, 340 'null' => NULL_ALLOWED, 341 'type' => PARAM_RAW, 342 'description' => 'The HTML source to view the list of tags', 343 ], 344 'authorsubheading' => [ 345 'optional' => true, 346 'default' => null, 347 'null' => NULL_ALLOWED, 348 'type' => PARAM_RAW, 349 'description' => 'The HTML source to view the author details', 350 ], 351 ] 352 ] 353 ]; 354 } 355 356 /** 357 * Get the additional values to inject while exporting. 358 * 359 * @param renderer_base $output The renderer. 360 * @return array Keys are the property names, values are their values. 361 */ 362 protected function get_other_values(renderer_base $output) { 363 $post = $this->post; 364 $authorgroups = $this->related['authorgroups']; 365 $forum = $this->related['forum']; 366 $discussion = $this->related['discussion']; 367 $author = $this->related['author']; 368 $authorcontextid = $this->related['authorcontextid']; 369 $user = $this->related['user']; 370 $readreceiptcollection = $this->related['readreceiptcollection']; 371 $rating = $this->related['rating']; 372 $tags = $this->related['tags']; 373 $attachments = $this->related['attachments']; 374 $inlineattachments = $this->related['messageinlinefiles']; 375 $includehtml = $this->related['includehtml']; 376 $isdeleted = $post->is_deleted(); 377 $isprivatereply = $post->is_private_reply(); 378 $hasrating = $rating != null; 379 $hastags = !empty($tags); 380 $discussionid = $post->get_discussion_id(); 381 $parentid = $post->get_parent_id(); 382 383 $capabilitymanager = $this->related['capabilitymanager']; 384 $canview = $capabilitymanager->can_view_post($user, $discussion, $post); 385 $canedit = $capabilitymanager->can_edit_post($user, $discussion, $post); 386 $candelete = $capabilitymanager->can_delete_post($user, $discussion, $post); 387 $cansplit = $capabilitymanager->can_split_post($user, $discussion, $post); 388 $canreply = $capabilitymanager->can_reply_to_post($user, $discussion, $post); 389 $canexport = $capabilitymanager->can_export_post($user, $post); 390 $cancontrolreadstatus = $capabilitymanager->can_manually_control_post_read_status($user); 391 $canselfenrol = $capabilitymanager->can_self_enrol($user); 392 $canreplyprivately = $capabilitymanager->can_reply_privately_to_post($user, $post); 393 394 $urlfactory = $this->related['urlfactory']; 395 $viewurl = $canview ? $urlfactory->get_view_post_url_from_post($post) : null; 396 $viewisolatedurl = $canview ? $urlfactory->get_view_isolated_post_url_from_post($post) : null; 397 $viewparenturl = $post->has_parent() ? $urlfactory->get_view_post_url_from_post_id($discussionid, $parentid) : null; 398 $editurl = $canedit ? $urlfactory->get_edit_post_url_from_post($forum, $post) : null; 399 $deleteurl = $candelete ? $urlfactory->get_delete_post_url_from_post($post) : null; 400 $spliturl = $cansplit ? $urlfactory->get_split_discussion_at_post_url_from_post($post) : null; 401 $replyurl = $canreply || $canselfenrol ? $urlfactory->get_reply_to_post_url_from_post($post) : null; 402 $exporturl = $canexport ? $urlfactory->get_export_post_url_from_post($post) : null; 403 $markasreadurl = $cancontrolreadstatus ? $urlfactory->get_mark_post_as_read_url_from_post($post) : null; 404 $markasunreadurl = $cancontrolreadstatus ? $urlfactory->get_mark_post_as_unread_url_from_post($post) : null; 405 $discussurl = $canview ? $urlfactory->get_discussion_view_url_from_post($post) : null; 406 407 $authorexporter = new author_exporter( 408 $author, 409 $authorcontextid, 410 $authorgroups, 411 $canview, 412 $this->related 413 ); 414 $exportedauthor = $authorexporter->export($output); 415 // Only bother loading the content if the user can see it. 416 $loadcontent = $canview && !$isdeleted; 417 $exportattachments = $loadcontent && !empty($attachments); 418 $exportinlineattachments = $loadcontent && !empty($inlineattachments); 419 420 if ($loadcontent) { 421 $subject = $post->get_subject(); 422 $timecreated = $this->get_start_time($discussion, $post); 423 $message = $this->get_message($post); 424 } else { 425 $subject = $isdeleted ? get_string('forumsubjectdeleted', 'forum') : get_string('forumsubjecthidden', 'forum'); 426 $message = $isdeleted ? get_string('forumbodydeleted', 'forum') : get_string('forumbodyhidden', 'forum'); 427 $timecreated = null; 428 } 429 430 $replysubject = $subject; 431 $strre = get_string('re', 'forum'); 432 if (!(substr($replysubject, 0, strlen($strre)) == $strre)) { 433 $replysubject = "{$strre} {$replysubject}"; 434 } 435 436 $showwordcount = $forum->should_display_word_count(); 437 if ($showwordcount) { 438 $wordcount = $post->get_wordcount() ?? count_words($message); 439 $charcount = $post->get_charcount() ?? count_letters($message); 440 } else { 441 $wordcount = null; 442 $charcount = null; 443 } 444 445 return [ 446 'id' => $post->get_id(), 447 'subject' => $subject, 448 'replysubject' => $replysubject, 449 'message' => $message, 450 'messageformat' => $post->get_message_format(), 451 'author' => $exportedauthor, 452 'discussionid' => $post->get_discussion_id(), 453 'hasparent' => $post->has_parent(), 454 'parentid' => $post->has_parent() ? $post->get_parent_id() : null, 455 'timecreated' => $timecreated, 456 'timemodified' => $post->get_time_modified(), 457 'unread' => ($loadcontent && $readreceiptcollection) ? !$readreceiptcollection->has_user_read_post($user, $post) : null, 458 'isdeleted' => $isdeleted, 459 'isprivatereply' => $isprivatereply, 460 'haswordcount' => $showwordcount, 461 'wordcount' => $wordcount, 462 'charcount' => $charcount, 463 'capabilities' => [ 464 'view' => $canview, 465 'edit' => $canedit, 466 'delete' => $candelete, 467 'split' => $cansplit, 468 'reply' => $canreply, 469 'export' => $canexport, 470 'controlreadstatus' => $cancontrolreadstatus, 471 'canreplyprivately' => $canreplyprivately, 472 'selfenrol' => $canselfenrol 473 ], 474 'urls' => [ 475 'view' => $viewurl ? $viewurl->out(false) : null, 476 'viewisolated' => $viewisolatedurl ? $viewisolatedurl->out(false) : null, 477 'viewparent' => $viewparenturl ? $viewparenturl->out(false) : null, 478 'edit' => $editurl ? $editurl->out(false) : null, 479 'delete' => $deleteurl ? $deleteurl->out(false) : null, 480 'split' => $spliturl ? $spliturl->out(false) : null, 481 'reply' => $replyurl ? $replyurl->out(false) : null, 482 'export' => $exporturl && $exporturl ? $exporturl->out(false) : null, 483 'markasread' => $markasreadurl ? $markasreadurl->out(false) : null, 484 'markasunread' => $markasunreadurl ? $markasunreadurl->out(false) : null, 485 'discuss' => $discussurl ? $discussurl->out(false) : null, 486 ], 487 'attachments' => ($exportattachments) ? $this->export_attachments($attachments, $post, $output, $canexport) : [], 488 'messageinlinefiles' => ($exportinlineattachments) ? $this->export_inline_attachments($inlineattachments, 489 $post, $output) : [], 490 'tags' => ($loadcontent && $hastags) ? $this->export_tags($tags) : [], 491 'html' => $includehtml ? [ 492 'rating' => ($loadcontent && $hasrating) ? $output->render($rating) : null, 493 'taglist' => ($loadcontent && $hastags) ? $output->tag_list($tags) : null, 494 'authorsubheading' => ($loadcontent) ? $this->get_author_subheading_html($exportedauthor, $timecreated) : null 495 ] : null 496 ]; 497 } 498 499 /** 500 * Returns a list of objects that are related. 501 * 502 * @return array 503 */ 504 protected static function define_related() { 505 return [ 506 'capabilitymanager' => 'mod_forum\local\managers\capability', 507 'readreceiptcollection' => 'mod_forum\local\entities\post_read_receipt_collection?', 508 'urlfactory' => 'mod_forum\local\factories\url', 509 'forum' => 'mod_forum\local\entities\forum', 510 'discussion' => 'mod_forum\local\entities\discussion', 511 'author' => 'mod_forum\local\entities\author', 512 'authorcontextid' => 'int?', 513 'user' => 'stdClass', 514 'context' => 'context', 515 'authorgroups' => 'stdClass[]', 516 'attachments' => '\stored_file[]?', 517 'messageinlinefiles' => '\stored_file[]?', 518 'tags' => '\core_tag_tag[]?', 519 'rating' => 'rating?', 520 'includehtml' => 'bool' 521 ]; 522 } 523 524 /** 525 * This method returns the parameters for the post's message to 526 * use with the function external_format_text(). 527 * 528 * @return array 529 */ 530 protected function get_format_parameters_for_message() { 531 return [ 532 'component' => 'mod_forum', 533 'filearea' => 'post', 534 'itemid' => $this->post->get_id(), 535 'options' => [ 536 'para' => false, 537 'trusted' => $this->post->is_message_trusted() 538 ] 539 ]; 540 } 541 542 /** 543 * Get the message text from a post. 544 * 545 * @param post_entity $post The post 546 * @return string 547 */ 548 private function get_message(post_entity $post) : string { 549 global $CFG; 550 551 $message = $post->get_message(); 552 553 if (!empty($CFG->enableplagiarism)) { 554 require_once($CFG->libdir . '/plagiarismlib.php'); 555 $forum = $this->related['forum']; 556 $message .= plagiarism_get_links([ 557 'userid' => $post->get_author_id(), 558 'content' => $message, 559 'cmid' => $forum->get_course_module_record()->id, 560 'course' => $forum->get_course_id(), 561 'forum' => $forum->get_id() 562 ]); 563 } 564 565 return $message; 566 } 567 568 /** 569 * Get the exported attachments for a post. 570 * 571 * @param stored_file[] $attachments The list of attachments for the post 572 * @param post_entity $post The post being exported 573 * @param renderer_base $output Renderer base 574 * @param bool $canexport If the user can export the post (relates to portfolios not exporters like this class) 575 * @return array 576 */ 577 private function export_attachments(array $attachments, post_entity $post, renderer_base $output, bool $canexport) : array { 578 global $CFG; 579 580 $urlfactory = $this->related['urlfactory']; 581 $enableplagiarism = $CFG->enableplagiarism; 582 $forum = $this->related['forum']; 583 $context = $this->related['context']; 584 585 if ($enableplagiarism) { 586 require_once($CFG->libdir . '/plagiarismlib.php' ); 587 } 588 589 return array_map(function($attachment) use ( 590 $output, 591 $enableplagiarism, 592 $canexport, 593 $context, 594 $forum, 595 $post, 596 $urlfactory 597 ) { 598 $exporter = new stored_file_exporter($attachment, ['context' => $context]); 599 $exportedattachment = $exporter->export($output); 600 $exporturl = $canexport ? $urlfactory->get_export_attachment_url_from_post_and_attachment($post, $attachment) : null; 601 602 if ($enableplagiarism) { 603 $plagiarismhtml = plagiarism_get_links([ 604 'userid' => $post->get_author_id(), 605 'file' => $attachment, 606 'cmid' => $forum->get_course_module_record()->id, 607 'course' => $forum->get_course_id(), 608 'forum' => $forum->get_id() 609 ]); 610 } else { 611 $plagiarismhtml = null; 612 } 613 614 $exportedattachment->urls = [ 615 'export' => $exporturl ? $exporturl->out(false) : null 616 ]; 617 $exportedattachment->html = [ 618 'plagiarism' => $plagiarismhtml 619 ]; 620 621 return $exportedattachment; 622 }, $attachments); 623 } 624 625 /** 626 * Get the exported inline attachments for a post. 627 * 628 * @param array $inlineattachments The list of inline attachments for the post 629 * @param post_entity $post The post being exported 630 * @param renderer_base $output Renderer base 631 * @return array 632 */ 633 private function export_inline_attachments(array $inlineattachments, post_entity $post, renderer_base $output) : array { 634 635 return array_map(function($attachment) use ( 636 $output, 637 $post 638 ) { 639 $exporter = new stored_file_exporter($attachment, ['context' => $this->related['context']]); 640 return $exporter->export($output);; 641 }, $inlineattachments); 642 } 643 644 /** 645 * Export the list of tags. 646 * 647 * @param core_tag_tag[] $tags List of tags to export 648 * @return array 649 */ 650 private function export_tags(array $tags) : array { 651 $user = $this->related['user']; 652 $context = $this->related['context']; 653 $capabilitymanager = $this->related['capabilitymanager']; 654 $canmanagetags = $capabilitymanager->can_manage_tags($user); 655 656 return array_values(array_map(function($tag) use ($context, $canmanagetags) { 657 $viewurl = core_tag_tag::make_url($tag->tagcollid, $tag->rawname, 0, $context->id); 658 return [ 659 'id' => $tag->taginstanceid, 660 'tagid' => $tag->id, 661 'isstandard' => $tag->isstandard, 662 'displayname' => $tag->get_display_name(), 663 'flag' => $canmanagetags && !empty($tag->flag), 664 'urls' => [ 665 'view' => $viewurl->out(false) 666 ] 667 ]; 668 }, $tags)); 669 } 670 671 /** 672 * Get the HTML to display as a subheading in a post. 673 * 674 * @param stdClass $exportedauthor The exported author object 675 * @param int $timecreated The post time created timestamp if it's to be displayed 676 * @return string 677 */ 678 private function get_author_subheading_html(stdClass $exportedauthor, int $timecreated) : string { 679 $fullname = $exportedauthor->fullname; 680 $profileurl = $exportedauthor->urls['profile'] ?? null; 681 $name = $profileurl ? "<a href=\"{$profileurl}\">{$fullname}</a>" : $fullname; 682 $date = userdate_htmltime($timecreated, get_string('strftimedaydatetime', 'core_langconfig')); 683 return get_string('bynameondate', 'mod_forum', ['name' => $name, 'date' => $date]); 684 } 685 686 /** 687 * Get the start time for a post. 688 * 689 * @param discussion_entity $discussion entity 690 * @param post_entity $post entity 691 * @return int The start time (timestamp) for a post 692 */ 693 private function get_start_time(discussion_entity $discussion, post_entity $post) { 694 global $CFG; 695 696 $posttime = $post->get_time_created(); 697 $discussiontime = $discussion->get_time_start(); 698 if (!empty($CFG->forum_enabletimedposts) && ($discussiontime > $posttime)) { 699 return $discussiontime; 700 } 701 return $posttime; 702 } 703 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body