Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * 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  
 326          $discussionrecord = $this->get_discussion_record($discussion);
 327          $context = $this->get_context();
 328          $coursemodule = $forum->get_course_module_record();
 329          $course = $forum->get_course_record();
 330  
 331          $status = forum_user_can_post($forumrecord, $discussionrecord, $user, $coursemodule, $course, $context);
 332  
 333          // If the user reaches the number of posts equal to warning/blocking setting then logically and canpost value with $status.
 334          if ($warningobj = forum_check_throttling($forumrecord, $coursemodule)) {
 335              return $status && $warningobj->canpost;
 336          }
 337          return $status;
 338      }
 339  
 340      /**
 341       * Can the user favourite the discussion
 342       *
 343       * @param stdClass $user The user to check
 344       * @return bool
 345       */
 346      public function can_favourite_discussion(stdClass $user) : bool {
 347          $context = $this->get_context();
 348          return has_capability('mod/forum:cantogglefavourite', $context, $user);
 349      }
 350  
 351      /**
 352       * Can the user view the content of a discussion?
 353       *
 354       * @param stdClass $user The user to check
 355       * @param discussion_entity $discussion The discussion to check
 356       * @return bool
 357       */
 358      public function can_view_discussion(stdClass $user, discussion_entity $discussion) : bool {
 359          $forumrecord = $this->get_forum_record();
 360          $discussionrecord = $this->get_discussion_record($discussion);
 361          $context = $this->get_context();
 362  
 363          return forum_user_can_see_discussion($forumrecord, $discussionrecord, $context, $user);
 364      }
 365  
 366      /**
 367       * Can the user view the content of the post in this discussion?
 368       *
 369       * @param stdClass $user The user to check
 370       * @param discussion_entity $discussion The discussion to check
 371       * @param post_entity $post The post the user wants to view
 372       * @return bool
 373       */
 374      public function can_view_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 375          if (!$this->can_view_post_shell($user, $post)) {
 376              return false;
 377          }
 378  
 379          // Return cached can view if possible.
 380          if (isset($this->canviewpostcache[$user->id][$post->get_id()])) {
 381              return $this->canviewpostcache[$user->id][$post->get_id()];
 382          }
 383  
 384          // Otherwise, check if the user can see this post.
 385          $forum = $this->get_forum();
 386          $forumrecord = $this->get_forum_record();
 387          $discussionrecord = $this->get_discussion_record($discussion);
 388          $postrecord = $this->get_post_record($post);
 389          $coursemodule = $forum->get_course_module_record();
 390          $canviewpost = forum_user_can_see_post($forumrecord, $discussionrecord, $postrecord, $user, $coursemodule, false);
 391  
 392          // Then cache the result before returning.
 393          $this->canviewpostcache[$user->id][$post->get_id()] = $canviewpost;
 394  
 395          return $canviewpost;
 396      }
 397  
 398      /**
 399       * Can the user view the post at all?
 400       * In some situations the user can view the shell of a post without being able to view its content.
 401       *
 402       * @param   stdClass $user The user to check
 403       * @param   post_entity $post The post the user wants to view
 404       * @return  bool
 405       *
 406       */
 407      public function can_view_post_shell(stdClass $user, post_entity $post) : bool {
 408          if ($post->is_owned_by_user($user)) {
 409              return true;
 410          }
 411  
 412          if (!$post->is_private_reply()) {
 413              return true;
 414          }
 415  
 416          if ($post->is_private_reply_intended_for_user($user)) {
 417              return true;
 418          }
 419  
 420          return $this->can_view_any_private_reply($user);
 421      }
 422  
 423      /**
 424       * Whether the user can view any private reply in the forum.
 425       *
 426       * @param   stdClass $user The user to check
 427       * @return  bool
 428       */
 429      public function can_view_any_private_reply(stdClass $user) : bool {
 430          return has_capability('mod/forum:readprivatereplies', $this->get_context(), $user);
 431      }
 432  
 433      /**
 434       * Can the user edit the post in this discussion?
 435       *
 436       * @param stdClass $user The user to check
 437       * @param discussion_entity $discussion The discussion to check
 438       * @param post_entity $post The post the user wants to edit
 439       * @return bool
 440       */
 441      public function can_edit_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 442          global $CFG;
 443  
 444          $context = $this->get_context();
 445          $ownpost = $post->is_owned_by_user($user);
 446          $ineditingtime = $post->get_age() < $CFG->maxeditingtime;
 447  
 448          switch ($this->forum->get_type()) {
 449              case 'news':
 450                  // Allow editing of news posts once the discussion has started.
 451                  $ineditingtime = !$post->has_parent() && $discussion->has_started();
 452                  break;
 453              case 'single':
 454                  if ($discussion->is_first_post($post)) {
 455                      return has_capability('moodle/course:manageactivities', $context, $user);
 456                  }
 457                  break;
 458          }
 459  
 460          return ($ownpost && $ineditingtime) || has_capability('mod/forum:editanypost', $context, $user);
 461      }
 462  
 463      /**
 464       * Verifies is the given user can delete a post.
 465       *
 466       * @param stdClass $user The user to check
 467       * @param discussion_entity $discussion The discussion to check
 468       * @param post_entity $post The post the user wants to delete
 469       * @param bool $hasreplies Whether the post has replies
 470       * @return bool
 471       * @throws moodle_exception
 472       */
 473      public function validate_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post,
 474              bool $hasreplies = false) : void {
 475          global $CFG;
 476  
 477          $forum = $this->get_forum();
 478  
 479          if ($forum->get_type() == 'single' && $discussion->is_first_post($post)) {
 480              // Do not allow deleting of first post in single simple type.
 481              throw new moodle_exception('cannotdeletepost', 'forum');
 482          }
 483  
 484          $context = $this->get_context();
 485          $ownpost = $post->is_owned_by_user($user);
 486          $ineditingtime = $post->get_age() < $CFG->maxeditingtime;
 487  
 488          if (!($ownpost && $ineditingtime && has_capability('mod/forum:deleteownpost', $context, $user) ||
 489                  has_capability('mod/forum:deleteanypost', $context, $user))) {
 490  
 491              throw new moodle_exception('cannotdeletepost', 'forum');
 492          }
 493  
 494          if ($post->get_total_score()) {
 495              throw new moodle_exception('couldnotdeleteratings', 'rating');
 496          }
 497  
 498          if ($hasreplies && !has_capability('mod/forum:deleteanypost', $context, $user)) {
 499              throw new moodle_exception('couldnotdeletereplies', 'forum');
 500          }
 501      }
 502  
 503  
 504      /**
 505       * Can the user delete the post in this discussion?
 506       *
 507       * @param stdClass $user The user to check
 508       * @param discussion_entity $discussion The discussion to check
 509       * @param post_entity $post The post the user wants to delete
 510       * @param bool $hasreplies Whether the post has replies
 511       * @return bool
 512       */
 513      public function can_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post,
 514              bool $hasreplies = false) : bool {
 515  
 516          try {
 517              $this->validate_delete_post($user, $discussion, $post, $hasreplies);
 518              return true;
 519          } catch (moodle_exception $e) {
 520              return false;
 521          }
 522      }
 523  
 524      /**
 525       * Can the user split the post in this discussion?
 526       *
 527       * @param stdClass $user The user to check
 528       * @param discussion_entity $discussion The discussion to check
 529       * @param post_entity $post The post the user wants to split
 530       * @return bool
 531       */
 532      public function can_split_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 533          if ($post->is_private_reply()) {
 534              // It is not possible to create a private discussion.
 535              return false;
 536          }
 537  
 538          return $this->can_split_discussions($user) && $post->has_parent();
 539      }
 540  
 541      /**
 542       * Can the user reply to the post in this discussion?
 543       *
 544       * @param stdClass $user The user to check
 545       * @param discussion_entity $discussion The discussion to check
 546       * @param post_entity $post The post the user wants to reply to
 547       * @return bool
 548       */
 549      public function can_reply_to_post(stdClass $user, discussion_entity $discussion, post_entity $post) : bool {
 550          if ($post->is_private_reply()) {
 551              // It is not possible to reply to a private reply.
 552              return false;
 553          } else if (!$this->can_view_post($user, $discussion, $post)) {
 554              // If the user cannot view the post in the first place, the user should not be able to reply to the post.
 555              return false;
 556          }
 557  
 558          return $this->can_post_in_discussion($user, $discussion);
 559      }
 560  
 561      /**
 562       * Can the user reply privately to the specified post?
 563       *
 564       * @param stdClass $user The user to check
 565       * @param post_entity $post The post the user wants to reply to
 566       * @return bool
 567       */
 568      public function can_reply_privately_to_post(stdClass $user, post_entity $post) : bool {
 569          if ($post->is_private_reply()) {
 570              // You cannot reply privately to a post which is, itself, a private reply.
 571              return false;
 572          }
 573  
 574          return has_capability('mod/forum:postprivatereply', $this->get_context(), $user);
 575      }
 576  
 577      /**
 578       * Can the user export (see portfolios) the post in this discussion?
 579       *
 580       * @param stdClass $user The user to check
 581       * @param post_entity $post The post the user wants to export
 582       * @return bool
 583       */
 584      public function can_export_post(stdClass $user, post_entity $post) : bool {
 585          global $CFG;
 586          $context = $this->get_context();
 587          return $CFG->enableportfolios  && (has_capability('mod/forum:exportpost', $context, $user) ||
 588              ($post->is_owned_by_user($user) && has_capability('mod/forum:exportownpost', $context, $user)));
 589      }
 590  
 591      /**
 592       * Get the forum entity for this capability manager.
 593       *
 594       * @return forum_entity
 595       */
 596      protected function get_forum() : forum_entity {
 597          return $this->forum;
 598      }
 599  
 600      /**
 601       * Get the legacy forum record for this forum.
 602       *
 603       * @return stdClass
 604       */
 605      protected function get_forum_record() : stdClass {
 606          return $this->forumrecord;
 607      }
 608  
 609      /**
 610       * Get the context for this capability manager.
 611       *
 612       * @return context
 613       */
 614      protected function get_context() : context {
 615          return $this->context;
 616      }
 617  
 618      /**
 619       * Get the legacy discussion record for the given discussion entity.
 620       *
 621       * @param discussion_entity $discussion The discussion to convert
 622       * @return stdClass
 623       */
 624      protected function get_discussion_record(discussion_entity $discussion) : stdClass {
 625          return $this->discussiondatamapper->to_legacy_object($discussion);
 626      }
 627  
 628      /**
 629       * Get the legacy post record for the given post entity.
 630       *
 631       * @param post_entity $post The post to convert
 632       * @return stdClass
 633       */
 634      protected function get_post_record(post_entity $post) : stdClass {
 635          return $this->postdatamapper->to_legacy_object($post);
 636      }
 637  
 638      /**
 639       * Can the user view the participants of this discussion?
 640       *
 641       * @param stdClass $user The user to check
 642       * @param discussion_entity $discussion The discussion to check
 643       * @return bool
 644       */
 645      public function can_view_participants(stdClass $user, discussion_entity $discussion) : bool {
 646          return course_can_view_participants($this->get_context()) &&
 647              !$this->must_post_before_viewing_discussion($user, $discussion);
 648      }
 649  
 650      /**
 651       * Can the user view hidden posts in this forum?
 652       *
 653       * @param stdClass $user The user to check
 654       * @return bool
 655       */
 656      public function can_view_hidden_posts(stdClass $user) : bool {
 657          return has_capability('mod/forum:viewhiddentimedposts', $this->get_context(), $user);
 658      }
 659  
 660      /**
 661       * Can the user manage this forum?
 662       *
 663       * @param stdClass $user The user to check
 664       * @return bool
 665       */
 666      public function can_manage_forum(stdClass $user) {
 667          return has_capability('moodle/course:manageactivities', $this->get_context(), $user);
 668      }
 669  
 670      /**
 671       * Can the user manage tags on the site?
 672       *
 673       * @param stdClass $user The user to check
 674       * @return bool
 675       */
 676      public function can_manage_tags(stdClass $user) : bool {
 677          return has_capability('moodle/tag:manage', context_system::instance(), $user);
 678      }
 679  
 680      /**
 681       * Checks whether the user can self enrol into the course.
 682       * Mimics the checks on the add button in deprecatedlib/forum_print_latest_discussions
 683       *
 684       * @param stdClass $user
 685       * @return bool
 686       */
 687      public function can_self_enrol(stdClass $user) : bool {
 688          $canstart = false;
 689  
 690          if ($this->forum->get_type() != 'news') {
 691              if (isguestuser($user) or !isloggedin()) {
 692                  $canstart = true;
 693              }
 694  
 695              if (!is_enrolled($this->context) and !is_viewing($this->context)) {
 696                   // Allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link,
 697                   // Normal users with temporary guest access see this button too, they are asked to enrol instead,
 698                   // Do not show the button to users with suspended enrolments here.
 699                  $canstart = enrol_selfenrol_available($this->forum->get_course_id());
 700              }
 701          }
 702  
 703          return $canstart;
 704      }
 705  
 706      /**
 707       * Checks whether the user can export the whole forum (discussions and posts).
 708       *
 709       * @param stdClass $user The user object.
 710       * @return bool True if the user can export the forum or false otherwise.
 711       */
 712      public function can_export_forum(stdClass $user) : bool {
 713          return has_capability('mod/forum:exportforum', $this->get_context(), $user);
 714      }
 715  
 716      /**
 717       * Check whether the supplied grader can grade the gradee.
 718       *
 719       * @param stdClass $grader The user grading
 720       * @param stdClass $gradee The user being graded
 721       * @return bool
 722       */
 723      public function can_grade(stdClass $grader, stdClass $gradee = null): bool {
 724          if (!has_capability('mod/forum:grade', $this->get_context(), $grader)) {
 725              return false;
 726          }
 727  
 728          return true;
 729      }
 730  }