Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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  }