Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  // 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->currentlogin);
 396      unset($user->description);
 397      unset($user->descriptionformat);
 398  }
 399  
 400  /**
 401   * Function to be run periodically according to the scheduled task.
 402   *
 403   * Finds all posts that have yet to be mailed out, and mails them out to all subscribers as well as other maintance
 404   * tasks.
 405   *
 406   * @deprecated since Moodle 3.7
 407   */
 408  function forum_cron() {
 409      debugging("forum_cron() has been deprecated and replaced with new tasks. Please uses these instead.",
 410              DEBUG_DEVELOPER);
 411  }
 412  
 413  /**
 414   * Prints a forum discussion
 415   *
 416   * @uses CONTEXT_MODULE
 417   * @uses FORUM_MODE_FLATNEWEST
 418   * @uses FORUM_MODE_FLATOLDEST
 419   * @uses FORUM_MODE_THREADED
 420   * @uses FORUM_MODE_NESTED
 421   * @param stdClass $course
 422   * @param stdClass $cm
 423   * @param stdClass $forum
 424   * @param stdClass $discussion
 425   * @param stdClass $post
 426   * @param int $mode
 427   * @param mixed $canreply
 428   * @param bool $canrate
 429   * @deprecated since Moodle 3.7
 430   */
 431  function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
 432      debugging('forum_print_discussion() has been deprecated, ' .
 433          'please use \mod_forum\local\renderers\discussion instead.', DEBUG_DEVELOPER);
 434  
 435      global $USER, $CFG;
 436  
 437      require_once($CFG->dirroot.'/rating/lib.php');
 438  
 439      $ownpost = (isloggedin() && $USER->id == $post->userid);
 440  
 441      $modcontext = context_module::instance($cm->id);
 442      if ($canreply === NULL) {
 443          $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
 444      } else {
 445          $reply = $canreply;
 446      }
 447  
 448      // $cm holds general cache for forum functions
 449      $cm->cache = new stdClass;
 450      $cm->cache->groups      = groups_get_all_groups($course->id, 0, $cm->groupingid);
 451      $cm->cache->usersgroups = array();
 452  
 453      $posters = array();
 454  
 455      // preload all posts - TODO: improve...
 456      if ($mode == FORUM_MODE_FLATNEWEST) {
 457          $sort = "p.created DESC";
 458      } else {
 459          $sort = "p.created ASC";
 460      }
 461  
 462      $forumtracked = forum_tp_is_tracked($forum);
 463      $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
 464      $post = $posts[$post->id];
 465  
 466      foreach ($posts as $pid=>$p) {
 467          $posters[$p->userid] = $p->userid;
 468      }
 469  
 470      // preload all groups of ppl that posted in this discussion
 471      if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
 472          foreach($postersgroups as $pg) {
 473              if (!isset($cm->cache->usersgroups[$pg->userid])) {
 474                  $cm->cache->usersgroups[$pg->userid] = array();
 475              }
 476              $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
 477          }
 478          unset($postersgroups);
 479      }
 480  
 481      //load ratings
 482      if ($forum->assessed != RATING_AGGREGATE_NONE) {
 483          $ratingoptions = new stdClass;
 484          $ratingoptions->context = $modcontext;
 485          $ratingoptions->component = 'mod_forum';
 486          $ratingoptions->ratingarea = 'post';
 487          $ratingoptions->items = $posts;
 488          $ratingoptions->aggregate = $forum->assessed;//the aggregation method
 489          $ratingoptions->scaleid = $forum->scale;
 490          $ratingoptions->userid = $USER->id;
 491          if ($forum->type == 'single' or !$discussion->id) {
 492              $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
 493          } else {
 494              $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
 495          }
 496          $ratingoptions->assesstimestart = $forum->assesstimestart;
 497          $ratingoptions->assesstimefinish = $forum->assesstimefinish;
 498  
 499          $rm = new rating_manager();
 500          $posts = $rm->get_ratings($ratingoptions);
 501      }
 502  
 503  
 504      $post->forum = $forum->id;   // Add the forum id to the post object, later used by forum_print_post
 505      $post->forumtype = $forum->type;
 506  
 507      $post->subject = format_string($post->subject);
 508  
 509      $postread = !empty($post->postread);
 510  
 511      forum_print_post_start($post);
 512      forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
 513                           '', '', $postread, true, $forumtracked);
 514  
 515      switch ($mode) {
 516          case FORUM_MODE_FLATOLDEST :
 517          case FORUM_MODE_FLATNEWEST :
 518          default:
 519              forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
 520              break;
 521  
 522          case FORUM_MODE_THREADED :
 523              forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
 524              break;
 525  
 526          case FORUM_MODE_NESTED :
 527              forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
 528              break;
 529      }
 530      forum_print_post_end($post);
 531  }
 532  
 533  
 534  /**
 535   * Return a static array of posts that are open.
 536   *
 537   * @return array
 538   * @deprecated since Moodle 3.7
 539   */
 540  function forum_post_nesting_cache() {
 541      debugging('forum_post_nesting_cache() has been deprecated, ' .
 542          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 543      static $nesting = array();
 544      return $nesting;
 545  }
 546  
 547  /**
 548   * Return true for the first time this post was started
 549   *
 550   * @param int $id The id of the post to start
 551   * @return bool
 552   * @deprecated since Moodle 3.7
 553   */
 554  function forum_should_start_post_nesting($id) {
 555      debugging('forum_should_start_post_nesting() has been deprecated, ' .
 556          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 557      $cache = forum_post_nesting_cache();
 558      if (!array_key_exists($id, $cache)) {
 559          $cache[$id] = 1;
 560          return true;
 561      } else {
 562          $cache[$id]++;
 563          return false;
 564      }
 565  }
 566  
 567  /**
 568   * Return true when all the opens are nested with a close.
 569   *
 570   * @param int $id The id of the post to end
 571   * @return bool
 572   * @deprecated since Moodle 3.7
 573   */
 574  function forum_should_end_post_nesting($id) {
 575      debugging('forum_should_end_post_nesting() has been deprecated, ' .
 576          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 577      $cache = forum_post_nesting_cache();
 578      if (!array_key_exists($id, $cache)) {
 579          return true;
 580      } else {
 581          $cache[$id]--;
 582          if ($cache[$id] == 0) {
 583              unset($cache[$id]);
 584              return true;
 585          }
 586      }
 587      return false;
 588  }
 589  
 590  /**
 591   * Start a forum post container
 592   *
 593   * @param object $post The post to print.
 594   * @param bool $return Return the string or print it
 595   * @return string
 596   * @deprecated since Moodle 3.7
 597   */
 598  function forum_print_post_start($post, $return = false) {
 599      debugging('forum_print_post_start() has been deprecated, ' .
 600          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 601      $output = '';
 602  
 603      if (forum_should_start_post_nesting($post->id)) {
 604          $attributes = [
 605              'id' => 'p'.$post->id,
 606              'tabindex' => -1,
 607              'class' => 'relativelink'
 608          ];
 609          $output .= html_writer::start_tag('article', $attributes);
 610      }
 611      if ($return) {
 612          return $output;
 613      }
 614      echo $output;
 615      return;
 616  }
 617  
 618  /**
 619   * End a forum post container
 620   *
 621   * @param object $post The post to print.
 622   * @param bool $return Return the string or print it
 623   * @return string
 624   * @deprecated since Moodle 3.7
 625   */
 626  function forum_print_post_end($post, $return = false) {
 627      debugging('forum_print_post_end() has been deprecated, ' .
 628          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 629      $output = '';
 630  
 631      if (forum_should_end_post_nesting($post->id)) {
 632          $output .= html_writer::end_tag('article');
 633      }
 634      if ($return) {
 635          return $output;
 636      }
 637      echo $output;
 638      return;
 639  }
 640  
 641  /**
 642   * Print a forum post
 643   * This function should always be surrounded with calls to forum_print_post_start
 644   * and forum_print_post_end to create the surrounding container for the post.
 645   * Replies can be nested before forum_print_post_end and should reflect the structure of
 646   * thread.
 647   *
 648   * @global object
 649   * @global object
 650   * @uses FORUM_MODE_THREADED
 651   * @uses PORTFOLIO_FORMAT_PLAINHTML
 652   * @uses PORTFOLIO_FORMAT_FILE
 653   * @uses PORTFOLIO_FORMAT_RICHHTML
 654   * @uses PORTFOLIO_ADD_TEXT_LINK
 655   * @uses CONTEXT_MODULE
 656   * @param object $post The post to print.
 657   * @param object $discussion
 658   * @param object $forum
 659   * @param object $cm
 660   * @param object $course
 661   * @param boolean $ownpost Whether this post belongs to the current user.
 662   * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
 663   * @param boolean $link Just print a shortened version of the post as a link to the full post.
 664   * @param string $footer Extra stuff to print after the message.
 665   * @param string $highlight Space-separated list of terms to highlight.
 666   * @param int $post_read true, false or -99. If we already know whether this user
 667   *          has read this post, pass that in, otherwise, pass in -99, and this
 668   *          function will work it out.
 669   * @param boolean $dummyifcantsee When forum_user_can_see_post says that
 670   *          the current user can't see this post, if this argument is true
 671   *          (the default) then print a dummy 'you can't see this post' post.
 672   *          If false, don't output anything at all.
 673   * @param bool|null $istracked
 674   * @return void
 675   * @deprecated since Moodle 3.7
 676   */
 677  function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
 678                            $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
 679      debugging('forum_print_post() has been deprecated, ' .
 680          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
 681      global $USER, $CFG, $OUTPUT;
 682  
 683      require_once($CFG->libdir . '/filelib.php');
 684  
 685      // String cache
 686      static $str;
 687      // This is an extremely hacky way to ensure we only print the 'unread' anchor
 688      // the first time we encounter an unread post on a page. Ideally this would
 689      // be moved into the caller somehow, and be better testable. But at the time
 690      // of dealing with this bug, this static workaround was the most surgical and
 691      // it fits together with only printing th unread anchor id once on a given page.
 692      static $firstunreadanchorprinted = false;
 693  
 694      $modcontext = context_module::instance($cm->id);
 695  
 696      $post->course = $course->id;
 697      $post->forum  = $forum->id;
 698      $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
 699      if (!empty($CFG->enableplagiarism)) {
 700          require_once($CFG->libdir.'/plagiarismlib.php');
 701          $post->message .= plagiarism_get_links(array('userid' => $post->userid,
 702              'content' => $post->message,
 703              'cmid' => $cm->id,
 704              'course' => $post->course,
 705              'forum' => $post->forum));
 706      }
 707  
 708      // caching
 709      if (!isset($cm->cache)) {
 710          $cm->cache = new stdClass;
 711      }
 712  
 713      if (!isset($cm->cache->caps)) {
 714          $cm->cache->caps = array();
 715          $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
 716          $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
 717          $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
 718          $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
 719          $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
 720          $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
 721          $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
 722          $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
 723          $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
 724      }
 725  
 726      if (!isset($cm->uservisible)) {
 727          $cm->uservisible = \core_availability\info_module::is_user_visible($cm, 0, false);
 728      }
 729  
 730      if ($istracked && is_null($postisread)) {
 731          $postisread = forum_tp_is_post_read($USER->id, $post);
 732      }
 733  
 734      if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
 735          // Do _not_ check the deleted flag - we need to display a different UI.
 736          $output = '';
 737          if (!$dummyifcantsee) {
 738              if ($return) {
 739                  return $output;
 740              }
 741              echo $output;
 742              return;
 743          }
 744  
 745          $output .= html_writer::start_tag('div', array('class' => 'forumpost clearfix',
 746                                                         'aria-label' => get_string('hiddenforumpost', 'forum')));
 747          $output .= html_writer::start_tag('header', array('class' => 'row header'));
 748          $output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation')); // Picture.
 749          if ($post->parent) {
 750              $output .= html_writer::start_tag('div', array('class' => 'topic'));
 751          } else {
 752              $output .= html_writer::start_tag('div', array('class' => 'topic starter'));
 753          }
 754          $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class' => 'subject',
 755                                                                                             'role' => 'header',
 756                                                                                             'id' => ('headp' . $post->id))); // Subject.
 757          $authorclasses = array('class' => 'author');
 758          $output .= html_writer::tag('address', get_string('forumauthorhidden', 'forum'), $authorclasses); // Author.
 759          $output .= html_writer::end_tag('div');
 760          $output .= html_writer::end_tag('header'); // Header.
 761          $output .= html_writer::start_tag('div', array('class'=>'row'));
 762          $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
 763          $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
 764          $output .= html_writer::end_tag('div'); // row
 765          $output .= html_writer::end_tag('div'); // forumpost
 766  
 767          if ($return) {
 768              return $output;
 769          }
 770          echo $output;
 771          return;
 772      }
 773  
 774      if (!empty($post->deleted)) {
 775          // Note: Posts marked as deleted are still returned by the above forum_user_can_post because it is required for
 776          // nesting of posts.
 777          $output = '';
 778          if (!$dummyifcantsee) {
 779              if ($return) {
 780                  return $output;
 781              }
 782              echo $output;
 783              return;
 784          }
 785          $output .= html_writer::start_tag('div', [
 786                  'class' => 'forumpost clearfix',
 787                  'aria-label' => get_string('forumbodydeleted', 'forum'),
 788              ]);
 789  
 790          $output .= html_writer::start_tag('header', array('class' => 'row header'));
 791          $output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation'));
 792  
 793          $classes = ['topic'];
 794          if (!empty($post->parent)) {
 795              $classes[] = 'starter';
 796          }
 797          $output .= html_writer::start_tag('div', ['class' => implode(' ', $classes)]);
 798  
 799          // Subject.
 800          $output .= html_writer::tag('div', get_string('forumsubjectdeleted', 'forum'), [
 801                  'class' => 'subject',
 802                  'role' => 'header',
 803                  'id' => ('headp' . $post->id)
 804              ]);
 805  
 806          // Author.
 807          $output .= html_writer::tag('address', '', ['class' => 'author']);
 808  
 809          $output .= html_writer::end_tag('div');
 810          $output .= html_writer::end_tag('header'); // End header.
 811          $output .= html_writer::start_tag('div', ['class' => 'row']);
 812          $output .= html_writer::tag('div', '&nbsp;', ['class' => 'left side']); // Groups.
 813          $output .= html_writer::tag('div', get_string('forumbodydeleted', 'forum'), ['class' => 'content']); // Content.
 814          $output .= html_writer::end_tag('div'); // End row.
 815          $output .= html_writer::end_tag('div'); // End forumpost.
 816  
 817          if ($return) {
 818              return $output;
 819          }
 820          echo $output;
 821          return;
 822      }
 823  
 824      if (empty($str)) {
 825          $str = new stdClass;
 826          $str->edit         = get_string('edit', 'forum');
 827          $str->delete       = get_string('delete', 'forum');
 828          $str->reply        = get_string('reply', 'forum');
 829          $str->parent       = get_string('parent', 'forum');
 830          $str->pruneheading = get_string('pruneheading', 'forum');
 831          $str->prune        = get_string('prune', 'forum');
 832          $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
 833          $str->markread     = get_string('markread', 'forum');
 834          $str->markunread   = get_string('markunread', 'forum');
 835      }
 836  
 837      $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
 838  
 839      // Build an object that represents the posting user
 840      $postuser = new stdClass;
 841      $postuserfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
 842      $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
 843      $postuser->id = $post->userid;
 844      $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
 845      $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
 846  
 847      // Prepare the groups the posting user belongs to
 848      if (isset($cm->cache->usersgroups)) {
 849          $groups = array();
 850          if (isset($cm->cache->usersgroups[$post->userid])) {
 851              foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
 852                  $groups[$gid] = $cm->cache->groups[$gid];
 853              }
 854          }
 855      } else {
 856          $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
 857      }
 858  
 859      // Prepare the attachements for the post, files then images
 860      list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
 861  
 862      // Determine if we need to shorten this post
 863      $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
 864  
 865      // Prepare an array of commands
 866      $commands = array();
 867  
 868      // Add a permalink.
 869      $permalink = new moodle_url($discussionlink);
 870      $permalink->set_anchor('p' . $post->id);
 871      $commands[] = array('url' => $permalink, 'text' => get_string('permalink', 'forum'), 'attributes' => ['rel' => 'bookmark']);
 872  
 873      // SPECIAL CASE: The front page can display a news item post to non-logged in users.
 874      // Don't display the mark read / unread controls in this case.
 875      if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
 876          $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
 877          $text = $str->markunread;
 878          if (!$postisread) {
 879              $url->param('mark', 'read');
 880              $text = $str->markread;
 881          }
 882          if ($str->displaymode == FORUM_MODE_THREADED) {
 883              $url->param('parent', $post->parent);
 884          } else {
 885              $url->set_anchor('p'.$post->id);
 886          }
 887          $commands[] = array('url'=>$url, 'text'=>$text, 'attributes' => ['rel' => 'bookmark']);
 888      }
 889  
 890      // Zoom in to the parent specifically
 891      if ($post->parent) {
 892          $url = new moodle_url($discussionlink);
 893          if ($str->displaymode == FORUM_MODE_THREADED) {
 894              $url->param('parent', $post->parent);
 895          } else {
 896              $url->set_anchor('p'.$post->parent);
 897          }
 898          $commands[] = array('url'=>$url, 'text'=>$str->parent, 'attributes' => ['rel' => 'bookmark']);
 899      }
 900  
 901      // Hack for allow to edit news posts those are not displayed yet until they are displayed
 902      $age = time() - $post->created;
 903      if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
 904          $age = 0;
 905      }
 906  
 907      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
 908          if (has_capability('moodle/course:manageactivities', $modcontext)) {
 909              // The first post in single simple is the forum description.
 910              $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
 911          }
 912      } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
 913          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
 914      }
 915  
 916      if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
 917          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
 918      }
 919  
 920      if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
 921          // Do not allow deleting of first post in single simple type.
 922      } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
 923          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
 924      }
 925  
 926      if ($reply) {
 927          $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
 928      }
 929  
 930      if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
 931          $p = array('postid' => $post->id);
 932          require_once($CFG->libdir.'/portfoliolib.php');
 933          $button = new portfolio_add_button();
 934          $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
 935          if (empty($attachments)) {
 936              $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
 937          } else {
 938              $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
 939          }
 940  
 941          $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
 942          if (!empty($porfoliohtml)) {
 943              $commands[] = $porfoliohtml;
 944          }
 945      }
 946      // Finished building commands
 947  
 948  
 949      // Begin output
 950  
 951      $output  = '';
 952  
 953      if ($istracked) {
 954          if ($postisread) {
 955              $forumpostclass = ' read';
 956          } else {
 957              $forumpostclass = ' unread';
 958              // If this is the first unread post printed then give it an anchor and id of unread.
 959              if (!$firstunreadanchorprinted) {
 960                  $output .= html_writer::tag('a', '', array('id' => 'unread'));
 961                  $firstunreadanchorprinted = true;
 962              }
 963          }
 964      } else {
 965          // ignore trackign status if not tracked or tracked param missing
 966          $forumpostclass = '';
 967      }
 968  
 969      $topicclass = '';
 970      if (empty($post->parent)) {
 971          $topicclass = ' firstpost starter';
 972      }
 973  
 974      if (!empty($post->lastpost)) {
 975          $forumpostclass .= ' lastpost';
 976      }
 977  
 978      // Flag to indicate whether we should hide the author or not.
 979      $authorhidden = forum_is_author_hidden($post, $forum);
 980      $postbyuser = new stdClass;
 981      $postbyuser->post = $post->subject;
 982      $postbyuser->user = $postuser->fullname;
 983      $discussionbyuser = get_string('postbyuser', 'forum', $postbyuser);
 984      // Begin forum post.
 985      $output .= html_writer::start_div('forumpost clearfix' . $forumpostclass . $topicclass,
 986          ['aria-label' => $discussionbyuser]);
 987      // Begin header row.
 988      $output .= html_writer::start_tag('header', ['class' => 'row header clearfix']);
 989  
 990      // User picture.
 991      if (!$authorhidden) {
 992          $picture = $OUTPUT->user_picture($postuser, ['courseid' => $course->id]);
 993          $output .= html_writer::div($picture, 'left picture', ['role' => 'presentation']);
 994          $topicclass = 'topic' . $topicclass;
 995      }
 996  
 997      // Begin topic column.
 998      $output .= html_writer::start_div($topicclass);
 999      $postsubject = $post->subject;
