Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Capability manager for the forum.** @package mod_forum* @copyright 2019 Ryan Wyllie <ryan@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/namespace mod_forum\local\managers;defined('MOODLE_INTERNAL') || die();use mod_forum\local\data_mappers\legacy\forum as legacy_forum_data_mapper;use mod_forum\local\data_mappers\legacy\discussion as legacy_discussion_data_mapper;use mod_forum\local\data_mappers\legacy\post as legacy_post_data_mapper;use mod_forum\local\entities\discussion as discussion_entity;use mod_forum\local\entities\forum as forum_entity;use mod_forum\local\entities\post as post_entity;use mod_forum\subscriptions;use context;use context_system;use stdClass;use moodle_exception;require_once($CFG->dirroot . '/mod/forum/lib.php');/*** Capability manager for the forum.** Defines all the business rules for what a user can and can't do in the forum.** @copyright 2019 Ryan Wyllie <ryan@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class capability {/** @var legacy_forum_data_mapper $forumdatamapper Legacy forum data mapper */private $forumdatamapper;/** @var legacy_discussion_data_mapper $discussiondatamapper Legacy discussion data mapper */private $discussiondatamapper;/** @var legacy_post_data_mapper $postdatamapper Legacy post data mapper */private $postdatamapper;/** @var forum_entity $forum Forum entity */private $forum;/** @var stdClass $forumrecord Legacy forum record */private $forumrecord;/** @var context $context Module context for the forum */private $context;/** @var array $canviewpostcache Cache of discussion posts that can be viewed by a user. */protected $canviewpostcache = [];/*** Constructor.** @param forum_entity $forum The forum entity to manage capabilities for.* @param legacy_forum_data_mapper $forumdatamapper Legacy forum data mapper* @param legacy_discussion_data_mapper $discussiondatamapper Legacy discussion data mapper* @param legacy_post_data_mapper $postdatamapper Legacy post data mapper*/public function __construct(forum_entity $forum,legacy_forum_data_mapper $forumdatamapper,legacy_discussion_data_mapper $discussiondatamapper,legacy_post_data_mapper $postdatamapper) {$this->forumdatamapper = $forumdatamapper;$this->discussiondatamapper = $discussiondatamapper;$this->postdatamapper = $postdatamapper;$this->forum = $forum;$this->forumrecord = $forumdatamapper->to_legacy_object($forum);$this->context = $forum->get_context();}/*** Can the user subscribe to this forum?** @param stdClass $user The user to check* @return bool*/public function can_subscribe_to_forum(stdClass $user): bool {if ($this->forum->get_type() == 'single') {return false;}return !is_guest($this->get_context(), $user) &&subscriptions::is_subscribable($this->get_forum_record());}/*** Can the user create discussions in this forum?** @param stdClass $user The user to check* @param int|null $groupid The current activity group id* @return bool*/public function can_create_discussions(stdClass $user, int $groupid = null): bool {if (isguestuser($user) or !isloggedin()) {return false;}if ($this->forum->is_cutoff_date_reached()) {if (!has_capability('mod/forum:canoverridecutoff', $this->get_context())) {return false;}}// If the user reaches the number of posts equal to warning/blocking setting then return the value of canpost in $warningobj.$cmrecord = $this->forum->get_course_module_record();if ($warningobj = forum_check_throttling($this->forumrecord, $cmrecord)) {return $warningobj->canpost;}switch ($this->forum->get_type()) {case 'news':$capability = 'mod/forum:addnews';break;case 'qanda':$capability = 'mod/forum:addquestion';break;default:$capability = 'mod/forum:startdiscussion';}if (!has_capability($capability, $this->forum->get_context(), $user)) {return false;}if ($this->forum->get_type() == 'eachuser') {if (forum_user_has_posted_discussion($this->forum->get_id(), $user->id, $groupid)) {return false;}}if ($this->forum->is_in_group_mode()) {return $groupid ? $this->can_access_group($user, $groupid) : $this->can_access_all_groups($user);} else {return true;}}/*** Can the user access all groups?** @param stdClass $user The user to check* @return bool*/public function can_access_all_groups(stdClass $user): bool {return has_capability('moodle/site:accessallgroups', $this->get_context(), $user);}/*** Can the user access the given group?** @param stdClass $user The user to check* @param int $groupid The id of the group that the forum is set to* @return bool*/public function can_access_group(stdClass $user, int $groupid): bool {if ($this->can_access_all_groups($user)) {// This user has access to all groups.return true;}// This is a group discussion for a forum in separate groups mode.// Check if the user is a member.// This is the most expensive check.return groups_is_member($groupid, $user->id);}/*** Can the user post to their groups?** @param stdClass $user The user to check* @return bool*/public function can_post_to_my_groups(stdClass $user): bool {return has_capability('mod/forum:canposttomygroups', $this->get_context(), $user);}/*** Can the user view discussions in this forum?** @param stdClass $user The user to check* @return bool*/public function can_view_discussions(stdClass $user): bool {return has_capability('mod/forum:viewdiscussion', $this->get_context(), $user);}/*** Can the user move discussions in this forum?** @param stdClass $user The user to check* @return bool*/public function can_move_discussions(stdClass $user): bool {$forum = $this->get_forum();return $forum->get_type() !== 'single' &&has_capability('mod/forum:movediscussions', $this->get_context(), $user);}/*** Can the user pin discussions in this forum?** @param stdClass $user The user to check* @return bool*/public function can_pin_discussions(stdClass $user): bool {return $this->forum->get_type() !== 'single' &&has_capability('mod/forum:pindiscussions', $this->get_context(), $user);}/*** Can the user split discussions in this forum?** @param stdClass $user The user to check* @return bool*/public function can_split_discussions(stdClass $user): bool {$forum = $this->get_forum();return $forum->get_type() !== 'single' && has_capability('mod/forum:splitdiscussions', $this->get_context(), $user);}/*** Can the user export (see portfolios) discussions in this forum?** @param stdClass $user The user to check* @return bool*/public function can_export_discussions(stdClass $user): bool {global $CFG;return $CFG->enableportfolios && has_capability('mod/forum:exportdiscussion', $this->get_context(), $user);}/*** Can the user manually mark posts as read/unread in this forum?** @param stdClass $user The user to check* @return bool*/public function can_manually_control_post_read_status(stdClass $user): bool {global $CFG;return $CFG->forum_usermarksread && isloggedin() && forum_tp_is_tracked($this->get_forum_record(), $user);}/*** Is the user required to post in the discussion before they can view it?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @return bool*/public function must_post_before_viewing_discussion(stdClass $user, discussion_entity $discussion): bool {$forum = $this->get_forum();if ($forum->get_type() === 'qanda') {// If it's a Q and A forum then the user must either have the capability to view without// posting or the user must have posted before they can view the discussion.return !has_capability('mod/forum:viewqandawithoutposting', $this->get_context(), $user) &&!forum_user_has_posted($forum->get_id(), $discussion->get_id(), $user->id);} else {// No other forum types require posting before viewing.return false;}}/*** Can the user subscribe to the give discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @return bool*/public function can_subscribe_to_discussion(stdClass $user, discussion_entity $discussion): bool {return $this->can_subscribe_to_forum($user);}/*** Can the user move the discussion in this forum?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @return bool*/public function can_move_discussion(stdClass $user, discussion_entity $discussion): bool {return $this->can_move_discussions($user);}/*** Is the user pin the discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @return bool*/public function can_pin_discussion(stdClass $user, discussion_entity $discussion): bool {return $this->can_pin_discussions($user);}/*** Can the user post in this discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @return bool*/public function can_post_in_discussion(stdClass $user, discussion_entity $discussion): bool {$forum = $this->get_forum();$forumrecord = $this->get_forum_record();$discussionrecord = $this->get_discussion_record($discussion);$context = $this->get_context();$coursemodule = $forum->get_course_module_record();$course = $forum->get_course_record();$status = forum_user_can_post($forumrecord, $discussionrecord, $user, $coursemodule, $course, $context);// If the user reaches the number of posts equal to warning/blocking setting then logically and canpost value with $status.if ($warningobj = forum_check_throttling($forumrecord, $coursemodule)) {return $status && $warningobj->canpost;}return $status;}/*** Can the user favourite the discussion** @param stdClass $user The user to check* @return bool*/public function can_favourite_discussion(stdClass $user): bool {$context = $this->get_context();return has_capability('mod/forum:cantogglefavourite', $context, $user);}/*** Can the user view the content of a discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @return bool*/public function can_view_discussion(stdClass $user, discussion_entity $discussion): bool {$forumrecord = $this->get_forum_record();$discussionrecord = $this->get_discussion_record($discussion);$context = $this->get_context();return forum_user_can_see_discussion($forumrecord, $discussionrecord, $context, $user);}/*** Can the user view the content of the post in this discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @param post_entity $post The post the user wants to view* @return bool*/public function can_view_post(stdClass $user, discussion_entity $discussion, post_entity $post): bool {if (!$this->can_view_post_shell($user, $post)) {return false;}// Return cached can view if possible.if (isset($this->canviewpostcache[$user->id][$post->get_id()])) {return $this->canviewpostcache[$user->id][$post->get_id()];}// Otherwise, check if the user can see this post.$forum = $this->get_forum();$forumrecord = $this->get_forum_record();$discussionrecord = $this->get_discussion_record($discussion);$postrecord = $this->get_post_record($post);$coursemodule = $forum->get_course_module_record();$canviewpost = forum_user_can_see_post($forumrecord, $discussionrecord, $postrecord, $user, $coursemodule, false);// Then cache the result before returning.$this->canviewpostcache[$user->id][$post->get_id()] = $canviewpost;return $canviewpost;}/*** Can the user view the post at all?* In some situations the user can view the shell of a post without being able to view its content.** @param stdClass $user The user to check* @param post_entity $post The post the user wants to view* @return bool**/public function can_view_post_shell(stdClass $user, post_entity $post): bool {if ($post->is_owned_by_user($user)) {return true;}if (!$post->is_private_reply()) {return true;}if ($post->is_private_reply_intended_for_user($user)) {return true;}return $this->can_view_any_private_reply($user);}/*** Whether the user can view any private reply in the forum.** @param stdClass $user The user to check* @return bool*/public function can_view_any_private_reply(stdClass $user): bool {return has_capability('mod/forum:readprivatereplies', $this->get_context(), $user);}/*** Can the user edit the post in this discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @param post_entity $post The post the user wants to edit* @return bool*/public function can_edit_post(stdClass $user, discussion_entity $discussion, post_entity $post): bool {global $CFG;$context = $this->get_context();$ownpost = $post->is_owned_by_user($user);$ineditingtime = $post->get_age() < $CFG->maxeditingtime;$mailnow = $post->should_mail_now();switch ($this->forum->get_type()) {case 'news':// Allow editing of news posts once the discussion has started.$ineditingtime = !$post->has_parent() && $discussion->has_started();break;case 'single':if ($discussion->is_first_post($post)) {return has_capability('moodle/course:manageactivities', $context, $user);}break;}return ($ownpost && $ineditingtime && !$mailnow) || has_capability('mod/forum:editanypost', $context, $user);}/*** Verifies is the given user can delete a post.** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @param post_entity $post The post the user wants to delete* @param bool $hasreplies Whether the post has replies* @return bool* @throws moodle_exception*/public function validate_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post,bool $hasreplies = false): void {global $CFG;$forum = $this->get_forum();if ($forum->get_type() == 'single' && $discussion->is_first_post($post)) {// Do not allow deleting of first post in single simple type.throw new moodle_exception('cannotdeletepost', 'forum');}$context = $this->get_context();$ownpost = $post->is_owned_by_user($user);$ineditingtime = $post->get_age() < $CFG->maxeditingtime;$mailnow = $post->should_mail_now();if (!($ownpost && $ineditingtime && has_capability('mod/forum:deleteownpost', $context, $user) && !$mailnow ||has_capability('mod/forum:deleteanypost', $context, $user))) {throw new moodle_exception('cannotdeletepost', 'forum');}if ($post->get_total_score()) {throw new moodle_exception('couldnotdeleteratings', 'rating');}if ($hasreplies && !has_capability('mod/forum:deleteanypost', $context, $user)) {throw new moodle_exception('couldnotdeletereplies', 'forum');}}/*** Can the user delete the post in this discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @param post_entity $post The post the user wants to delete* @param bool $hasreplies Whether the post has replies* @return bool*/public function can_delete_post(stdClass $user, discussion_entity $discussion, post_entity $post,bool $hasreplies = false): bool {try {$this->validate_delete_post($user, $discussion, $post, $hasreplies);return true;} catch (moodle_exception $e) {return false;}}/*** Can the user split the post in this discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @param post_entity $post The post the user wants to split* @return bool*/public function can_split_post(stdClass $user, discussion_entity $discussion, post_entity $post): bool {if ($post->is_private_reply()) {// It is not possible to create a private discussion.return false;}return $this->can_split_discussions($user) && $post->has_parent();}/*** Can the user reply to the post in this discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @param post_entity $post The post the user wants to reply to* @return bool*/public function can_reply_to_post(stdClass $user, discussion_entity $discussion, post_entity $post): bool {if ($post->is_private_reply()) {// It is not possible to reply to a private reply.return false;} else if (!$this->can_view_post($user, $discussion, $post)) {// If the user cannot view the post in the first place, the user should not be able to reply to the post.return false;}return $this->can_post_in_discussion($user, $discussion);}/*** Can the user reply privately to the specified post?** @param stdClass $user The user to check* @param post_entity $post The post the user wants to reply to* @return bool*/public function can_reply_privately_to_post(stdClass $user, post_entity $post): bool {if ($post->is_private_reply()) {// You cannot reply privately to a post which is, itself, a private reply.return false;}return has_capability('mod/forum:postprivatereply', $this->get_context(), $user);}/*** Can the user export (see portfolios) the post in this discussion?** @param stdClass $user The user to check* @param post_entity $post The post the user wants to export* @return bool*/public function can_export_post(stdClass $user, post_entity $post): bool {global $CFG;$context = $this->get_context();return $CFG->enableportfolios && (has_capability('mod/forum:exportpost', $context, $user) ||($post->is_owned_by_user($user) && has_capability('mod/forum:exportownpost', $context, $user)));}/*** Get the forum entity for this capability manager.** @return forum_entity*/protected function get_forum(): forum_entity {return $this->forum;}/*** Get the legacy forum record for this forum.** @return stdClass*/protected function get_forum_record(): stdClass {return $this->forumrecord;}/*** Get the context for this capability manager.** @return context*/protected function get_context(): context {return $this->context;}/*** Get the legacy discussion record for the given discussion entity.** @param discussion_entity $discussion The discussion to convert* @return stdClass*/protected function get_discussion_record(discussion_entity $discussion): stdClass {return $this->discussiondatamapper->to_legacy_object($discussion);}/*** Get the legacy post record for the given post entity.** @param post_entity $post The post to convert* @return stdClass*/protected function get_post_record(post_entity $post): stdClass {return $this->postdatamapper->to_legacy_object($post);}/*** Can the user view the participants of this discussion?** @param stdClass $user The user to check* @param discussion_entity $discussion The discussion to check* @return bool*/public function can_view_participants(stdClass $user, discussion_entity $discussion): bool {return course_can_view_participants($this->get_context()) &&!$this->must_post_before_viewing_discussion($user, $discussion);}/*** Can the user view hidden posts in this forum?** @param stdClass $user The user to check* @return bool*/public function can_view_hidden_posts(stdClass $user): bool {return has_capability('mod/forum:viewhiddentimedposts', $this->get_context(), $user);}/*** Can the user manage this forum?** @param stdClass $user The user to check* @return bool*/public function can_manage_forum(stdClass $user) {return has_capability('moodle/course:manageactivities', $this->get_context(), $user);}/*** Can the user manage tags on the site?** @param stdClass $user The user to check* @return bool*/public function can_manage_tags(stdClass $user): bool {return has_capability('moodle/tag:manage', context_system::instance(), $user);}/*** Checks whether the user can self enrol into the course.* Mimics the checks on the add button in deprecatedlib/forum_print_latest_discussions** @param stdClass $user* @return bool*/public function can_self_enrol(stdClass $user): bool {$canstart = false;if ($this->forum->get_type() != 'news') {if (isguestuser($user) or !isloggedin()) {$canstart = true;}if (!is_enrolled($this->context) and !is_viewing($this->context)) {// Allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link,// Normal users with temporary guest access see this button too, they are asked to enrol instead,// Do not show the button to users with suspended enrolments here.$canstart = enrol_selfenrol_available($this->forum->get_course_id());}}return $canstart;}/*** Checks whether the user can export the whole forum (discussions and posts).** @param stdClass $user The user object.* @return bool True if the user can export the forum or false otherwise.*/public function can_export_forum(stdClass $user): bool {return has_capability('mod/forum:exportforum', $this->get_context(), $user);}/*** Check whether the supplied grader can grade the gradee.** @param stdClass $grader The user grading* @param stdClass $gradee The user being graded* @return bool*/public function can_grade(stdClass $grader, stdClass $gradee = null): bool {if (!has_capability('mod/forum:grade', $this->get_context(), $grader)) {return false;}return true;}}