Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 310] [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   * @package   mod_forum
  19   * @copyright 2014 Andrew Robert Nicols <andrew@nicols.co.uk>
  20   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21   */
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  // Deprecated a very long time ago.
  26  
  27  /**
  28   * @deprecated since Moodle 1.1 - please do not use this function any more.
  29   */
  30  function forum_count_unrated_posts() {
  31      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  32  }
  33  
  34  
  35  // Since Moodle 1.5.
  36  
  37  /**
  38   * @deprecated since Moodle 1.5 - please do not use this function any more.
  39   */
  40  function forum_tp_count_discussion_read_records() {
  41      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  42  }
  43  
  44  /**
  45   * @deprecated since Moodle 1.5 - please do not use this function any more.
  46   */
  47  function forum_get_user_discussions() {
  48      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  49  }
  50  
  51  
  52  // Since Moodle 1.6.
  53  
  54  /**
  55   * @deprecated since Moodle 1.6 - please do not use this function any more.
  56   */
  57  function forum_tp_count_forum_posts() {
  58      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  59  }
  60  
  61  /**
  62   * @deprecated since Moodle 1.6 - please do not use this function any more.
  63   */
  64  function forum_tp_count_forum_read_records() {
  65      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  66  }
  67  
  68  
  69  // Since Moodle 1.7.
  70  
  71  /**
  72   * @deprecated since Moodle 1.7 - please do not use this function any more.
  73   */
  74  function forum_get_open_modes() {
  75      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  76  }
  77  
  78  
  79  // Since Moodle 1.9.
  80  
  81  /**
  82   * @deprecated since Moodle 1.9 MDL-13303 - please do not use this function any more.
  83   */
  84  function forum_get_child_posts() {
  85      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  86  }
  87  
  88  /**
  89   * @deprecated since Moodle 1.9 MDL-13303 - please do not use this function any more.
  90   */
  91  function forum_get_discussion_posts() {
  92      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
  93  }
  94  
  95  
  96  // Since Moodle 2.0.
  97  
  98  /**
  99   * @deprecated since Moodle 2.0 MDL-21657 - please do not use this function any more.
 100   */
 101  function forum_get_ratings() {
 102      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 103  }
 104  
 105  /**
 106   * @deprecated since Moodle 2.0 MDL-14632 - please do not use this function any more.
 107   */
 108  function forum_get_tracking_link() {
 109      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 110  }
 111  
 112  /**
 113   * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
 114   */
 115  function forum_tp_count_discussion_unread_posts() {
 116      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 117  }
 118  
 119  /**
 120   * @deprecated since Moodle 2.0 MDL-23479 - please do not use this function any more.
 121   */
 122  function forum_convert_to_roles() {
 123      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 124  }
 125  
 126  /**
 127   * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
 128   */
 129  function forum_tp_get_read_records() {
 130      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 131  }
 132  
 133  /**
 134   * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
 135   */
 136  function forum_tp_get_discussion_read_records() {
 137      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 138  }
 139  
 140  // Deprecated in 2.3.
 141  
 142  /**
 143   * @deprecated since Moodle 2.3 MDL-33166 - please do not use this function any more.
 144   */
 145  function forum_user_enrolled() {
 146      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 147  }
 148  
 149  
 150  // Deprecated in 2.4.
 151  
 152  /**
 153   * @deprecated since Moodle 2.4 use forum_user_can_see_post() instead
 154   */
 155  function forum_user_can_view_post() {
 156      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 157  }
 158  
 159  
 160  // Deprecated in 2.6.
 161  
 162  /**
 163   * FORUM_TRACKING_ON - deprecated alias for FORUM_TRACKING_FORCED.
 164   * @deprecated since 2.6
 165   */
 166  define('FORUM_TRACKING_ON', 2);
 167  
 168  /**
 169   * @deprecated since Moodle 2.6
 170   * @see shorten_text()
 171   */
 172  function forum_shorten_post($message) {
 173      throw new coding_exception(__FUNCTION__ . '() can not be used any more. '
 174          . 'Please use shorten_text($message, $CFG->forum_shortpost) instead.');
 175  }
 176  
 177  // Deprecated in 2.8.
 178  
 179  /**
 180   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::is_subscribed() instead
 181   */
 182  function forum_is_subscribed() {
 183      throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
 184  }
 185  
 186  /**
 187   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::subscribe_user() instead
 188   */
 189  function forum_subscribe() {
 190      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 191          . \mod_forum\subscriptions::class . '::subscribe_user() instead');
 192  }
 193  
 194  /**
 195   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::unsubscribe_user() instead
 196   */
 197  function forum_unsubscribe() {
 198      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 199          . \mod_forum\subscriptions::class . '::unsubscribe_user() instead');
 200  }
 201  
 202  /**
 203   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::fetch_subscribed_users() instead
 204    */
 205  function forum_subscribed_users() {
 206      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 207          . \mod_forum\subscriptions::class . '::fetch_subscribed_users() instead');
 208  }
 209  
 210  /**
 211   * Determine whether the forum is force subscribed.
 212   *
 213   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::is_forcesubscribed() instead
 214   */
 215  function forum_is_forcesubscribed($forum) {
 216      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 217          . \mod_forum\subscriptions::class . '::is_forcesubscribed() instead');
 218  }
 219  
 220  /**
 221   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::set_subscription_mode() instead
 222   */
 223  function forum_forcesubscribe($forumid, $value = 1) {
 224      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 225          . \mod_forum\subscriptions::class . '::set_subscription_mode() instead');
 226  }
 227  
 228  /**
 229   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::get_subscription_mode() instead
 230   */
 231  function forum_get_forcesubscribed($forum) {
 232      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 233          . \mod_forum\subscriptions::class . '::set_subscription_mode() instead');
 234  }
 235  
 236  /**
 237   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::is_subscribed in combination wtih
 238   * \mod_forum\subscriptions::fill_subscription_cache_for_course instead.
 239   */
 240  function forum_get_subscribed_forums() {
 241      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 242          . \mod_forum\subscriptions::class . '::is_subscribed(), and '
 243          . \mod_forum\subscriptions::class . '::fill_subscription_cache_for_course() instead');
 244  }
 245  
 246  /**
 247   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::get_unsubscribable_forums() instead
 248   */
 249  function forum_get_optional_subscribed_forums() {
 250      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 251          . \mod_forum\subscriptions::class . '::get_unsubscribable_forums() instead');
 252  }
 253  
 254  /**
 255   * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::get_potential_subscribers() instead
 256   */
 257  function forum_get_potential_subscribers() {
 258      throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
 259          . \mod_forum\subscriptions::class . '::get_potential_subscribers() instead');
 260  }
 261  
 262  /**
 263   * Builds and returns the body of the email notification in plain text.
 264   *
 265   * @uses CONTEXT_MODULE
 266   * @param object $course
 267   * @param object $cm
 268   * @param object $forum
 269   * @param object $discussion
 270   * @param object $post
 271   * @param object $userfrom
 272   * @param object $userto
 273   * @param boolean $bare
 274   * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
 275   * @return string The email body in plain text format.
 276   * @deprecated since Moodle 3.0 use \mod_forum\output\forum_post_email instead
 277   */
 278  function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false, $replyaddress = null) {
 279      global $PAGE;
 280      $renderable = new \mod_forum\output\forum_post_email(
 281          $course,
 282          $cm,
 283          $forum,
 284          $discussion,
 285          $post,
 286          $userfrom,
 287          $userto,
 288          forum_user_can_post($forum, $discussion, $userto, $cm, $course)
 289          );
 290  
 291      $modcontext = context_module::instance($cm->id);
 292      $renderable->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
 293  
 294      if ($bare) {
 295          $renderer = $PAGE->get_renderer('mod_forum', 'emaildigestfull', 'textemail');
 296      } else {
 297          $renderer = $PAGE->get_renderer('mod_forum', 'email', 'textemail');
 298      }
 299  
 300      debugging("forum_make_mail_text() has been deprecated, please use the \mod_forum\output\forum_post_email renderable instead.",
 301              DEBUG_DEVELOPER);
 302  
 303      return $renderer->render($renderable);
 304  }
 305  
 306  /**
 307   * Builds and returns the body of the email notification in html format.
 308   *
 309   * @param object $course
 310   * @param object $cm
 311   * @param object $forum
 312   * @param object $discussion
 313   * @param object $post
 314   * @param object $userfrom
 315   * @param object $userto
 316   * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
 317   * @return string The email text in HTML format
 318   * @deprecated since Moodle 3.0 use \mod_forum\output\forum_post_email instead
 319   */
 320  function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $replyaddress = null) {
 321      return forum_make_mail_post($course,
 322          $cm,
 323          $forum,
 324          $discussion,
 325          $post,
 326          $userfrom,
 327          $userto,
 328          forum_user_can_post($forum, $discussion, $userto, $cm, $course)
 329      );
 330  }
 331  
 332  /**
 333   * Given the data about a posting, builds up the HTML to display it and
 334   * returns the HTML in a string.  This is designed for sending via HTML email.
 335   *
 336   * @param object $course
 337   * @param object $cm
 338   * @param object $forum
 339   * @param object $discussion
 340   * @param object $post
 341   * @param object $userfrom
 342   * @param object $userto
 343   * @param bool $ownpost
 344   * @param bool $reply
 345   * @param bool $link
 346   * @param bool $rate
 347   * @param string $footer
 348   * @return string
 349   * @deprecated since Moodle 3.0 use \mod_forum\output\forum_post_email instead
 350   */
 351  function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
 352                                $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
 353      global $PAGE;
 354      $renderable = new \mod_forum\output\forum_post_email(
 355          $course,
 356          $cm,
 357          $forum,
 358          $discussion,
 359          $post,
 360          $userfrom,
 361          $userto,
 362          $reply);
 363  
 364      $modcontext = context_module::instance($cm->id);
 365      $renderable->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
 366  
 367      // Assume that this is being used as a standard forum email.
 368      $renderer = $PAGE->get_renderer('mod_forum', 'email', 'htmlemail');
 369  
 370      debugging("forum_make_mail_post() has been deprecated, please use the \mod_forum\output\forum_post_email renderable instead.",
 371              DEBUG_DEVELOPER);
 372  
 373      return $renderer->render($renderable);
 374  }
 375  
 376  /**
 377   * Removes properties from user record that are not necessary for sending post notifications.
 378   *
 379   * @param stdClass $user
 380   * @return void, $user parameter is modified
 381   * @deprecated since Moodle 3.7
 382   */
 383  function forum_cron_minimise_user_record(stdClass $user) {
 384      debugging("forum_cron_minimise_user_record() has been deprecated and has not been replaced.",
 385              DEBUG_DEVELOPER);
 386  
 387      // We store large amount of users in one huge array,
 388      // make sure we do not store info there we do not actually need
 389      // in mail generation code or messaging.
 390  
 391      unset($user->institution);
 392      unset($user->department);
 393      unset($user->address);
 394      unset($user->city);
 395      unset($user->url);
 396      unset($user->currentlogin);
 397      unset($user->description);
 398      unset($user->descriptionformat);
 399  }
 400  
 401  /**
 402   * Function to be run periodically according to the scheduled task.
 403   *
 404   * Finds all posts that have yet to be mailed out, and mails them out to all subscribers as well as other maintance
 405   * tasks.
 406   *
 407   * @deprecated since Moodle 3.7
 408   */
 409  function forum_cron() {
 410      debugging("forum_cron() has been deprecated and replaced with new tasks. Please uses these instead.",
 411              DEBUG_DEVELOPER);
 412  }
 413  
 414  /**
 415   * Prints a forum discussion
 416   *
 417   * @uses CONTEXT_MODULE
 418   * @uses FORUM_MODE_FLATNEWEST
 419   * @uses FORUM_MODE_FLATOLDEST
 420   * @uses FORUM_MODE_THREADED
 421   * @uses FORUM_MODE_NESTED
 422   * @param stdClass $course
 423   * @param stdClass $cm
 424   * @param stdClass $forum
 425   * @param stdClass $discussion
 426   * @param stdClass $post
 427   * @param int $mode
 428   * @param mixed $canreply
 429   * @param bool $canrate
 430   * @deprecated since Moodle 3.7
 431   */
 432  function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
 433      debugging('forum_print_discussion() has been deprecated, ' .
 434          'please use \mod_forum\local\renderers\discussion instead.', DEBUG_DEVELOPER);
 435  
 436      global $USER, $CFG;
 437  
 438      require_once($CFG->dirroot.'/rating/lib.php');
 439  
 440      $ownpost = (isloggedin() && $USER->id == $post->userid);
 441  
 442      $modcontext = context_module::instance($cm->id);
 443      if ($canreply === NULL) {
 444          $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
 445      } else {
 446          $reply = $canreply;
 447      }
 448  
 449      // $cm holds general cache for forum functions
 450      $cm->cache = new stdClass;
 451      $cm->cache->groups      = groups_get_all_groups($course->id, 0, $cm->groupingid);
 452      $cm->cache->usersgroups = array();
 453  
 454      $posters = array();
 455  
 456      // preload all posts - TODO: improve...
 457      if ($mode == FORUM_MODE_FLATNEWEST) {
 458          $sort = "p.created DESC";
 459      } else {
 460          $sort = "p.created ASC";
 461      }
 462  
 463      $forumtracked = forum_tp_is_tracked($forum);
 464      $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
 465      $post = $posts[$post->id];
 466  
 467      foreach ($posts as $pid=>$p) {
 468          $posters[$p->userid] = $p->userid;
 469      }
 470  
 471      // preload all groups of ppl that posted in this discussion
 472      if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
 473          foreach($postersgroups as $pg) {
 474              if (!isset($cm->cache->usersgroups[$pg->userid])) {
 475                  $cm->cache->usersgroups[$pg->userid] = array();
 476              }
 477              $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
 478          }
 479          unset($postersgroups);
 480      }
 481  
 482      //load ratings
 483      if ($forum->assessed != RATING_AGGREGATE_NONE) {
 484          $ratingoptions = new stdClass;
 485          $ratingoptions->context = $modcontext;
 486          $ratingoptions->component = 'mod_forum';
 487          $ratingoptions->ratingarea = 'post';
 488          $ratingoptions->items = $posts;
 489          $ratingoptions->aggregate = $forum->assessed;//the aggregation method
 490          $ratingoptions->scaleid = $forum->scale;
 491          $ratingoptions->userid = $USER->id;
 492          if ($forum->type == 'single' or !$discussion->id) {
 493              $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
 494          } else {
 495              $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
 496          }
 497          $ratingoptions->assesstimestart = $forum->assesstimestart;
 498          $ratingoptions->assesstimefinish = $forum->assesstimefinish;
 499  
 500          $rm = new rating_manager();
 501          $posts = $rm->get_ratings($ratingoptions);
 502      }
 503  
 504  
 505      $post->forum = $forum->id;   // Add the forum id to the post object, later used by forum_print_post
 506      $post->forumtype = $forum->type;
 507  
 508      $post->subject = format_string($post->subject);
 509  
 510      $postread = !empty($post->postread);
 511  
 512      forum_print_post_start($post);
 513      forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
 514                           '', '', $postread, true, $forumtracked);
 515  
 516      switch ($mode) {
 517          case FORUM_MODE_FLATOLDEST :
 518          case FORUM_MODE_FLATNEWEST :
 519          default:
 520              forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
 521              break;
 522  
 523          case FORUM_MODE_THREADED :
 524              forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
 525              break;
 526  
 527          case FORUM_MODE_NESTED :
 528              forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
 529              break;
 530      }
 531      forum_print_post_end($post);
 532  }
 533  
 534  
 535  /**
 536   * Return a static array of posts that are open.
 537   *
 538   * @return array
 539   * @deprecated since Moodle 3.7
 540   */
 541  function forum_post_nesting_cache() {
 542      debugging('forum_post_nesting_cache() has been deprecated, ' .
 543          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 544      static $nesting = array();
 545      return $nesting;
 546  }
 547  
 548  /**
 549   * Return true for the first time this post was started
 550   *
 551   * @param int $id The id of the post to start
 552   * @return bool
 553   * @deprecated since Moodle 3.7
 554   */
 555  function forum_should_start_post_nesting($id) {
 556      debugging('forum_should_start_post_nesting() has been deprecated, ' .
 557          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 558      $cache = forum_post_nesting_cache();
 559      if (!array_key_exists($id, $cache)) {
 560          $cache[$id] = 1;
 561          return true;
 562      } else {
 563          $cache[$id]++;
 564          return false;
 565      }
 566  }
 567  
 568  /**
 569   * Return true when all the opens are nested with a close.
 570   *
 571   * @param int $id The id of the post to end
 572   * @return bool
 573   * @deprecated since Moodle 3.7
 574   */
 575  function forum_should_end_post_nesting($id) {
 576      debugging('forum_should_end_post_nesting() has been deprecated, ' .
 577          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 578      $cache = forum_post_nesting_cache();
 579      if (!array_key_exists($id, $cache)) {
 580          return true;
 581      } else {
 582          $cache[$id]--;
 583          if ($cache[$id] == 0) {
 584              unset($cache[$id]);
 585              return true;
 586          }
 587      }
 588      return false;
 589  }
 590  
 591  /**
 592   * Start a forum post container
 593   *
 594   * @param object $post The post to print.
 595   * @param bool $return Return the string or print it
 596   * @return string
 597   * @deprecated since Moodle 3.7
 598   */
 599  function forum_print_post_start($post, $return = false) {
 600      debugging('forum_print_post_start() has been deprecated, ' .
 601          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 602      $output = '';
 603  
 604      if (forum_should_start_post_nesting($post->id)) {
 605          $attributes = [
 606              'id' => 'p'.$post->id,
 607              'tabindex' => -1,
 608              'class' => 'relativelink'
 609          ];
 610          $output .= html_writer::start_tag('article', $attributes);
 611      }
 612      if ($return) {
 613          return $output;
 614      }
 615      echo $output;
 616      return;
 617  }
 618  
 619  /**
 620   * End a forum post container
 621   *
 622   * @param object $post The post to print.
 623   * @param bool $return Return the string or print it
 624   * @return string
 625   * @deprecated since Moodle 3.7
 626   */
 627  function forum_print_post_end($post, $return = false) {
 628      debugging('forum_print_post_end() has been deprecated, ' .
 629          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 630      $output = '';
 631  
 632      if (forum_should_end_post_nesting($post->id)) {
 633          $output .= html_writer::end_tag('article');
 634      }
 635      if ($return) {
 636          return $output;
 637      }
 638      echo $output;
 639      return;
 640  }
 641  
 642  /**
 643   * Print a forum post
 644   * This function should always be surrounded with calls to forum_print_post_start
 645   * and forum_print_post_end to create the surrounding container for the post.
 646   * Replies can be nested before forum_print_post_end and should reflect the structure of
 647   * thread.
 648   *
 649   * @global object
 650   * @global object
 651   * @uses FORUM_MODE_THREADED
 652   * @uses PORTFOLIO_FORMAT_PLAINHTML
 653   * @uses PORTFOLIO_FORMAT_FILE
 654   * @uses PORTFOLIO_FORMAT_RICHHTML
 655   * @uses PORTFOLIO_ADD_TEXT_LINK
 656   * @uses CONTEXT_MODULE
 657   * @param object $post The post to print.
 658   * @param object $discussion
 659   * @param object $forum
 660   * @param object $cm
 661   * @param object $course
 662   * @param boolean $ownpost Whether this post belongs to the current user.
 663   * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
 664   * @param boolean $link Just print a shortened version of the post as a link to the full post.
 665   * @param string $footer Extra stuff to print after the message.
 666   * @param string $highlight Space-separated list of terms to highlight.
 667   * @param int $post_read true, false or -99. If we already know whether this user
 668   *          has read this post, pass that in, otherwise, pass in -99, and this
 669   *          function will work it out.
 670   * @param boolean $dummyifcantsee When forum_user_can_see_post says that
 671   *          the current user can't see this post, if this argument is true
 672   *          (the default) then print a dummy 'you can't see this post' post.
 673   *          If false, don't output anything at all.
 674   * @param bool|null $istracked
 675   * @return void
 676   * @deprecated since Moodle 3.7
 677   */
 678  function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
 679                            $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
 680      debugging('forum_print_post() has been deprecated, ' .
 681          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 682      global $USER, $CFG, $OUTPUT;
 683  
 684      require_once($CFG->libdir . '/filelib.php');
 685  
 686      // String cache
 687      static $str;
 688      // This is an extremely hacky way to ensure we only print the 'unread' anchor
 689      // the first time we encounter an unread post on a page. Ideally this would
 690      // be moved into the caller somehow, and be better testable. But at the time
 691      // of dealing with this bug, this static workaround was the most surgical and
 692      // it fits together with only printing th unread anchor id once on a given page.
 693      static $firstunreadanchorprinted = false;
 694  
 695      $modcontext = context_module::instance($cm->id);
 696  
 697      $post->course = $course->id;
 698      $post->forum  = $forum->id;
 699      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
 700      if (!empty($CFG->enableplagiarism)) {
 701          require_once($CFG->libdir.'/plagiarismlib.php');
 702          $post->message .= plagiarism_get_links(array('userid' => $post->userid,
 703              'content' => $post->message,
 704              'cmid' => $cm->id,
 705              'course' => $post->course,
 706              'forum' => $post->forum));
 707      }
 708  
 709      // caching
 710      if (!isset($cm->cache)) {
 711          $cm->cache = new stdClass;
 712      }
 713  
 714      if (!isset($cm->cache->caps)) {
 715          $cm->cache->caps = array();
 716          $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
 717          $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
 718          $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
 719          $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
 720          $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
 721          $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
 722          $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
 723          $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
 724          $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
 725      }
 726  
 727      if (!isset($cm->uservisible)) {
 728          $cm->uservisible = \core_availability\info_module::is_user_visible($cm, 0, false);
 729      }
 730  
 731      if ($istracked && is_null($postisread)) {
 732          $postisread = forum_tp_is_post_read($USER->id, $post);
 733      }
 734  
 735      if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
 736          // Do _not_ check the deleted flag - we need to display a different UI.
 737          $output = '';
 738          if (!$dummyifcantsee) {
 739              if ($return) {
 740                  return $output;
 741              }
 742              echo $output;
 743              return;
 744          }
 745  
 746          $output .= html_writer::start_tag('div', array('class' => 'forumpost clearfix',
 747                                                         'aria-label' => get_string('hiddenforumpost', 'forum')));
 748          $output .= html_writer::start_tag('header', array('class' => 'row header'));
 749          $output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation')); // Picture.
 750          if ($post->parent) {
 751              $output .= html_writer::start_tag('div', array('class' => 'topic'));
 752          } else {
 753              $output .= html_writer::start_tag('div', array('class' => 'topic starter'));
 754          }
 755          $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class' => 'subject',
 756                                                                                             'role' => 'header',
 757                                                                                             'id' => ('headp' . $post->id))); // Subject.
 758          $authorclasses = array('class' => 'author');
 759          $output .= html_writer::tag('address', get_string('forumauthorhidden', 'forum'), $authorclasses); // Author.
 760          $output .= html_writer::end_tag('div');
 761          $output .= html_writer::end_tag('header'); // Header.
 762          $output .= html_writer::start_tag('div', array('class'=>'row'));
 763          $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
 764          $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
 765          $output .= html_writer::end_tag('div'); // row
 766          $output .= html_writer::end_tag('div'); // forumpost
 767  
 768          if ($return) {
 769              return $output;
 770          }
 771          echo $output;
 772          return;
 773      }
 774  
 775      if (!empty($post->deleted)) {
 776          // Note: Posts marked as deleted are still returned by the above forum_user_can_post because it is required for
 777          // nesting of posts.
 778          $output = '';
 779          if (!$dummyifcantsee) {
 780              if ($return) {
 781                  return $output;
 782              }
 783              echo $output;
 784              return;
 785          }
 786          $output .= html_writer::start_tag('div', [
 787                  'class' => 'forumpost clearfix',
 788                  'aria-label' => get_string('forumbodydeleted', 'forum'),
 789              ]);
 790  
 791          $output .= html_writer::start_tag('header', array('class' => 'row header'));
 792          $output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation'));
 793  
 794          $classes = ['topic'];
 795          if (!empty($post->parent)) {
 796              $classes[] = 'starter';
 797          }
 798          $output .= html_writer::start_tag('div', ['class' => implode(' ', $classes)]);
 799  
 800          // Subject.
 801          $output .= html_writer::tag('div', get_string('forumsubjectdeleted', 'forum'), [
 802                  'class' => 'subject',
 803                  'role' => 'header',
 804                  'id' => ('headp' . $post->id)
 805              ]);
 806  
 807          // Author.
 808          $output .= html_writer::tag('address', '', ['class' => 'author']);
 809  
 810          $output .= html_writer::end_tag('div');
 811          $output .= html_writer::end_tag('header'); // End header.
 812          $output .= html_writer::start_tag('div', ['class' => 'row']);
 813          $output .= html_writer::tag('div', '&nbsp;', ['class' => 'left side']); // Groups.
 814          $output .= html_writer::tag('div', get_string('forumbodydeleted', 'forum'), ['class' => 'content']); // Content.
 815          $output .= html_writer::end_tag('div'); // End row.
 816          $output .= html_writer::end_tag('div'); // End forumpost.
 817  
 818          if ($return) {
 819              return $output;
 820          }
 821          echo $output;
 822          return;
 823      }
 824  
 825      if (empty($str)) {
 826          $str = new stdClass;
 827          $str->edit         = get_string('edit', 'forum');
 828          $str->delete       = get_string('delete', 'forum');
 829          $str->reply        = get_string('reply', 'forum');
 830          $str->parent       = get_string('parent', 'forum');
 831          $str->pruneheading = get_string('pruneheading', 'forum');
 832          $str->prune        = get_string('prune', 'forum');
 833          $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
 834          $str->markread     = get_string('markread', 'forum');
 835          $str->markunread   = get_string('markunread', 'forum');
 836      }
 837  
 838      $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
 839  
 840      // Build an object that represents the posting user
 841      $postuser = new stdClass;
 842      $postuserfields = explode(',', user_picture::fields());
 843      $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
 844      $postuser->id = $post->userid;
 845      $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
 846      $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
 847  
 848      // Prepare the groups the posting user belongs to
 849      if (isset($cm->cache->usersgroups)) {
 850          $groups = array();
 851          if (isset($cm->cache->usersgroups[$post->userid])) {
 852              foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
 853                  $groups[$gid] = $cm->cache->groups[$gid];
 854              }
 855          }
 856      } else {
 857          $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
 858      }
 859  
 860      // Prepare the attachements for the post, files then images
 861      list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
 862  
 863      // Determine if we need to shorten this post
 864      $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
 865  
 866      // Prepare an array of commands
 867      $commands = array();
 868  
 869      // Add a permalink.
 870      $permalink = new moodle_url($discussionlink);
 871      $permalink->set_anchor('p' . $post->id);
 872      $commands[] = array('url' => $permalink, 'text' => get_string('permalink', 'forum'), 'attributes' => ['rel' => 'bookmark']);
 873  
 874      // SPECIAL CASE: The front page can display a news item post to non-logged in users.
 875      // Don't display the mark read / unread controls in this case.
 876      if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
 877          $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
 878          $text = $str->markunread;
 879          if (!$postisread) {
 880              $url->param('mark', 'read');
 881              $text = $str->markread;
 882          }
 883          if ($str->displaymode == FORUM_MODE_THREADED) {
 884              $url->param('parent', $post->parent);
 885          } else {
 886              $url->set_anchor('p'.$post->id);
 887          }
 888          $commands[] = array('url'=>$url, 'text'=>$text, 'attributes' => ['rel' => 'bookmark']);
 889      }
 890  
 891      // Zoom in to the parent specifically
 892      if ($post->parent) {
 893          $url = new moodle_url($discussionlink);
 894          if ($str->displaymode == FORUM_MODE_THREADED) {
 895              $url->param('parent', $post->parent);
 896          } else {
 897              $url->set_anchor('p'.$post->parent);
 898          }
 899          $commands[] = array('url'=>$url, 'text'=>$str->parent, 'attributes' => ['rel' => 'bookmark']);
 900      }
 901  
 902      // Hack for allow to edit news posts those are not displayed yet until they are displayed
 903      $age = time() - $post->created;
 904      if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
 905          $age = 0;
 906      }
 907  
 908      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
 909          if (has_capability('moodle/course:manageactivities', $modcontext)) {
 910              // The first post in single simple is the forum description.
 911              $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
 912          }
 913      } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
 914          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
 915      }
 916  
 917      if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
 918          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
 919      }
 920  
 921      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
 922          // Do not allow deleting of first post in single simple type.
 923      } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
 924          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
 925      }
 926  
 927      if ($reply) {
 928          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
 929      }
 930  
 931      if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
 932          $p = array('postid' => $post->id);
 933          require_once($CFG->libdir.'/portfoliolib.php');
 934          $button = new portfolio_add_button();
 935          $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
 936          if (empty($attachments)) {
 937              $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
 938          } else {
 939              $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
 940          }
 941  
 942          $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
 943          if (!empty($porfoliohtml)) {
 944              $commands[] = $porfoliohtml;
 945          }
 946      }
 947      // Finished building commands
 948  
 949  
 950      // Begin output
 951  
 952      $output  = '';
 953  
 954      if ($istracked) {
 955          if ($postisread) {
 956              $forumpostclass = ' read';
 957          } else {
 958              $forumpostclass = ' unread';
 959              // If this is the first unread post printed then give it an anchor and id of unread.
 960              if (!$firstunreadanchorprinted) {
 961                  $output .= html_writer::tag('a', '', array('id' => 'unread'));
 962                  $firstunreadanchorprinted = true;
 963              }
 964          }
 965      } else {
 966          // ignore trackign status if not tracked or tracked param missing
 967          $forumpostclass = '';
 968      }
 969  
 970      $topicclass = '';
 971      if (empty($post->parent)) {
 972          $topicclass = ' firstpost starter';
 973      }
 974  
 975      if (!empty($post->lastpost)) {
 976          $forumpostclass .= ' lastpost';
 977      }
 978  
 979      // Flag to indicate whether we should hide the author or not.
 980      $authorhidden = forum_is_author_hidden($post, $forum);
 981      $postbyuser = new stdClass;
 982      $postbyuser->post = $post->subject;
 983      $postbyuser->user = $postuser->fullname;
 984      $discussionbyuser = get_string('postbyuser', 'forum', $postbyuser);
 985      // Begin forum post.
 986      $output .= html_writer::start_div('forumpost clearfix' . $forumpostclass . $topicclass,
 987          ['aria-label' => $discussionbyuser]);
 988      // Begin header row.
 989      $output .= html_writer::start_tag('header', ['class' => 'row header clearfix']);
 990  
 991      // User picture.
 992      if (!$authorhidden) {
 993          $picture = $OUTPUT->user_picture($postuser, ['courseid' => $course->id]);
 994          $output .= html_writer::div($picture, 'left picture', ['role' => 'presentation']);
 995          $topicclass = 'topic' . $topicclass;
 996      }
 997  
 998      // Begin topic column.
 999      $output .= html_writer::start_div($topicclass);