1000      if (empty($post->subjectnoformat)) {
1001          $postsubject = format_string($postsubject);
1002      }
1003      $output .= html_writer::div($postsubject, 'subject', ['role' => 'heading', 'aria-level' => '1', 'id' => ('headp' . $post->id)]);
1004  
1005      if ($authorhidden) {
1006          $bytext = userdate_htmltime($post->created);
1007      } else {
1008          $by = new stdClass();
1009          $by->date = userdate_htmltime($post->created);
1010          $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
1011          $bytext = get_string('bynameondate', 'forum', $by);
1012      }
1013      $bytextoptions = [
1014          'class' => 'author'
1015      ];
1016      $output .= html_writer::tag('address', $bytext, $bytextoptions);
1017      // End topic column.
1018      $output .= html_writer::end_div();
1019  
1020      // End header row.
1021      $output .= html_writer::end_tag('header');
1022  
1023      // Row with the forum post content.
1024      $output .= html_writer::start_div('row maincontent clearfix');
1025      // Show if author is not hidden or we have groups.
1026      if (!$authorhidden || $groups) {
1027          $output .= html_writer::start_div('left');
1028          $groupoutput = '';
1029          if ($groups) {
1030              $groupoutput = print_group_picture($groups, $course->id, false, true, true);
1031          }
1032          if (empty($groupoutput)) {
1033              $groupoutput = '&nbsp;';
1034          }
1035          $output .= html_writer::div($groupoutput, 'grouppictures');
1036          $output .= html_writer::end_div(); // Left side.
1037      }
1038  
1039      $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
1040      $output .= html_writer::start_tag('div', array('class'=>'content'));
1041  
1042      $options = new stdClass;
1043      $options->para    = false;
1044      $options->trusted = $post->messagetrust;
1045      $options->context = $modcontext;
1046      if ($shortenpost) {
1047          // Prepare shortened version by filtering the text then shortening it.
1048          $postclass    = 'shortenedpost';
1049          $postcontent  = format_text($post->message, $post->messageformat, $options);
1050          $postcontent  = shorten_text($postcontent, $CFG->forum_shortpost);
1051          $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
1052          $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
1053              array('class'=>'post-word-count'));
1054      } else {
1055          // Prepare whole post
1056          $postclass    = 'fullpost';
1057          $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
1058          if (!empty($highlight)) {
1059              $postcontent = highlight($highlight, $postcontent);
1060          }
1061          if (!empty($forum->displaywordcount)) {
1062              $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($postcontent)),
1063                  array('class'=>'post-word-count'));
1064          }
1065          $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
1066      }
1067  
1068      if (\core_tag_tag::is_enabled('mod_forum', 'forum_posts')) {
1069          $postcontent .= $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id), null, 'forum-tags');
1070      }
1071  
1072      // Output the post content
1073      $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
1074      $output .= html_writer::end_tag('div'); // Content
1075      $output .= html_writer::end_tag('div'); // Content mask
1076      $output .= html_writer::end_tag('div'); // Row
1077  
1078      $output .= html_writer::start_tag('nav', array('class' => 'row side'));
1079      $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
1080      $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
1081  
1082      if (!empty($attachments)) {
1083          $output .= html_writer::tag('div', $attachments, array('class' => 'attachments'));
1084      }
1085  
1086      // Output ratings
1087      if (!empty($post->rating)) {
1088          $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
1089      }
1090  
1091      // Output the commands
1092      $commandhtml = array();
1093      foreach ($commands as $command) {
1094          if (is_array($command)) {
1095              $attributes = ['class' => 'nav-item nav-link'];
1096              if (isset($command['attributes'])) {
1097                  $attributes = array_merge($attributes, $command['attributes']);
1098              }
1099              $commandhtml[] = html_writer::link($command['url'], $command['text'], $attributes);
1100          } else {
1101              $commandhtml[] = $command;
1102          }
1103      }
1104      $output .= html_writer::tag('div', implode(' ', $commandhtml), array('class' => 'commands nav'));
1105  
1106      // Output link to post if required
1107      if ($link) {
1108          if (forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext)) {
1109              $langstring = 'discussthistopic';
1110          } else {
1111              $langstring = 'viewthediscussion';
1112          }
1113          if ($post->replies == 1) {
1114              $replystring = get_string('repliesone', 'forum', $post->replies);
1115          } else {
1116              $replystring = get_string('repliesmany', 'forum', $post->replies);
1117          }
1118          if (!empty($discussion->unread) && $discussion->unread !== '-') {
1119              $replystring .= ' <span class="sep">/</span> <span class="unread">';
1120              $unreadlink = new moodle_url($discussionlink, null, 'unread');
1121              if ($discussion->unread == 1) {
1122                  $replystring .= html_writer::link($unreadlink, get_string('unreadpostsone', 'forum'));
1123              } else {
1124                  $replystring .= html_writer::link($unreadlink, get_string('unreadpostsnumber', 'forum', $discussion->unread));
1125              }
1126              $replystring .= '</span>';
1127          }
1128  
1129          $output .= html_writer::start_tag('div', array('class'=>'link'));
1130          $output .= html_writer::link($discussionlink, get_string($langstring, 'forum'));
1131          $output .= '&nbsp;('.$replystring.')';
1132          $output .= html_writer::end_tag('div'); // link
1133      }
1134  
1135      // Output footer if required
1136      if ($footer) {
1137          $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
1138      }
1139  
1140      // Close remaining open divs
1141      $output .= html_writer::end_tag('div'); // content
1142      $output .= html_writer::end_tag('nav'); // row
1143      $output .= html_writer::end_tag('div'); // forumpost
1144  
1145      // Mark the forum post as read if required
1146      if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
1147          forum_tp_mark_post_read($USER->id, $post);
1148      }
1149  
1150      if ($return) {
1151          return $output;
1152      }
1153      echo $output;
1154      return;
1155  }
1156  
1157  /**
1158   * @global object
1159   * @global object
1160   * @uses FORUM_MODE_FLATNEWEST
1161   * @param object $course
1162   * @param object $cm
1163   * @param object $forum
1164   * @param object $discussion
1165   * @param object $post
1166   * @param object $mode
1167   * @param bool $reply
1168   * @param bool $forumtracked
1169   * @param array $posts
1170   * @return void
1171   * @deprecated since Moodle 3.7
1172   */
1173  function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
1174      debugging('forum_print_posts_flat() has been deprecated, ' .
1175          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1176      global $USER, $CFG;
1177  
1178      $link  = false;
1179  
1180      foreach ($posts as $post) {
1181          if (!$post->parent) {
1182              continue;
1183          }
1184          $post->subject = format_string($post->subject);
1185          $ownpost = ($USER->id == $post->userid);
1186  
1187          $postread = !empty($post->postread);
1188  
1189          forum_print_post_start($post);
1190          forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1191                               '', '', $postread, true, $forumtracked);
1192          forum_print_post_end($post);
1193      }
1194  }
1195  
1196  /**
1197   * @todo Document this function
1198   *
1199   * @global object
1200   * @global object
1201   * @uses CONTEXT_MODULE
1202   * @return void
1203   * @deprecated since Moodle 3.7
1204   */
1205  function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
1206      debugging('forum_print_posts_threaded() has been deprecated, ' .
1207          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1208      global $USER, $CFG;
1209  
1210      $link  = false;
1211  
1212      if (!empty($posts[$parent->id]->children)) {
1213          $posts = $posts[$parent->id]->children;
1214  
1215          $modcontext       = context_module::instance($cm->id);
1216          $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
1217  
1218          foreach ($posts as $post) {
1219  
1220              echo '<div class="indent">';
1221              if ($depth > 0) {
1222                  $ownpost = ($USER->id == $post->userid);
1223                  $post->subject = format_string($post->subject);
1224  
1225                  $postread = !empty($post->postread);
1226  
1227                  forum_print_post_start($post);
1228                  forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1229                                       '', '', $postread, true, $forumtracked);
1230                  forum_print_post_end($post);
1231              } else {
1232                  if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, true)) {
1233                      if (forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
1234                          // This post has been deleted but still exists and may have children.
1235                          $subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
1236                          $byline = '';
1237                      } else {
1238                          // The user can't see this post at all.
1239                          echo "</div>\n";
1240                          continue;
1241                      }
1242                  } else {
1243                      $by = new stdClass();
1244                      $by->name = fullname($post, $canviewfullnames);
1245                      $by->date = userdate_htmltime($post->modified);
1246                      $byline = ' ' . get_string("bynameondate", "forum", $by);
1247                      $subject = format_string($post->subject, true);
1248                  }
1249  
1250                  if ($forumtracked) {
1251                      if (!empty($post->postread)) {
1252                          $style = '<span class="forumthread read">';
1253                      } else {
1254                          $style = '<span class="forumthread unread">';
1255                      }
1256                  } else {
1257                      $style = '<span class="forumthread">';
1258                  }
1259  
1260                  echo $style;
1261                  echo "<a name='{$post->id}'></a>";
1262                  echo html_writer::link(new moodle_url('/mod/forum/discuss.php', [
1263                          'd' => $post->discussion,
1264                          'parent' => $post->id,
1265                      ]), $subject);
1266                  echo $byline;
1267                  echo "</span>";
1268              }
1269  
1270              forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
1271              echo "</div>\n";
1272          }
1273      }
1274  }
1275  
1276  /**
1277   * @todo Document this function
1278   * @global object
1279   * @global object
1280   * @return void
1281   * @deprecated since Moodle 3.7
1282   */
1283  function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
1284      debugging('forum_print_posts_nested() has been deprecated, ' .
1285          'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1286      global $USER, $CFG;
1287  
1288      $link  = false;
1289  
1290      if (!empty($posts[$parent->id]->children)) {
1291          $posts = $posts[$parent->id]->children;
1292  
1293          foreach ($posts as $post) {
1294  
1295              echo '<div class="indent">';
1296              if (!isloggedin()) {
1297                  $ownpost = false;
1298              } else {
1299                  $ownpost = ($USER->id == $post->userid);
1300              }
1301  
1302              $post->subject = format_string($post->subject);
1303              $postread = !empty($post->postread);
1304  
1305              forum_print_post_start($post);
1306              forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1307                                   '', '', $postread, true, $forumtracked);
1308              forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
1309              forum_print_post_end($post);
1310              echo "</div>\n";
1311          }
1312      }
1313  }
1314  
1315  /**
1316   * Prints the discussion view screen for a forum.
1317   *
1318   * @param object $course The current course object.
1319   * @param object $forum Forum to be printed.
1320   * @param int $maxdiscussions
1321   * @param string $displayformat The display format to use (optional).
1322   * @param string $sort Sort arguments for database query (optional).
1323   * @param int $currentgroup
1324   * @param int $groupmode Group mode of the forum (optional).
1325   * @param int $page Page mode, page to display (optional).
1326   * @param int $perpage The maximum number of discussions per page(optional)
1327   * @param stdClass $cm
1328   * @deprecated since Moodle 3.7
1329   */
1330  function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $displayformat = 'plain', $sort = '',
1331                                          $currentgroup = -1, $groupmode = -1, $page = -1, $perpage = 100, $cm = null) {
1332      debugging('forum_print_latest_discussions has been deprecated.', DEBUG_DEVELOPER);
1333      global $CFG, $USER, $OUTPUT;
1334  
1335      require_once($CFG->dirroot . '/course/lib.php');
1336  
1337      if (!$cm) {
1338          if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
1339              print_error('invalidcoursemodule');
1340          }
1341      }
1342      $context = context_module::instance($cm->id);
1343  
1344      if (empty($sort)) {
1345          $sort = forum_get_default_sort_order();
1346      }
1347  
1348      $olddiscussionlink = false;
1349  
1350      // Sort out some defaults.
1351      if ($perpage <= 0) {
1352          $perpage = 0;
1353          $page    = -1;
1354      }
1355  
1356      if ($maxdiscussions == 0) {
1357          // All discussions - backwards compatibility.
1358          $page    = -1;
1359          $perpage = 0;
1360          if ($displayformat == 'plain') {
1361              $displayformat = 'header';  // Abbreviate display by default.
1362          }
1363  
1364      } else if ($maxdiscussions > 0) {
1365          $page    = -1;
1366          $perpage = $maxdiscussions;
1367      }
1368  
1369      $fullpost = false;
1370      if ($displayformat == 'plain') {
1371          $fullpost = true;
1372      }
1373  
1374      // Decide if current user is allowed to see ALL the current discussions or not.
1375      // First check the group stuff.
1376      if ($currentgroup == -1 or $groupmode == -1) {
1377          $groupmode    = groups_get_activity_groupmode($cm, $course);
1378          $currentgroup = groups_get_activity_group($cm);
1379      }
1380  
1381      // Cache.
1382      $groups = array();
1383  
1384      // If the user can post discussions, then this is a good place to put the
1385      // button for it. We do not show the button if we are showing site news
1386      // and the current user is a guest.
1387  
1388      $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
1389      if (!$canstart and $forum->type !== 'news') {
1390          if (isguestuser() or !isloggedin()) {
1391              $canstart = true;
1392          }
1393          if (!is_enrolled($context) and !is_viewing($context)) {
1394              // Allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
1395              // normal users with temporary guest access see this button too, they are asked to enrol instead
1396              // do not show the button to users with suspended enrolments here.
1397              $canstart = enrol_selfenrol_available($course->id);
1398          }
1399      }
1400  
1401      if ($canstart) {
1402          switch ($forum->type) {
1403              case 'news':
1404              case 'blog':
1405                  $buttonadd = get_string('addanewtopic', 'forum');
1406                  break;
1407              case 'qanda':
1408                  $buttonadd = get_string('addanewquestion', 'forum');
1409                  break;
1410              default:
1411                  $buttonadd = get_string('addanewdiscussion', 'forum');
1412                  break;
1413          }
1414          $button = new single_button(new moodle_url('/mod/forum/post.php', ['forum' => $forum->id]), $buttonadd, 'get');
1415          $button->class = 'singlebutton forumaddnew';
1416          $button->formid = 'newdiscussionform';
1417          echo $OUTPUT->render($button);
1418  
1419      } else if (isguestuser() or !isloggedin() or $forum->type == 'news' or
1420          $forum->type == 'qanda' and !has_capability('mod/forum:addquestion', $context) or
1421          $forum->type != 'qanda' and !has_capability('mod/forum:startdiscussion', $context)) {
1422          // No button and no info.
1423          $ignore = true;
1424      } else if ($groupmode and !has_capability('moodle/site:accessallgroups', $context)) {
1425          // Inform users why they can not post new discussion.
1426          if (!$currentgroup) {
1427              if (!has_capability('mod/forum:canposttomygroups', $context)) {
1428                  echo $OUTPUT->notification(get_string('cannotadddiscussiongroup', 'forum'));
1429              } else {
1430                  echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
1431              }
1432          } else if (!groups_is_member($currentgroup)) {
1433              echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
1434          }
1435      }
1436  
1437      // Get all the recent discussions we're allowed to see.
1438  
1439      $getuserlastmodified = ($displayformat == 'header');
1440  
1441      $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage);
1442      if (!$discussions) {
1443          echo '<div class="forumnodiscuss">';
1444          if ($forum->type == 'news') {
1445              echo '('.get_string('nonews', 'forum').')';
1446          } else if ($forum->type == 'qanda') {
1447              echo '('.get_string('noquestions', 'forum').')';
1448          } else {
1449              echo '('.get_string('nodiscussions', 'forum').')';
1450          }
1451          echo "</div>\n";
1452          return;
1453      }
1454  
1455      $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $context);
1456      // If we want paging.
1457      if ($page != -1) {
1458          // Get the number of discussions found.
1459          $numdiscussions = forum_get_discussions_count($cm);
1460  
1461          // Show the paging bar.
1462          echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
1463          if ($numdiscussions > 1000) {
1464              // Saves some memory on sites with very large forums.
1465              $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage, $canseeprivatereplies);
1466          } else {
1467              $replies = forum_count_discussion_replies($forum->id, "", -1, -1, 0, $canseeprivatereplies);
1468          }
1469  
1470      } else {
1471          $replies = forum_count_discussion_replies($forum->id, "", -1, -1, 0, $canseeprivatereplies);
1472  
1473          if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
1474              $olddiscussionlink = true;
1475          }
1476      }
1477  
1478      $canviewparticipants = course_can_view_participants($context);
1479      $canviewhiddentimedposts = has_capability('mod/forum:viewhiddentimedposts', $context);
1480  
1481      $strdatestring = get_string('strftimerecentfull');
1482  
1483      // Check if the forum is tracked.
1484      if ($cantrack = forum_tp_can_track_forums($forum)) {
1485          $forumtracked = forum_tp_is_tracked($forum);
1486      } else {
1487          $forumtracked = false;
1488      }
1489  
1490      if ($forumtracked) {
1491          $unreads = forum_get_discussions_unread($cm);
1492      } else {
1493          $unreads = array();
1494      }
1495  
1496      if ($displayformat == 'header') {
1497          echo '<table cellspacing="0" class="forumheaderlist">';
1498          echo '<thead class="text-left">';
1499          echo '<tr>';
1500          echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
1501          echo '<th class="header author" scope="col">'.get_string('startedby', 'forum').'</th>';
1502          if ($groupmode > 0) {
1503              echo '<th class="header group" scope="col">'.get_string('group').'</th>';
1504          }
1505          if (has_capability('mod/forum:viewdiscussion', $context)) {
1506              echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
1507              // If the forum can be tracked, display the unread column.
1508              if ($cantrack) {
1509                  echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
1510                  if ($forumtracked) {
1511                      echo '<a title="'.get_string('markallread', 'forum').
1512                           '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
1513                           $forum->id.'&amp;mark=read&amp;return=/mod/forum/view.php&amp;sesskey=' . sesskey() . '">'.
1514                           $OUTPUT->pix_icon('t/markasread', get_string('markallread', 'forum')) . '</a>';
1515                  }
1516                  echo '</th>';
1517              }
1518          }
1519          echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
1520          if ((!is_guest($context, $USER) && isloggedin()) && has_capability('mod/forum:viewdiscussion', $context)) {
1521              if (\mod_forum\subscriptions::is_subscribable($forum)) {
1522                  echo '<th class="header discussionsubscription" scope="col">';
1523                  echo forum_get_discussion_subscription_icon_preloaders();
1524                  echo '</th>';
1525              }
1526          }
1527          echo '</tr>';
1528          echo '</thead>';
1529          echo '<tbody>';
1530      }
1531  
1532      foreach ($discussions as $discussion) {
1533          if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $context) &&
1534              !forum_user_has_posted($forum->id, $discussion->discussion, $USER->id)) {
1535              $canviewparticipants = false;
1536          }
1537  
1538          if (!empty($replies[$discussion->discussion])) {
1539              $discussion->replies = $replies[$discussion->discussion]->replies;
1540              $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
1541          } else {
1542              $discussion->replies = 0;
1543          }
1544  
1545          // SPECIAL CASE: The front page can display a news item post to non-logged in users.
1546          // All posts are read in this case.
1547          if (!$forumtracked) {
1548              $discussion->unread = '-';
1549          } else if (empty($USER)) {
1550              $discussion->unread = 0;
1551          } else {
1552              if (empty($unreads[$discussion->discussion])) {
1553                  $discussion->unread = 0;
1554              } else {
1555                  $discussion->unread = $unreads[$discussion->discussion];
1556              }
1557          }
1558  
1559          if (isloggedin()) {
1560              $ownpost = ($discussion->userid == $USER->id);
1561          } else {
1562              $ownpost = false;
1563          }
1564          // Use discussion name instead of subject of first post.
1565          $discussion->subject = $discussion->name;
1566  
1567          switch ($displayformat) {
1568              case 'header':
1569                  if ($groupmode > 0) {
1570                      if (isset($groups[$discussion->groupid])) {
1571                          $group = $groups[$discussion->groupid];
1572                      } else {
1573                          $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
1574                      }
1575                  } else {
1576                      $group = -1;
1577                  }
1578                  forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
1579                      $canviewparticipants, $context, $canviewhiddentimedposts);
1580              break;
1581              default:
1582                  $link = false;
1583  
1584                  if ($discussion->replies) {
1585                      $link = true;
1586                  } else {
1587                      $modcontext = context_module::instance($cm->id);
1588                      $link = forum_user_can_see_discussion($forum, $discussion, $modcontext, $USER);
1589                  }
1590  
1591                  $discussion->forum = $forum->id;
1592  
1593                  forum_print_post_start($discussion);
1594                  forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
1595                          '', null, true, $forumtracked);
1596                  forum_print_post_end($discussion);
1597              break;
1598          }
1599      }
1600  
1601      if ($displayformat == "header") {
1602          echo '</tbody>';
1603          echo '</table>';
1604      }
1605  
1606      if ($olddiscussionlink) {
1607          if ($forum->type == 'news') {
1608              $strolder = get_string('oldertopics', 'forum');
1609          } else {
1610              $strolder = get_string('olderdiscussions', 'forum');
1611          }
1612          echo '<div class="forumolddiscuss">';
1613          echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
1614          echo $strolder.'</a> ...</div>';
1615      }
1616  
1617      if ($page != -1) {
1618          // Show the paging bar.
1619          echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
1620      }
1621  }
1622  
1623  /**
1624   * Count the number of replies to the specified post.
1625   *
1626   * @param object $post
1627   * @param bool $children
1628   * @return int
1629   * @deprecated since Moodle 3.7
1630   * @todo MDL-65252 This will be removed in Moodle 3.11
1631   */
1632  function forum_count_replies($post, $children = true) {
1633      global $USER;
1634      debugging('forum_count_replies has been deprecated. Please use the Post vault instead.', DEBUG_DEVELOPER);
1635  
1636      if (!$children) {
1637          return $DB->count_records('forum_posts', array('parent' => $post->id));
1638      }
1639  
1640      $entityfactory = mod_forum\local\container::get_entity_factory();
1641      $postentity = $entityfactory->get_post_from_stdclass($post);
1642  
1643      $vaultfactory = mod_forum\local\container::get_vault_factory();
1644      $postvault = $vaultfactory->get_post_vault();
1645  
1646      return $postvault->get_reply_count_for_post_id_in_discussion_id(
1647              $USER,
1648              $postentity->get_id(),
1649              $postentity->get_discussion_id(),
1650              true
1651          );
1652  }
1653  
1654  /**
1655   * @deprecated since Moodle 3.8
1656   */
1657  function forum_scale_used() {
1658      throw new coding_exception('forum_scale_used() can not be used anymore. Plugins can implement ' .
1659          '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
1660  }
1661  
1662  /**
1663   * Return grade for given user or all users.
1664   *
1665   * @deprecated since Moodle 3.8
1666   * @param object $forum
1667   * @param int $userid optional user id, 0 means all users
1668   * @return array array of grades, false if none
1669   */
1670  function forum_get_user_grades($forum, $userid = 0) {
1671      global $CFG;
1672  
1673      require_once($CFG->dirroot.'/rating/lib.php');
1674  
1675      $ratingoptions = (object) [
1676          'component' => 'mod_forum',
1677          'ratingarea' => 'post',
1678          'contextid' => $contextid,
1679  
1680          'modulename' => 'forum',
1681          'moduleid  ' => $forum->id,
1682          'userid' => $userid,
1683          'aggregationmethod' => $forum->assessed,
1684          'scaleid' => $forum->scale,
1685          'itemtable' => 'forum_posts',
1686          'itemtableusercolumn' => 'userid',
1687      ];
1688  
1689      $rm = new rating_manager();
1690      return $rm->get_user_grades($ratingoptions);
1691  }
1692  
1693  /**
1694   * Obtains the automatic completion state for this forum based on any conditions
1695   * in forum settings.
1696   *
1697   * @deprecated since Moodle 3.11
1698   * @todo MDL-71196 Final deprecation in Moodle 4.3
1699   * @see \mod_forum\completion\custom_completion
1700   * @global object
1701   * @global object
1702   * @param object $course Course
1703   * @param object $cm Course-module
1704   * @param int $userid User ID
1705   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1706   * @return bool True if completed, false if not. (If no conditions, then return
1707   *   value depends on comparison type)
1708   */
1709  function forum_get_completion_state($course, $cm, $userid, $type) {
1710      global $DB;
1711  
1712      // No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
1713  
1714      // Get forum details.
1715      if (!($forum = $DB->get_record('forum', array('id' => $cm->instance)))) {
1716          throw new Exception("Can't find forum {$cm->instance}");
1717      }
1718  
1719      $result = $type; // Default return value.
1720  
1721      $postcountparams = array('userid' => $userid, 'forumid' => $forum->id);
1722      $postcountsql = "
1723  SELECT
1724      COUNT(1)
1725  FROM
1726      {forum_posts} fp
1727      INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
1728  WHERE
1729      fp.userid=:userid AND fd.forum=:forumid";
1730  
1731      if ($forum->completiondiscussions) {
1732          $value = $forum->completiondiscussions <=
1733              $DB->count_records('forum_discussions', array('forum' => $forum->id, 'userid' => $userid));
1734          if ($type == COMPLETION_AND) {
1735              $result = $result && $value;
1736          } else {
1737              $result = $result || $value;
1738          }
1739      }
1740      if ($forum->completionreplies) {
1741          $value = $forum->completionreplies <=
1742              $DB->get_field_sql($postcountsql . ' AND fp.parent<>0', $postcountparams);
1743          if ($type == COMPLETION_AND) {
1744              $result = $result && $value;
1745          } else {
1746              $result = $result || $value;
1747          }
1748      }
1749      if ($forum->completionposts) {
1750          $value = $forum->completionposts <= $DB->get_field_sql($postcountsql, $postcountparams);
1751          if ($type == COMPLETION_AND) {
1752              $result = $result && $value;
1753          } else {
1754              $result = $result || $value;
1755          }
1756      }
1757  
1758      return $result;
1759  }