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   * Capability manager for the forum.
  19   *
  20   * @package    mod_forum
  21   * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_forum\local\managers;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use mod_forum\local\data_mappers\legacy\forum as legacy_forum_data_mapper;
  30  use mod_forum\local\data_mappers\legacy\discussion as legacy_discussion_data_mapper;
  31  use mod_forum\local\data_mappers\legacy\post as legacy_post_data_mapper;
  32  use mod_forum\local\entities\discussion as discussion_entity;
  33  use mod_forum\local\entities\forum as forum_entity;
  34  use mod_forum\local\entities\post as post_entity;
  35  use mod_forum\subscriptions;
  36  use context;
  37  use context_system;
  38  use stdClass;
  39  use moodle_exception;
  40  
  41  require_once($CFG->dirroot . '/mod/forum/lib.php');
  42  
  43  /**
  44   * Capability manager for the forum.
  45   *
  46   * Defines all the business rules for what a user can and can't do in the forum.
  47   *
  48   * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
  49   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  50   */
  51  class capability {
  52      /** @var legacy_forum_data_mapper $forumdatamapper Legacy forum data mapper */
  53      private $forumdatamapper;
  54      /** @var legacy_discussion_data_mapper $discussiondatamapper Legacy discussion data mapper */
  55      private $discussiondatamapper;
  56      /** @var legacy_post_data_mapper $postdatamapper Legacy post data mapper */
  57      private $postdatamapper;
  58      /** @var forum_entity $forum Forum entity */
  59      private $forum;
  60      /** @var stdClass $forumrecord Legacy forum record */
  61      private $forumrecord;
  62      /** @var context $context Module context for the forum */
  63      private $context;
  64      /** @var array $canviewpostcache Cache of discussion posts that can be viewed by a user. */
  65      protected $canviewpostcache = [];
  66  
  67      /**
  68       * Constructor.
  69       *
  70       * @param forum_entity $forum The forum entity to manage capabilities for.
  71       * @param legacy_forum_data_mapper $forumdatamapper Legacy forum data mapper
  72       * @param legacy_discussion_data_mapper $discussiondatamapper Legacy discussion data mapper
  73       * @param legacy_post_data_mapper $postdatamapper Legacy post data mapper
  74       */
  75      public function __construct(
  76          forum_entity $forum,
  77          legacy_forum_data_mapper $forumdatamapper,
  78          legacy_discussion_data_mapper $discussiondatamapper,
  79          legacy_post_data_mapper $postdatamapper
  80      ) {
  81          $this->forumdatamapper = $forumdatamapper;
  82          $this->discussiondatamapper = $discussiondatamapper;
  83          $this->postdatamapper = $postdatamapper;
  84          $this->forum = $forum;
  85          $this->forumrecord = $forumdatamapper->to_legacy_object($forum);
  86          $this->context = $forum->get_context();
  87      }
  88  
  89      /**
  90       * Can the user subscribe to this forum?
  91       *
  92       * @param stdClass $user The user to check
  93       * @return bool
  94       */
  95      public function can_subscribe_to_forum(stdClass $user) : bool {
  96          if ($this->forum->get_type() == 'single') {
  97              return false;
  98          }
  99  
 100          return !is_guest($this->get_context(), $user) &&
 101              subscriptions::is_subscribable($this->get_forum_record());
 102      }
 103  
 104      /**
 105       * Can the user create discussions in this forum?
 106       *
 107       * @param stdClass $user The user to check
 108       * @param int|null $groupid The current activity group id
 109       * @return bool
 110       */
 111      public function can_create_discussions(stdClass $user, int $groupid = null) : bool {
 112          if (isguestuser($user) or !isloggedin()) {
 113              return false;
 114          }
 115  
 116          if ($this->forum->is_cutoff_date_reached()) {
 117              if (!has_capability('mod/forum:canoverridecutoff', $this->get_context())) {
 118                  return false;
 119              }
 120          }
 121  
 122          // If the user reaches the number of posts equal to warning/blocking setting then return the value of canpost in $warningobj.
 123          $cmrecord = $this->forum->get_course_module_record();
 124          if ($warningobj = forum_check_throttling($this->forumrecord, $cmrecord)) {
 125              return $warningobj->canpost;
 126          }
 127  
 128          switch ($this->forum->get_type()) {
 129              case 'news':
 130                  $capability = 'mod/forum:addnews';
 131                  break;
 132              case 'qanda':
 133                  $capability = 'mod/forum:addquestion';
 134                  break;
 135              default:
 136                  $capability = 'mod/forum:startdiscussion';
 137          }
 138  
 139          if (!has_capability($capability, $this->forum->get_context(), $user)) {
 140              return false;
 141          }
 142  
 143          if ($this->forum->get_type() == 'eachuser') {
 144              if (forum_user_has_posted_discussion($this->forum->get_id(), $user->id, $groupid)) {
 145                  return false;
 146              }
 147          }
 148  
 149          if ($this->forum->is_in_group_mode()) {
 150              return $groupid ? $this->can_access_group($user, $groupid) : $this->can_access_all_groups($user);
 151          } else {
 152              return true;
 153          }
 154      }
 155  
 156      /**
 157       * Can the user access all groups?
 158       *
 159       * @param stdClass $user The user to check
 160       * @return bool
 161       */
 162      public function can_access_all_groups(stdClass $user) : bool {
 163          return has_capability('moodle/site:accessallgroups', $this->get_context(), $user);
 164      }
 165  
 166      /**
 167       * Can the user access the given group?
 168       *
 169       * @param stdClass $user The user to check
 170       * @param int $groupid The id of the group that the forum is set to
 171       * @return bool
 172       */
 173      public function can_access_group(stdClass $user, int $groupid) : bool {
 174          if ($this->can_access_all_groups($user)) {
 175              // This user has access to all groups.
 176              return true;
 177          }
 178  
 179          // This is a group discussion for a forum in separate groups mode.
 180          // Check if the user is a member.
 181          // This is the most expensive check.
 182          return groups_is_member($groupid, $user->id);
 183      }
 184  
 185      /**
 186       * Can the user post to their groups?
 187       *
 188       * @param stdClass $user The user to check
 189       * @return bool
 190       */
 191      public function can_post_to_my_groups(stdClass $user) : bool {
 192          return has_capability('mod/forum:canposttomygroups', $this->get_context(), $user);
 193      }
 194  
 195      /**
 196       * Can the user view discussions in this forum?
 197       *
 198       * @param stdClass $user The user to check
 199       * @return bool
 200       */
 201      public function can_view_discussions(stdClass $user) : bool {
 202          return has_capability('mod/forum:viewdiscussion', $this->get_context(), $user);
 203      }
 204  
 205      /**
 206       * Can the user move discussions in this forum?
 207       *
 208       * @param stdClass $user The user to check
 209       * @return bool
 210       */
 211      public function can_move_discussions(stdClass $user) : bool {
 212          $forum = $this->get_forum();
 213          return $forum->get_type() !== 'single' &&
 214                  has_capability('mod/forum:movediscussions', $this->get_context(), $user);
 215      }
 216  
 217      /**
 218       * Can the user pin discussions in this forum?
 219       *
 220       * @param stdClass $user The user to check
 221       * @return bool
 222       */
 223      public function can_pin_discussions(stdClass $user) : bool {
 224          return $this->forum->get_type() !== 'single' &&
 225                  has_capability('mod/forum:pindiscussions', $this->get_context(), $user);
 226      }
 227  
 228      /**
 229       * Can the user split discussions in this forum?
 230       *
 231       * @param stdClass $user The user to check
 232       * @return bool
 233       */
 234      public function can_split_discussions(stdClass $user) : bool {
 235          $forum = $this->get_forum();
 236          return $forum->get_type() !== 'single' && has_capability('mod/forum:splitdiscussions', $this->get_context(), $user);
 237      }
 238  
 239      /**
 240       * Can the user export (see portfolios) discussions in this forum?
 241       *
 242       * @param stdClass $user The user to check
 243       * @return bool
 244       */
 245      public function can_export_discussions(stdClass $user) : bool {
 246          global $CFG;
 247          return $CFG->enableportfolios && has_capability('mod/forum:exportdiscussion', $this->get_context(), $user);
 248      }
 249  
 250      /**
 251       * Can the user manually mark posts as read/unread in this forum?
 252       *
 253       * @param stdClass $user The user to check
 254       * @return bool
 255       */
 256      public function can_manually_control_post_read_status(stdClass $user) : bool {
 257          global $CFG;
 258          return $CFG->forum_usermarksread && isloggedin() && forum_tp_is_tracked($this->get_forum_record(), $user);
 259      }
 260  
 261      /**
 262       * Is the user required to post in the discussion before they can view it?
 263       *
 264       * @param stdClass $user The user to check
 265       * @param discussion_entity $discussion The discussion to check
 266       * @return bool
 267       */
 268      public function must_post_before_viewing_discussion(stdClass $user, discussion_entity $discussion) : bool {
 269          $forum = $this->get_forum();
 270  
 271          if ($forum->get_type() === 'qanda') {
 272              // If it's a Q and A forum then the user must either have the capability to view without
 273              // posting or the user must have posted before they can view the discussion.
 274              return !has_capability('mod/forum:viewqandawithoutposting', $this->get_context(), $user) &&
 275                  !forum_user_has_posted($forum->get_id(), $discussion->get_id(), $user->id);
 276          } else {
 277              // No other forum types require posting before viewing.
 278              return false;
 279          }
 280      }
 281  
 282      /**
 283       * Can the user subscribe to the give discussion?
 284       *
 285       * @param stdClass $user The user to check
 286       * @param discussion_entity $discussion The discussion to check
 287       * @return bool
 288       */
 289      public function can_subscribe_to_discussion(stdClass $user, discussion_entity $discussion) : bool {
 290          return $this->can_subscribe_to_forum($user);
 291      }
 292  
 293      /**
 294       * Can the user move the discussion in this forum?
 295       *
 296       * @param stdClass $user The user to check
 297       * @param discussion_entity $discussion The discussion to check
 298       * @return bool
 299       */
 300      public function can_move_discussion(stdClass $user, discussion_entity $discussion) : bool {
 301          return $this->can_move_discussions($user);
 302      }
 303  
 304      /**
 305       * Is the user pin the discussion?
 306       *
 307       * @param stdClass $user The user to check
 308       * @param discussion_entity $discussion The discussion to check
 309       * @return bool
 310       */
 311      public function can_pin_discussion(stdClass $user, discussion_entity $discussion) : bool {
 312          return $this->can_pin_discussions($user);
 313      }
 314  
 315      /**
 316       * Can the user post in this discussion?
 317       *
 318       * @param stdClass $user The user to check
 319       * @param discussion_entity $discussion The discussion to check
 320       * @return bool
 321       */
 322      public function can_post_in_discussion(stdClass $user, discussion_entity $discussion) : bool {
 323          $forum = $this->get_forum();
 324          $forumrecord = $this->get_forum_record();
 325          $discussionrecord = $this->get_discussion_record($discussion);
 326          $context = $this->get_context();
 327          $coursemodule = $forum->get_course_module_record();
 328          $course = $forum->get_course_record();
 329  
 330          $status = forum_user_can_post($forumrecord, $discussionrecord, $user, $coursemodule, $course, $context);
 331          // If the user reaches the number of posts equal to warning/blocking setting then logically and canpost value with $status.
 332          if ($warningobj = forum_check_throttling($forumrecord, $coursemodule)) {
 333              return $status && $warningobj->canpost;
 334          }
 335          return $status;
 336      }
 337  
 338      /**
 339       * Can the user favourite the discussion
 340       *
 341       * @param stdClass $user The user to check
 342       * @return bool
 343       */
 344      public function can_favourite_discussion(stdClass $user) : bool {
 345          $context = $this->get_context();
 346          return has_capability('mod/forum:cantogglefavourite', $context, $user);
 347      }
 348  
 349      /**
 350       * Can the user view the content of a discussion?
 351       *
 352       * @param stdClass $user The user to check
 353       * @param discussion_entity $discussion The discussion to check
 354       * @return bool
 355       */
 356      public function can_view_discussion(stdClass $user, discussion_entity $discussion) : bool {
 357          $forumrecord = $this->get_forum_record();
 358          $discussionrecord = $this->get_discussion_record($discussion);
 359          $context = $this->get_context();
 360  
 361          return forum_user_can_see_discussion($forumrecord, $discussionrecord, $context, $user);
 362      }
 363  
 364      /**
 365       * Can the user view the content of the post in this discussion?
 366       *
 367       * @param stdClass $user The user to check
 368       * @param discussion_entity $discussion The discussion to check
 369       * @param post_entity $post The post the user wants to view
 370       * @return bool
 371       */
 372      public function can_view_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 373          if (!$this->can_view_post_shell($user, $post)) {
 374              return false;
 375          }
 376  
 377          // Return cached can view if possible.
 378          if (isset($this->canviewpostcache[$user->id][$post->get_id()])) {
 379              return $this->canviewpostcache[$user->id][$post->get_id()];
 380          }
 381  
 382          // Otherwise, check if the user can see this post.
 383          $forum = $this->get_forum();
 384          $forumrecord = $this->get_forum_record();
 385          $discussionrecord = $this->get_discussion_record($discussion);
 386          $postrecord = $this->get_post_record($post);
 387          $coursemodule = $forum->get_course_module_record();
 388          $canviewpost = forum_user_can_see_post($forumrecord, $discussionrecord, $postrecord, $user, $coursemodule, false);
 389  
 390          // Then cache the result before returning.
 391          $this->canviewpostcache[$user->id][$post->get_id()] = $canviewpost;
 392  
 393          return $canviewpost;
 394      }
 395  
 396      /**
 397       * Can the user view the post at all?
 398       * In some situations the user can view the shell of a post without being able to view its content.
 399       *
 400       * @param   stdClass $user The user to check
 401       * @param   post_entity $post The post the user wants to view
 402       * @return  bool
 403       *
 404       */
 405      public function can_view_post_shell(stdClass $user, post_entity $post) : bool {
 406          if (!$post->is_private_reply()) {
 407              return true;
 408          }
 409  
 410          if ($post->is_private_reply_intended_for_user($user)) {
 411              return true;
 412          }
 413  
 414          return $this->can_view_any_private_reply($user);
 415      }
 416  
 417      /**
 418       * Whether the user can view any private reply in the forum.
 419       *
 420       * @param   stdClass $user The user to check
 421       * @return  bool
 422       */
 423      public function can_view_any_private_reply(stdClass $user) : bool {
 424          return has_capability('mod/forum:readprivatereplies', $this->get_context(), $user);
 425      }
 426  
 427      /**
 428       * Can the user edit the post in this discussion?
 429       *
 430       * @param stdClass $user The user to check
 431       * @param discussion_entity $discussion The discussion to check
 432       * @param post_entity $post The post the user wants to edit
 433       * @return bool
 434       */
 435      public function can_edit_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 436          global $CFG;
 437  
 438          $context = $this->get_context();
 439          $ownpost = $post->is_owned_by_user($user);
 440          $ineditingtime = $post->get_age() < $CFG->maxeditingtime;
 441  
 442          switch ($this->forum->get_type()) {
 443              case 'news':
 444                  // Allow editing of news posts once the discussion has started.
 445                  $ineditingtime = !$post->has_parent() && $discussion->has_started();
 446                  break;
 447              case 'single':
 448                  if ($discussion->is_first_post($post)) {
 449                      return has_capability('moodle/course:manageactivities', $context, $user);
 450                  }
 451                  break;
 452          }
 453  
 454          return ($ownpost && $ineditingtime) || has_capability('mod/forum:editanypost', $context, $user);
 455      }
 456  
 457      /**
 458       * Verifies is the given user can delete a post.
 459       *
 460       * @param stdClass $user The user to check
 461       * @param discussion_entity $discussion The discussion to check
 462       * @param post_entity $post The post the user wants to delete
 463       * @param bool $hasreplies Whether the post has replies
 464       * @return bool
 465       * @throws moodle_exception
 466       */
 467      public function validate_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post,
 468              bool $hasreplies = false) : void {
 469          global $CFG;
 470  
 471          $forum = $this->get_forum();
 472  
 473          if ($forum->get_type() == 'single' && $discussion->is_first_post($post)) {
 474              // Do not allow deleting of first post in single simple type.
 475              throw new moodle_exception('cannotdeletepost', 'forum');
 476          }
 477  
 478          $context = $this->get_context();
 479          $ownpost = $post->is_owned_by_user($user);
 480          $ineditingtime = $post->get_age() < $CFG->maxeditingtime;
 481  
 482          if (!($ownpost && $ineditingtime && has_capability('mod/forum:deleteownpost', $context, $user) ||
 483                  has_capability('mod/forum:deleteanypost', $context, $user))) {
 484  
 485              throw new moodle_exception('cannotdeletepost', 'forum');
 486          }
 487  
 488          if ($post->get_total_score()) {
 489              throw new moodle_exception('couldnotdeleteratings', 'rating');
 490          }
 491  
 492          if ($hasreplies && !has_capability('mod/forum:deleteanypost', $context, $user)) {
 493              throw new moodle_exception('couldnotdeletereplies', 'forum');
 494          }
 495      }
 496  
 497  
 498      /**
 499       * Can the user delete the post in this discussion?
 500       *
 501       * @param stdClass $user The user to check
 502       * @param discussion_entity $discussion The discussion to check
 503       * @param post_entity $post The post the user wants to delete
 504       * @param bool $hasreplies Whether the post has replies
 505       * @return bool
 506       */
 507      public function can_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post,
 508              bool $hasreplies = false) : bool {
 509  
 510          try {
 511              $this->validate_delete_post($user, $discussion, $post, $hasreplies);
 512              return true;
 513          } catch (moodle_exception $e) {
 514              return false;
 515          }
 516      }
 517  
 518      /**
 519       * Can the user split the post in this discussion?
 520       *
 521       * @param stdClass $user The user to check
 522       * @param discussion_entity $discussion The discussion to check
 523       * @param post_entity $post The post the user wants to split
 524       * @return bool
 525       */
 526      public function can_split_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 527          if ($post->is_private_reply()) {
 528              // It is not possible to create a private discussion.
 529              return false;
 530          }
 531  
 532          return $this->can_split_discussions($user) && $post->has_parent();
 533      }
 534  
 535      /**
 536       * Can the user reply to the post in this discussion?
 537       *
 538       * @param stdClass $user The user to check
 539       * @param discussion_entity $discussion The discussion to check
 540       * @param post_entity $post The post the user wants to reply to
 541       * @return bool
 542       */
 543      public function can_reply_to_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 544          if ($post->is_private_reply()) {
 545              // It is not possible to reply to a private reply.
 546              return false;
 547          } else if (!$this->can_view_post($user, $discussion, $post)) {
 548              // If the user cannot view the post in the first place, the user should not be able to reply to the post.
 549              return false;
 550          }
 551  
 552          return $this->can_post_in_discussion($user, $discussion);
 553      }
 554  
 555      /**
 556       * Can the user reply privately to the specified post?
 557       *
 558       * @param stdClass $user The user to check
 559       * @param post_entity $post The post the user wants to reply to
 560       * @return bool
 561       */
 562      public function can_reply_privately_to_post(stdClass $user, post_entity $post) : bool {
 563          if ($post->is_private_reply()) {
 564              // You cannot reply privately to a post which is, itself, a private reply.
 565              return false;
 566          }
 567  
 568          return has_capability('mod/forum:postprivatereply', $this->get_context(), $user);
 569      }
 570  
 571      /**
 572       * Can the user export (see portfolios) the post in this discussion?
 573       *
 574       * @param stdClass $user The user to check
 575       * @param post_entity $post The post the user wants to export
 576       * @return bool
 577       */
 578      public function can_export_post(stdClass $user, post_entity $post) : bool {
 579          global $CFG;
 580          $context = $this->get_context();
 581          return $CFG->enableportfolios  && (has_capability('mod/forum:exportpost', $context, $user) ||
 582              ($post->is_owned_by_user($user) && has_capability('mod/forum:exportownpost', $context, $user)));
 583      }
 584  
 585      /**
 586       * Get the forum entity for this capability manager.
 587       *
 588       * @return forum_entity
 589       */
 590      protected function get_forum() : forum_entity {
 591          return $this->forum;
 592      }
 593  
 594      /**
 595       * Get the legacy forum record for this forum.
 596       *
 597       * @return stdClass
 598       */
 599      protected function get_forum_record() : stdClass {
 600          return $this->forumrecord;
 601      }
 602  
 603      /**
 604       * Get the context for this capability manager.
 605       *
 606       * @return context
 607       */
 608      protected function get_context() : context {
 609          return $this->context;
 610      }
 611  
 612      /**
 613       * Get the legacy discussion record for the given discussion entity.
 614       *
 615       * @param discussion_entity $discussion The discussion to convert
 616       * @return stdClass
 617       */
 618      protected function get_discussion_record(discussion_entity $discussion) : stdClass {
 619          return $this->discussiondatamapper->to_legacy_object($discussion);
 620      }
 621  
 622      /**
 623       * Get the legacy post record for the given post entity.
 624       *
 625       * @param post_entity $post The post to convert
 626       * @return stdClass
 627       */
 628      protected function get_post_record(post_entity $post) : stdClass {
 629          return $this->postdatamapper->to_legacy_object($post);
 630      }
 631  
 632      /**
 633       * Can the user view the participants of this discussion?
 634       *
 635       * @param stdClass $user The user to check
 636       * @param discussion_entity $discussion The discussion to check
 637       * @return bool
 638       */
 639      public function can_view_participants(stdClass $user, discussion_entity $discussion) : bool {
 640          return course_can_view_participants($this->get_context()) &&
 641              !$this->must_post_before_viewing_discussion($user, $discussion);
 642      }
 643  
 644      /**
 645       * Can the user view hidden posts in this forum?
 646       *
 647       * @param stdClass $user The user to check
 648       * @return bool
 649       */
 650      public function can_view_hidden_posts(stdClass $user) : bool {
 651          return has_capability('mod/forum:viewhiddentimedposts', $this->get_context(), $user);
 652      }
 653  
 654      /**
 655       * Can the user manage this forum?
 656       *
 657       * @param stdClass $user The user to check
 658       * @return bool
 659       */
 660      public function can_manage_forum(stdClass $user) {
 661          return has_capability('moodle/course:manageactivities', $this->get_context(), $user);
 662      }
 663  
 664      /**
 665       * Can the user manage tags on the site?
 666       *
 667       * @param stdClass $user The user to check
 668       * @return bool
 669       */
 670      public function can_manage_tags(stdClass $user) : bool {
 671          return has_capability('moodle/tag:manage', context_system::instance(), $user);
 672      }
 673  
 674      /**
 675       * Checks whether the user can self enrol into the course.
 676       * Mimics the checks on the add button in deprecatedlib/forum_print_latest_discussions
 677       *
 678       * @param stdClass $user
 679       * @return bool
 680       */
 681      public function can_self_enrol(stdClass $user) : bool {
 682          $canstart = false;
 683  
 684          if ($this->forum->get_type() != 'news') {
 685              if (isguestuser($user) or !isloggedin()) {
 686                  $canstart = true;
 687              }
 688  
 689              if (!is_enrolled($this->context) and !is_viewing($this->context)) {
 690                   // Allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link,
 691                   // Normal users with temporary guest access see this button too, they are asked to enrol instead,
 692                   // Do not show the button to users with suspended enrolments here.
 693                  $canstart = enrol_selfenrol_available($this->forum->get_course_id());
 694              }
 695          }
 696  
 697          return $canstart;
 698      }
 699  
 700      /**
 701       * Checks whether the user can export the whole forum (discussions and posts).
 702       *
 703       * @param stdClass $user The user object.
 704       * @return bool True if the user can export the forum or false otherwise.
 705       */
 706      public function can_export_forum(stdClass $user) : bool {
 707          return has_capability('mod/forum:exportforum', $this->get_context(), $user);
 708      }
 709  
 710      /**
 711       * Check whether the supplied grader can grade the gradee.
 712       *
 713       * @param stdClass $grader The user grading
 714       * @param stdClass $gradee The user being graded
 715       * @return bool
 716       */
 717      public function can_grade(stdClass $grader, stdClass $gradee = null): bool {
 718          if (!has_capability('mod/forum:grade', $this->get_context(), $grader)) {
 719              return false;
 720          }
 721  
 722          return true;
 723      }
 724  }