1000      $postsubject = $post->subject;
1001      if (empty($post->subjectnoformat)) {
1002          $postsubject = format_string($postsubject);
1003      }
1004      $output .= html_writer::div($postsubject, 'subject', ['role' => 'heading', 'aria-level' => '1', 'id' => ('headp' . $post->id)]);
1005  
1006      if ($authorhidden) {
1007          $bytext = userdate_htmltime($post->created);
1008      } else {
1009          $by = new stdClass();
1010          $by->date = userdate_htmltime($post->created);
1011          $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
1012          $bytext = get_string('bynameondate', 'forum', $by);
1013      }
1014      $bytextoptions = [
1015          'class' => 'author'
1016      ];
1017      $output .= html_writer::tag('address', $bytext, $bytextoptions);
1018      // End topic column.
1019      $output .= html_writer::end_div();
1020  
1021      // End header row.
1022      $output .= html_writer::end_tag('header');
1023  
1024      // Row with the forum post content.
1025      $output .= html_writer::start_div('row maincontent clearfix');
1026      // Show if author is not hidden or we have groups.
1027      if (!$authorhidden || $groups) {
1028          $output .= html_writer::start_div('left');
1029          $groupoutput = '';
1030          if ($groups) {
1031              $groupoutput = print_group_picture($groups, $course->id, false, true, true);
1032          }
1033          if (empty($groupoutput)) {
1034              $groupoutput = '&nbsp;';
1035          }
1036          $output .= html_writer::div($groupoutput, 'grouppictures');
1037          $output .= html_writer::end_div(); // Left side.
1038      }
1039  
1040      $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
1041      $output .= html_writer::start_tag('div', array('class'=>'content'));
1042  
1043      $options = new stdClass;
1044      $options->para    = false;
1045      $options->trusted = $post->messagetrust;
1046      $options->context = $modcontext;
1047      if ($shortenpost) {
1048          // Prepare shortened version by filtering the text then shortening it.
1049          $postclass    = 'shortenedpost';
1050          $postcontent  = format_text($post->message, $post->messageformat, $options);
1051          $postcontent  = shorten_text($postcontent, $CFG->forum_shortpost);
1052          $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
1053          $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
1054              array('class'=>'post-word-count'));
1055      } else {
1056          // Prepare whole post
1057          $postclass    = 'fullpost';
1058          $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
1059          if (!empty($highlight)) {
1060              $postcontent = highlight($highlight, $postcontent);
1061          }
1062          if (!empty($forum->displaywordcount)) {
1063              $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($postcontent)),
1064                  array('class'=>'post-word-count'));
1065          }
1066          $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
1067      }
1068  
1069      if (\core_tag_tag::is_enabled('mod_forum', 'forum_posts')) {
1070          $postcontent .= $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id), null, 'forum-tags');
1071      }
1072  
1073      // Output the post content
1074      $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
1075      $output .= html_writer::end_tag('div'); // Content
1076      $output .= html_writer::end_tag('div'); // Content mask
1077      $output .= html_writer::end_tag('div'); // Row
1078  
1079      $output .= html_writer::start_tag('nav', array('class' => 'row side'));
1080      $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
1081      $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
1082  
1083      if (!empty($attachments)) {
1084          $output .= html_writer::tag('div', $attachments, array('class' => 'attachments'));
1085      }
1086  
1087      // Output ratings
1088      if (!empty($post->rating)) {
1089          $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
1090      }
1091  
1092      // Output the commands
1093      $commandhtml = array();
1094      foreach ($commands as $command) {
1095          if (is_array($command)) {
1096              $attributes = ['class' => 'nav-item nav-link'];
1097              if (isset($command['attributes'])) {
1098                  $attributes = array_merge($attributes, $command['attributes']);
1099              }
1100              $commandhtml[] = html_writer::link($command['url'], $command['text'], $attributes);
1101          } else {
1102              $commandhtml[] = $command;
1103          }
1104      }
1105      $output .= html_writer::tag('div', implode(' ', $commandhtml), array('class' => 'commands nav'));
1106  
1107      // Output link to post if required
1108      if ($link) {
1109          if (forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext)) {
1110              $langstring = 'discussthistopic';
1111          } else {
1112              $langstring = 'viewthediscussion';
1113          }
1114          if ($post->replies == 1) {
1115              $replystring = get_string('repliesone', 'forum', $post->replies);
1116          } else {
1117              $replystring = get_string('repliesmany', 'forum', $post->replies);
1118          }
1119          if (!empty($discussion->unread) && $discussion->unread !== '-') {
1120              $replystring .= ' <span class="sep">/</span> <span class="unread">';
1121              $unreadlink = new moodle_url($discussionlink, null, 'unread');
1122              if ($discussion->unread == 1) {
1123                  $replystring .= html_writer::link($unreadlink, get_string('unreadpostsone', 'forum'));
1124              } else {
1125                  $replystring .= html_writer::link($unreadlink, get_string('unreadpostsnumber', 'forum', $discussion->unread));
1126              }
1127              $replystring .= '</span>';
1128          }
1129  
1130          $output .= html_writer::start_tag('div', array('class'=>'link'));
1131          $output .= html_writer::link($discussionlink, get_string($langstring, 'forum'));
1132          $output .= '&nbsp;('.$replystring.')';
1133          $output .= html_writer::end_tag('div'); // link
1134      }
1135  
1136      // Output footer if required
1137      if ($footer) {
1138          $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
1139      }
1140  
1141      // Close remaining open divs
1142      $output .= html_writer::end_tag('div'); // content
1143      $output .= html_writer::end_tag('nav'); // row
1144      $output .= html_writer::end_tag('div'); // forumpost
1145  
1146      // Mark the forum post as read if required
1147      if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
1148          forum_tp_mark_post_read($USER->id, $post);
1149      }
1150  
1151      if ($return) {
1152          return $output;
1153      }
1154      echo $output;
1155      return;
1156  }
1157  
1158  /**
1159   * @global object
1160   * @global object
1161   * @uses FORUM_MODE_FLATNEWEST
1162   * @param object $course
1163   * @param object $cm
1164   * @param object $forum
1165   * @param object $discussion
1166   * @param object $post
1167   * @param object $mode
1168   * @param bool $reply
1169   * @param bool $forumtracked
1170   * @param array $posts
1171   * @return void
1172   * @deprecated since Moodle 3.7
1173   */
1174  function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
1175      debugging('forum_print_posts_flat() has been deprecated, ' .
1176          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1177      global $USER, $CFG;
1178  
1179      $link  = false;
1180  
1181      foreach ($posts as $post) {
1182          if (!$post->parent) {
1183              continue;
1184          }
1185          $post->subject = format_string($post->subject);
1186          $ownpost = ($USER->id == $post->userid);
1187  
1188          $postread = !empty($post->postread);
1189  
1190          forum_print_post_start($post);
1191          forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1192                               '', '', $postread, true, $forumtracked);
1193          forum_print_post_end($post);
1194      }
1195  }
1196  
1197  /**
1198   * @todo Document this function
1199   *
1200   * @global object
1201   * @global object
1202   * @uses CONTEXT_MODULE
1203   * @return void
1204   * @deprecated since Moodle 3.7
1205   */
1206  function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
1207      debugging('forum_print_posts_threaded() has been deprecated, ' .
1208          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1209      global $USER, $CFG;
1210  
1211      $link  = false;
1212  
1213      if (!empty($posts[$parent->id]->children)) {
1214          $posts = $posts[$parent->id]->children;
1215  
1216          $modcontext       = context_module::instance($cm->id);
1217          $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
1218  
1219          foreach ($posts as $post) {
1220  
1221              echo '<div class="indent">';
1222              if ($depth > 0) {
1223                  $ownpost = ($USER->id == $post->userid);
1224                  $post->subject = format_string($post->subject);
1225  
1226                  $postread = !empty($post->postread);
1227  
1228                  forum_print_post_start($post);
1229                  forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1230                                       '', '', $postread, true, $forumtracked);
1231                  forum_print_post_end($post);
1232              } else {
1233                  if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, true)) {
1234                      if (forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
1235                          // This post has been deleted but still exists and may have children.
1236                          $subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
1237                          $byline = '';
1238                      } else {
1239                          // The user can't see this post at all.
1240                          echo "</div>\n";
1241                          continue;
1242                      }
1243                  } else {
1244                      $by = new stdClass();
1245                      $by->name = fullname($post, $canviewfullnames);
1246                      $by->date = userdate_htmltime($post->modified);
1247                      $byline = ' ' . get_string("bynameondate", "forum", $by);
1248                      $subject = format_string($post->subject, true);
1249                  }
1250  
1251                  if ($forumtracked) {
1252                      if (!empty($post->postread)) {
1253                          $style = '<span class="forumthread read">';
1254                      } else {
1255                          $style = '<span class="forumthread unread">';
1256                      }
1257                  } else {
1258                      $style = '<span class="forumthread">';
1259                  }
1260  
1261                  echo $style;
1262                  echo "<a name='{$post->id}'></a>";
1263                  echo html_writer::link(new moodle_url('/mod/forum/discuss.php', [
1264                          'd' => $post->discussion,
1265                          'parent' => $post->id,
1266                      ]), $subject);
1267                  echo $byline;
1268                  echo "</span>";
1269              }
1270  
1271              forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
1272              echo "</div>\n";
1273          }
1274      }
1275  }
1276  
1277  /**
1278   * @todo Document this function
1279   * @global object
1280   * @global object
1281   * @return void
1282   * @deprecated since Moodle 3.7
1283   */
1284  function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
1285      debugging('forum_print_posts_nested() has been deprecated, ' .
1286          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1287      global $USER, $CFG;
1288  
1289      $link  = false;
1290  
1291      if (!empty($posts[$parent->id]->children)) {
1292          $posts = $posts[$parent->id]->children;
1293  
1294          foreach ($posts as $post) {
1295  
1296              echo '<div class="indent">';
1297              if (!isloggedin()) {
1298                  $ownpost = false;
1299              } else {
1300                  $ownpost = ($USER->id == $post->userid);
1301              }
1302  
1303              $post->subject = format_string($post->subject);
1304              $postread = !empty($post->postread);
1305  
1306              forum_print_post_start($post);
1307              forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1308                                   '', '', $postread, true, $forumtracked);
1309              forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
1310              forum_print_post_end($post);
1311              echo "</div>\n";
1312          }
1313      }
1314  }
1315  
1316  /**
1317   * Prints the discussion view screen for a forum.
1318   *
1319   * @param object $course The current course object.
1320   * @param object $forum Forum to be printed.
1321   * @param int $maxdiscussions
1322   * @param string $displayformat The display format to use (optional).
1323   * @param string $sort Sort arguments for database query (optional).
1324   * @param int $currentgroup
1325   * @param int $groupmode Group mode of the forum (optional).
1326   * @param int $page Page mode, page to display (optional).
1327   * @param int $perpage The maximum number of discussions per page(optional)
1328   * @param stdClass $cm
1329   * @deprecated since Moodle 3.7
1330   */
1331  function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $displayformat = 'plain', $sort = '',
1332                                          $currentgroup = -1, $groupmode = -1, $page = -1, $perpage = 100, $cm = null) {
1333      debugging('forum_print_latest_discussions has been deprecated.', DEBUG_DEVELOPER);
1334      global $CFG, $USER, $OUTPUT;
1335  
1336      require_once($CFG->dirroot . '/course/lib.php');
1337  
1338      if (!$cm) {
1339          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
1340              print_error('invalidcoursemodule');
1341          }
1342      }
1343      $context = context_module::instance($cm->id);
1344  
1345      if (empty($sort)) {
1346          $sort = forum_get_default_sort_order();
1347      }
1348  
1349      $olddiscussionlink = false;
1350  
1351      // Sort out some defaults.
1352      if ($perpage <= 0) {
1353          $perpage = 0;
1354          $page    = -1;
1355      }
1356  
1357      if ($maxdiscussions == 0) {
1358          // All discussions - backwards compatibility.
1359          $page    = -1;
1360          $perpage = 0;
1361          if ($displayformat == 'plain') {
1362              $displayformat = 'header';  // Abbreviate display by default.
1363          }
1364  
1365      } else if ($maxdiscussions > 0) {
1366          $page    = -1;
1367          $perpage = $maxdiscussions;
1368      }
1369  
1370      $fullpost = false;
1371      if ($displayformat == 'plain') {
1372          $fullpost = true;
1373      }
1374  
1375      // Decide if current user is allowed to see ALL the current discussions or not.
1376      // First check the group stuff.
1377      if ($currentgroup == -1 or $groupmode == -1) {
1378          $groupmode    = groups_get_activity_groupmode($cm, $course);
1379          $currentgroup = groups_get_activity_group($cm);
1380      }
1381  
1382      // Cache.
1383      $groups = array();
1384  
1385      // If the user can post discussions, then this is a good place to put the
1386      // button for it. We do not show the button if we are showing site news
1387      // and the current user is a guest.
1388  
1389      $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
1390      if (!$canstart and $forum->type !== 'news') {
1391          if (isguestuser() or !isloggedin()) {
1392              $canstart = true;
1393          }
1394          if (!is_enrolled($context) and !is_viewing($context)) {
1395              // Allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
1396              // normal users with temporary guest access see this button too, they are asked to enrol instead
1397              // do not show the button to users with suspended enrolments here.
1398              $canstart = enrol_selfenrol_available($course->id);
1399          }
1400      }
1401  
1402      if ($canstart) {
1403          switch ($forum->type) {
1404              case 'news':
1405              case 'blog':
1406                  $buttonadd = get_string('addanewtopic', 'forum');
1407                  break;
1408              case 'qanda':
1409                  $buttonadd = get_string('addanewquestion', 'forum');
1410                  break;
1411              default:
1412                  $buttonadd = get_string('addanewdiscussion', 'forum');
1413                  break;
1414          }
1415          $button = new single_button(new moodle_url('/mod/forum/post.php', ['forum' => $forum->id]), $buttonadd, 'get');
1416          $button->class = 'singlebutton forumaddnew';
1417          $button->formid = 'newdiscussionform';
1418          echo $OUTPUT->render($button);
1419  
1420      } else if (isguestuser() or !isloggedin() or $forum->type == 'news' or
1421          $forum->type == 'qanda' and !has_capability('mod/forum:addquestion', $context) or
1422          $forum->type != 'qanda' and !has_capability('mod/forum:startdiscussion', $context)) {
1423          // No button and no info.
1424          $ignore = true;
1425      } else if ($groupmode and !has_capability('moodle/site:accessallgroups', $context)) {
1426          // Inform users why they can not post new discussion.
1427          if (!$currentgroup) {
1428              if (!has_capability('mod/forum:canposttomygroups', $context)) {
1429                  echo $OUTPUT->notification(get_string('cannotadddiscussiongroup', 'forum'));
1430              } else {
1431                  echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
1432              }
1433          } else if (!groups_is_member($currentgroup)) {
1434              echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
1435          }
1436      }
1437  
1438      // Get all the recent discussions we're allowed to see.
1439  
1440      $getuserlastmodified = ($displayformat == 'header');
1441  
1442      $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage);
1443      if (!$discussions) {
1444          echo '<div class="forumnodiscuss">';
1445          if ($forum->type == 'news') {
1446              echo '('.get_string('nonews', 'forum').')';
1447          } else if ($forum->type == 'qanda') {
1448              echo '('.get_string('noquestions', 'forum').')';
1449          } else {
1450              echo '('.get_string('nodiscussions', 'forum').')';
1451          }
1452          echo "</div>\n";
1453          return;
1454      }
1455  
1456      $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $context);
1457      // If we want paging.
1458      if ($page != -1) {
1459          // Get the number of discussions found.
1460          $numdiscussions = forum_get_discussions_count($cm);
1461  
1462          // Show the paging bar.
1463          echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
1464          if ($numdiscussions > 1000) {
1465              // Saves some memory on sites with very large forums.
1466              $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage, $canseeprivatereplies);
1467          } else {
1468              $replies = forum_count_discussion_replies($forum->id, "", -1, -1, 0, $canseeprivatereplies);
1469          }
1470  
1471      } else {
1472          $replies = forum_count_discussion_replies($forum->id, "", -1, -1, 0, $canseeprivatereplies);
1473  
1474          if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
1475              $olddiscussionlink = true;
1476          }
1477      }
1478  
1479      $canviewparticipants = course_can_view_participants($context);
1480      $canviewhiddentimedposts = has_capability('mod/forum:viewhiddentimedposts', $context);
1481  
1482      $strdatestring = get_string('strftimerecentfull');
1483  
1484      // Check if the forum is tracked.
1485      if ($cantrack = forum_tp_can_track_forums($forum)) {
1486          $forumtracked = forum_tp_is_tracked($forum);
1487      } else {
1488          $forumtracked = false;
1489      }
1490  
1491      if ($forumtracked) {
1492          $unreads = forum_get_discussions_unread($cm);
1493      } else {
1494          $unreads = array();
1495      }
1496  
1497      if ($displayformat == 'header') {
1498          echo '<table cellspacing="0" class="forumheaderlist">';
1499          echo '<thead class="text-left">';
1500          echo '<tr>';
1501          echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
1502          echo '<th class="header author" scope="col">'.get_string('startedby', 'forum').'</th>';
1503          if ($groupmode > 0) {
1504              echo '<th class="header group" scope="col">'.get_string('group').'</th>';
1505          }
1506          if (has_capability('mod/forum:viewdiscussion', $context)) {
1507              echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
1508              // If the forum can be tracked, display the unread column.
1509              if ($cantrack) {
1510                  echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
1511                  if ($forumtracked) {
1512                      echo '<a title="'.get_string('markallread', 'forum').
1513                           '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
1514                           $forum->id.'&amp;mark=read&amp;return=/mod/forum/view.php&amp;sesskey=' . sesskey() . '">'.
1515                           $OUTPUT->pix_icon('t/markasread', get_string('markallread', 'forum')) . '</a>';
1516                  }
1517                  echo '</th>';
1518              }
1519          }
1520          echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
1521          if ((!is_guest($context, $USER) && isloggedin()) && has_capability('mod/forum:viewdiscussion', $context)) {
1522              if (\mod_forum\subscriptions::is_subscribable($forum)) {
1523                  echo '<th class="header discussionsubscription" scope="col">';
1524                  echo forum_get_discussion_subscription_icon_preloaders();
1525                  echo '</th>';
1526              }
1527          }
1528          echo '</tr>';
1529          echo '</thead>';
1530          echo '<tbody>';
1531      }
1532  
1533      foreach ($discussions as $discussion) {
1534          if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $context) &&
1535              !forum_user_has_posted($forum->id, $discussion->discussion, $USER->id)) {
1536              $canviewparticipants = false;
1537          }
1538  
1539          if (!empty($replies[$discussion->discussion])) {
1540              $discussion->replies = $replies[$discussion->discussion]->replies;
1541              $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
1542          } else {
1543              $discussion->replies = 0;
1544          }
1545  
1546          // SPECIAL CASE: The front page can display a news item post to non-logged in users.
1547          // All posts are read in this case.
1548          if (!$forumtracked) {
1549              $discussion->unread = '-';
1550          } else if (empty($USER)) {
1551              $discussion->unread = 0;
1552          } else {
1553              if (empty($unreads[$discussion->discussion])) {
1554                  $discussion->unread = 0;
1555              } else {
1556                  $discussion->unread = $unreads[$discussion->discussion];
1557              }
1558          }
1559  
1560          if (isloggedin()) {
1561              $ownpost = ($discussion->userid == $USER->id);
1562          } else {
1563              $ownpost = false;
1564          }
1565          // Use discussion name instead of subject of first post.
1566          $discussion->subject = $discussion->name;
1567  
1568          switch ($displayformat) {
1569              case 'header':
1570                  if ($groupmode > 0) {
1571                      if (isset($groups[$discussion->groupid])) {
1572                          $group = $groups[$discussion->groupid];
1573                      } else {
1574                          $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
1575                      }
1576                  } else {
1577                      $group = -1;
1578                  }
1579                  forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
1580                      $canviewparticipants, $context, $canviewhiddentimedposts);
1581              break;
1582              default:
1583                  $link = false;
1584  
1585                  if ($discussion->replies) {
1586                      $link = true;
1587                  } else {
1588                      $modcontext = context_module::instance($cm->id);
1589                      $link = forum_user_can_see_discussion($forum, $discussion, $modcontext, $USER);
1590                  }
1591  
1592                  $discussion->forum = $forum->id;
1593  
1594                  forum_print_post_start($discussion);
1595                  forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
1596                          '', null, true, $forumtracked);
1597                  forum_print_post_end($discussion);
1598              break;
1599          }
1600      }
1601  
1602      if ($displayformat == "header") {
1603          echo '</tbody>';
1604          echo '</table>';
1605      }
1606  
1607      if ($olddiscussionlink) {
1608          if ($forum->type == 'news') {
1609              $strolder = get_string('oldertopics', 'forum');
1610          } else {
1611              $strolder = get_string('olderdiscussions', 'forum');
1612          }
1613          echo '<div class="forumolddiscuss">';
1614          echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
1615          echo $strolder.'</a> ...</div>';
1616      }
1617  
1618      if ($page != -1) {
1619          // Show the paging bar.
1620          echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
1621      }
1622  }
1623  
1624  /**
1625   * Count the number of replies to the specified post.
1626   *
1627   * @param object $post
1628   * @param bool $children
1629   * @return int
1630   * @deprecated since Moodle 3.7
1631   * @todo MDL-65252 This will be removed in Moodle 4.1
1632   */
1633  function forum_count_replies($post, $children = true) {
1634      global $USER;
1635      debugging('forum_count_replies has been deprecated. Please use the Post vault instead.', DEBUG_DEVELOPER);
1636  
1637      if (!$children) {
1638          return $DB->count_records('forum_posts', array('parent' => $post->id));
1639      }
1640  
1641      $entityfactory = mod_forum\local\container::get_entity_factory();
1642      $postentity = $entityfactory->get_post_from_stdclass($post);
1643  
1644      $vaultfactory = mod_forum\local\container::get_vault_factory();
1645      $postvault = $vaultfactory->get_post_vault();
1646  
1647      return $postvault->get_reply_count_for_post_id_in_discussion_id(
1648              $USER,
1649              $postentity->get_id(),
1650              $postentity->get_discussion_id(),
1651              true
1652          );
1653  }
1654  
1655  /**
1656   * @deprecated since Moodle 3.8
1657   */
1658  function forum_scale_used() {
1659      throw new coding_exception('forum_scale_used() can not be used anymore. Plugins can implement ' .
1660          '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
1661  }
1662  
1663  /**
1664   * Return grade for given user or all users.
1665   *
1666   * @deprecated since Moodle 3.8
1667   * @param object $forum
1668   * @param int $userid optional user id, 0 means all users
1669   * @return array array of grades, false if none
1670   */
1671  function forum_get_user_grades($forum, $userid = 0) {
1672      global $CFG;
1673  
1674      require_once($CFG->dirroot.'/rating/lib.php');
1675  
1676      $ratingoptions = (object) [
1677          'component' => 'mod_forum',
1678          'ratingarea' => 'post',
1679          'contextid' => $contextid,
1680  
1681          'modulename' => 'forum',
1682          'moduleid  ' => $forum->id,
1683          'userid' => $userid,
1684          'aggregationmethod' => $forum->assessed,
1685          'scaleid' => $forum->scale,
1686          'itemtable' => 'forum_posts',
1687          'itemtableusercolumn' => 'userid',
1688      ];
1689  
1690      $rm = new rating_manager();
1691      return $rm->get_user_grades($ratingoptions);
1692  }