Merge branch 'MDL-22077-master' of git://github.com/peterRd/moodle
This commit is contained in:
@@ -32,6 +32,7 @@ use mod_forum\local\entities\post as post_entity;
|
||||
use mod_forum\local\factories\legacy_data_mapper as legacy_data_mapper_factory;
|
||||
use mod_forum\local\factories\exporter as exporter_factory;
|
||||
use mod_forum\local\factories\vault as vault_factory;
|
||||
use mod_forum\local\factories\manager as manager_factory;
|
||||
use rating_manager;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
@@ -67,6 +68,9 @@ class exported_discussion_summaries {
|
||||
/** @var vault_factory $vaultfactory Vault factory */
|
||||
private $vaultfactory;
|
||||
|
||||
/** @var manager_factory $managerfactory Manager factory */
|
||||
private $managerfactory;
|
||||
|
||||
/** @var rating_manager $ratingmanager Rating manager */
|
||||
private $ratingmanager;
|
||||
|
||||
@@ -77,20 +81,21 @@ class exported_discussion_summaries {
|
||||
* @param legacy_data_mapper_factory $legacydatamapperfactory Legacy data mapper factory
|
||||
* @param exporter_factory $exporterfactory Exporter factory
|
||||
* @param vault_factory $vaultfactory Vault factory
|
||||
* @param rating_manager $ratingmanager Rating manager
|
||||
* @param manager_factory $managerfactory Manager factory
|
||||
*/
|
||||
public function __construct(
|
||||
renderer_base $renderer,
|
||||
legacy_data_mapper_factory $legacydatamapperfactory,
|
||||
exporter_factory $exporterfactory,
|
||||
vault_factory $vaultfactory,
|
||||
rating_manager $ratingmanager
|
||||
manager_factory $managerfactory
|
||||
) {
|
||||
$this->renderer = $renderer;
|
||||
$this->legacydatamapperfactory = $legacydatamapperfactory;
|
||||
$this->exporterfactory = $exporterfactory;
|
||||
$this->vaultfactory = $vaultfactory;
|
||||
$this->ratingmanager = $ratingmanager;
|
||||
$this->managerfactory = $managerfactory;
|
||||
$this->ratingmanager = $managerfactory->get_rating_manager();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,16 +113,18 @@ class exported_discussion_summaries {
|
||||
forum_entity $forum,
|
||||
array $discussions
|
||||
) : array {
|
||||
$capabilitymanager = $this->managerfactory->get_capability_manager($forum);
|
||||
$canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($user);
|
||||
|
||||
$discussionids = array_keys($discussions);
|
||||
|
||||
$postvault = $this->vaultfactory->get_post_vault();
|
||||
$posts = $postvault->get_from_discussion_ids($discussionids);
|
||||
$posts = $postvault->get_from_discussion_ids($user, $discussionids, $canseeanyprivatereply);
|
||||
$groupsbyid = $this->get_groups_available_in_forum($forum);
|
||||
$groupsbyauthorid = $this->get_author_groups_from_posts($posts, $forum);
|
||||
|
||||
$replycounts = $postvault->get_reply_count_for_discussion_ids($discussionids);
|
||||
$latestposts = $postvault->get_latest_post_id_for_discussion_ids($discussionids);
|
||||
$replycounts = $postvault->get_reply_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
|
||||
$latestposts = $postvault->get_latest_post_id_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
|
||||
|
||||
$unreadcounts = [];
|
||||
|
||||
@@ -125,7 +132,7 @@ class exported_discussion_summaries {
|
||||
$forumrecord = $forumdatamapper->to_legacy_object($forum);
|
||||
|
||||
if (forum_tp_can_track_forums($forumrecord)) {
|
||||
$unreadcounts = $postvault->get_unread_count_for_discussion_ids($user, $discussionids);
|
||||
$unreadcounts = $postvault->get_unread_count_for_discussion_ids($user, $discussionids, $canseeanyprivatereply);
|
||||
}
|
||||
|
||||
$summaryexporter = $this->exporterfactory->get_discussion_summaries_exporter(
|
||||
|
||||
@@ -107,6 +107,9 @@ class exported_posts {
|
||||
* to load the additional resources as efficiently as possible but there is no way around some of
|
||||
* the additional overhead.
|
||||
*
|
||||
* Note: Some posts will be removed as part of the build process according to capabilities.
|
||||
* A one-to-one mapping should not be expected.
|
||||
*
|
||||
* @param stdClass $user The user to export the posts for.
|
||||
* @param forum_entity[] $forums A list of all forums that each of the $discussions belong to
|
||||
* @param discussion_entity[] $discussions A list of all discussions that each of the $posts belong to
|
||||
|
||||
@@ -59,7 +59,8 @@ class post {
|
||||
'attachment' => $post->has_attachments(),
|
||||
'totalscore' => $post->get_total_score(),
|
||||
'mailnow' => $post->should_mail_now(),
|
||||
'deleted' => $post->is_deleted()
|
||||
'deleted' => $post->is_deleted(),
|
||||
'privatereplyto' => $post->get_private_reply_recipient_id(),
|
||||
];
|
||||
}, $posts);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ class post {
|
||||
private $mailnow;
|
||||
/** @var bool $deleted Is the post deleted */
|
||||
private $deleted;
|
||||
/** @var int $privatereplyto The user being privately replied to */
|
||||
private $privatereplyto;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -84,6 +86,7 @@ class post {
|
||||
* @param int $totalscore Total score
|
||||
* @param bool $mailnow Should this post be mailed immediately
|
||||
* @param bool $deleted Is the post deleted
|
||||
* @param int $privatereplyto Which user this reply is intended for in a private reply situation
|
||||
*/
|
||||
public function __construct(
|
||||
int $id,
|
||||
@@ -100,7 +103,8 @@ class post {
|
||||
bool $hasattachments,
|
||||
int $totalscore,
|
||||
bool $mailnow,
|
||||
bool $deleted
|
||||
bool $deleted,
|
||||
int $privatereplyto
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->discussionid = $discussionid;
|
||||
@@ -117,6 +121,7 @@ class post {
|
||||
$this->totalscore = $totalscore;
|
||||
$this->mailnow = $mailnow;
|
||||
$this->deleted = $deleted;
|
||||
$this->privatereplyto = $privatereplyto;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,6 +268,25 @@ class post {
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this post private?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_private_reply() : bool {
|
||||
return !empty($this->privatereplyto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the user that this post was intended for.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_private_reply_recipient_id() : int {
|
||||
return $this->privatereplyto;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the post's age in seconds.
|
||||
*
|
||||
@@ -281,4 +305,14 @@ class post {
|
||||
public function is_owned_by_user(stdClass $user) : bool {
|
||||
return $this->get_author_id() == $user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given post is a private reply intended for the given user.
|
||||
*
|
||||
* @param stdClass $user The user to check.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_private_reply_intended_for_user(stdClass $user) : bool {
|
||||
return $this->get_private_reply_recipient_id() == $user->id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace mod_forum\local\exporters;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use mod_forum\local\entities\author as author_entity;
|
||||
use mod_forum\local\exporters\group as group_exporter;
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
|
||||
@@ -81,31 +82,21 @@ class author extends exporter {
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'groups' => [
|
||||
'type' => group_exporter::read_properties_definition(),
|
||||
'multiple' => true,
|
||||
'optional' => true,
|
||||
'type' => [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'image' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'profile' => [
|
||||
'description' => 'The URL for the use profile page',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'profileimage' => [
|
||||
'description' => 'The URL for the use profile image',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Course Group exporter.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\local\exporters;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\external\exporter;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
|
||||
/**
|
||||
* Group exporter.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class group extends exporter {
|
||||
/** @var stdClass $group Group */
|
||||
private $group;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param stdClass $group The group to export
|
||||
* @param array $related The related data for the export.
|
||||
*/
|
||||
public function __construct(stdClass $group, array $related = []) {
|
||||
$this->group = $group;
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'urls' => [
|
||||
'type' => [
|
||||
'image' => [
|
||||
'description' => 'The URL for the group image',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
* @param renderer_base $output The renderer.
|
||||
* @return array Keys are the property names, values are their values.
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
return [
|
||||
'id' => $group->id,
|
||||
'urls' => [
|
||||
'image' => $imageurl ? $imageurl->out(false) : null
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'urlmanager' => 'mod_forum\local\managers\url',
|
||||
'context' => 'context'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@ class post extends exporter {
|
||||
'type' => [
|
||||
'export' => [
|
||||
'type' => PARAM_URL,
|
||||
'description' => 'The URL used to export the attachment',
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
@@ -80,6 +81,7 @@ class post extends exporter {
|
||||
'type' => [
|
||||
'plagiarism' => [
|
||||
'type' => PARAM_RAW,
|
||||
'description' => 'The HTML source for the Plagiarism Response',
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
@@ -109,6 +111,7 @@ class post extends exporter {
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'isdeleted' => ['type' => PARAM_BOOL],
|
||||
'isprivatereply' => ['type' => PARAM_BOOL],
|
||||
'haswordcount' => ['type' => PARAM_BOOL],
|
||||
'wordcount' => [
|
||||
'type' => PARAM_INT,
|
||||
@@ -118,13 +121,41 @@ class post extends exporter {
|
||||
],
|
||||
'capabilities' => [
|
||||
'type' => [
|
||||
'view' => ['type' => PARAM_BOOL],
|
||||
'edit' => ['type' => PARAM_BOOL],
|
||||
'delete' => ['type' => PARAM_BOOL],
|
||||
'split' => ['type' => PARAM_BOOL],
|
||||
'reply' => ['type' => PARAM_BOOL],
|
||||
'export' => ['type' => PARAM_BOOL],
|
||||
'controlreadstatus' => ['type' => PARAM_BOOL]
|
||||
'view' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_ALLOWED,
|
||||
'description' => 'Whether the user can view the post',
|
||||
],
|
||||
'edit' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_ALLOWED,
|
||||
'description' => 'Whether the user can edit the post',
|
||||
],
|
||||
'delete' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_ALLOWED,
|
||||
'description' => 'Whether the user can delete the post',
|
||||
],
|
||||
'split' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_ALLOWED,
|
||||
'description' => 'Whether the user can split the post',
|
||||
],
|
||||
'reply' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_ALLOWED,
|
||||
'description' => 'Whether the user can reply to the post',
|
||||
],
|
||||
'export' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_ALLOWED,
|
||||
'description' => 'Whether the user can export the post',
|
||||
],
|
||||
'controlreadstatus' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'null' => NULL_ALLOWED,
|
||||
'description' => 'Whether the user can control the read status of the post',
|
||||
],
|
||||
]
|
||||
],
|
||||
'urls' => [
|
||||
@@ -133,60 +164,71 @@ class post extends exporter {
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => [
|
||||
'view' => [
|
||||
'description' => 'The URL used to view the post',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'viewisolated' => [
|
||||
'description' => 'The URL used to view the post in isolation',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'viewparent' => [
|
||||
'description' => 'The URL used to view the parent of the post',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'edit' => [
|
||||
'description' => 'The URL used to edit the post',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'delete' => [
|
||||
'description' => 'The URL used to delete the post',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'split' => [
|
||||
'description' => 'The URL used to split the discussion ' .
|
||||
'with the selected post being the first post in the new discussion',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'reply' => [
|
||||
'description' => 'The URL used to reply to the post',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'export' => [
|
||||
'description' => 'The URL used to export the post',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'markasread' => [
|
||||
'description' => 'The URL used to mark the post as read',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'markasunread' => [
|
||||
'description' => 'The URL used to mark the post as unread',
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
@@ -210,14 +252,40 @@ class post extends exporter {
|
||||
'null' => NULL_ALLOWED,
|
||||
'multiple' => true,
|
||||
'type' => [
|
||||
'id' => ['type' => PARAM_INT],
|
||||
'tagid' => ['type' => PARAM_INT],
|
||||
'isstandard' => ['type' => PARAM_BOOL],
|
||||
'displayname' => ['type' => PARAM_TEXT],
|
||||
'flag' => ['type' => PARAM_BOOL],
|
||||
'id' => [
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The ID of the Tag',
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
],
|
||||
'tagid' => [
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The tagid',
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
],
|
||||
'isstandard' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'description' => 'Whether this is a standard tag',
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
],
|
||||
'displayname' => [
|
||||
'type' => PARAM_TEXT,
|
||||
'description' => 'The display name of the tag',
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
],
|
||||
'flag' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'description' => 'Wehther this tag is flagged',
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
],
|
||||
'urls' => [
|
||||
'description' => 'URLs associated with the tag',
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
'type' => [
|
||||
'view' => ['type' => PARAM_URL]
|
||||
'view' => [
|
||||
'type' => PARAM_URL,
|
||||
'description' => 'The URL to view the tag',
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
@@ -231,19 +299,22 @@ class post extends exporter {
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => PARAM_RAW
|
||||
'type' => PARAM_RAW,
|
||||
'description' => 'The HTML source to rate the post',
|
||||
],
|
||||
'taglist' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => PARAM_RAW
|
||||
'type' => PARAM_RAW,
|
||||
'description' => 'The HTML source to view the list of tags',
|
||||
],
|
||||
'authorsubheading' => [
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED,
|
||||
'type' => PARAM_RAW
|
||||
'type' => PARAM_RAW,
|
||||
'description' => 'The HTML source to view the author details',
|
||||
],
|
||||
]
|
||||
]
|
||||
@@ -269,6 +340,7 @@ class post extends exporter {
|
||||
$attachments = $this->related['attachments'];
|
||||
$includehtml = $this->related['includehtml'];
|
||||
$isdeleted = $post->is_deleted();
|
||||
$isprivatereply = $post->is_private_reply();
|
||||
$hasrating = $rating != null;
|
||||
$hastags = !empty($tags);
|
||||
$discussionid = $post->get_discussion_id();
|
||||
@@ -328,6 +400,7 @@ class post extends exporter {
|
||||
'timecreated' => $timecreated,
|
||||
'unread' => ($loadcontent && $readreceiptcollection) ? !$readreceiptcollection->has_user_read_post($user, $post) : null,
|
||||
'isdeleted' => $isdeleted,
|
||||
'isprivatereply' => $isprivatereply,
|
||||
'haswordcount' => $forum->should_display_word_count(),
|
||||
'wordcount' => $forum->should_display_word_count() ? count_words($message) : null,
|
||||
'capabilities' => [
|
||||
|
||||
@@ -105,7 +105,7 @@ class builder {
|
||||
$this->legacydatamapperfactory,
|
||||
$this->exporterfactory,
|
||||
$this->vaultfactory,
|
||||
$this->managerfactory->get_rating_manager()
|
||||
$this->managerfactory
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,8 @@ class entity {
|
||||
$record->attachment,
|
||||
$record->totalscore,
|
||||
$record->mailnow,
|
||||
$record->deleted
|
||||
$record->deleted,
|
||||
$record->privatereplyto
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@ class renderer {
|
||||
$this->urlfactory,
|
||||
$template,
|
||||
$notifications,
|
||||
function($discussions, $user, $forum) {
|
||||
function($discussions, $user, $forum) use ($capabilitymanager) {
|
||||
$exportedpostsbuilder = $this->builderfactory->get_exported_posts_builder();
|
||||
$discussionentries = [];
|
||||
$postentries = [];
|
||||
@@ -449,7 +449,12 @@ class renderer {
|
||||
);
|
||||
|
||||
$postvault = $this->vaultfactory->get_post_vault();
|
||||
$discussionrepliescount = $postvault->get_reply_count_for_discussion_ids($discussionentriesids);
|
||||
$canseeanyprivatereply = $capabilitymanager->can_view_any_private_reply($user);
|
||||
$discussionrepliescount = $postvault->get_reply_count_for_discussion_ids(
|
||||
$user,
|
||||
$discussionentriesids,
|
||||
$canseeanyprivatereply
|
||||
);
|
||||
|
||||
array_walk($exportedposts['posts'], function($post) use ($discussionrepliescount) {
|
||||
$post->discussionrepliescount = $discussionrepliescount[$post->discussionid] ?? 0;
|
||||
|
||||
@@ -316,7 +316,7 @@ class capability {
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the user view the post in this discussion?
|
||||
* 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
|
||||
@@ -324,6 +324,10 @@ class capability {
|
||||
* @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;
|
||||
}
|
||||
|
||||
$forum = $this->get_forum();
|
||||
$forumrecord = $this->get_forum_record();
|
||||
$discussionrecord = $this->get_discussion_record($discussion);
|
||||
@@ -332,6 +336,37 @@ class capability {
|
||||
return forum_user_can_see_post($forumrecord, $discussionrecord, $postrecord, $user, $coursemodule, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_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?
|
||||
*
|
||||
@@ -394,6 +429,11 @@ class capability {
|
||||
* @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();
|
||||
}
|
||||
|
||||
@@ -406,9 +446,30 @@ class capability {
|
||||
* @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;
|
||||
}
|
||||
|
||||
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?
|
||||
*
|
||||
|
||||
@@ -155,4 +155,44 @@ class forum extends db_table_vault {
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum entity for the given post id.
|
||||
*
|
||||
* @param int $id The course module id
|
||||
* @return forum_entity|null
|
||||
*/
|
||||
public function get_from_post_id(int $id) : ?forum_entity {
|
||||
$alias = $this->get_table_alias();
|
||||
$thistable = new dml_table(self::TABLE, $alias, $alias);
|
||||
$coursemoduletable = new dml_table('course_modules', 'cm', 'cm_');
|
||||
$coursetable = new dml_table('course', 'c', 'c_');
|
||||
|
||||
$tablefields = $thistable->get_field_select();
|
||||
$coursemodulefields = $coursemoduletable->get_field_select();
|
||||
$coursefields = $coursetable->get_field_select();
|
||||
|
||||
$fields = implode(', ', [
|
||||
$tablefields,
|
||||
context_helper::get_preload_record_columns_sql('ctx'),
|
||||
$coursemodulefields,
|
||||
$coursefields,
|
||||
]);
|
||||
|
||||
$tables = "{forum_posts} p";
|
||||
$tables .= " JOIN {forum_discussions} d ON d.id = p.discussion";
|
||||
$tables .= ' JOIN {' . self::TABLE . "} {$alias} ON {$alias}.id = d.forum";
|
||||
$tables .= " JOIN {modules} m ON m.name = 'forum'";
|
||||
$tables .= " JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = {$alias}.id";
|
||||
$tables .= ' JOIN {context} ctx ON ctx.contextlevel = ' . CONTEXT_MODULE . ' AND ctx.instanceid = cm.id';
|
||||
$tables .= " JOIN {course} c ON c.id = {$alias}.course";
|
||||
|
||||
$sql = "SELECT {$fields} FROM {$tables} WHERE p.id = :postid";
|
||||
$records = $this->get_db()->get_records_sql($sql, [
|
||||
'postid' => $id,
|
||||
]);
|
||||
|
||||
$records = $this->transform_db_records_to_entities($records);
|
||||
return count($records) ? array_shift($records) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,16 +96,32 @@ class post extends db_table_vault {
|
||||
/**
|
||||
* Get the post ids for the given discussion.
|
||||
*
|
||||
* @param stdClass $user The user to check the unread count for
|
||||
* @param int $discussionid The discussion to load posts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @param string $orderby Order the results
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_from_discussion_id(int $discussionid, string $orderby = 'created ASC') : array {
|
||||
public function get_from_discussion_id(
|
||||
stdClass $user,
|
||||
int $discussionid,
|
||||
bool $canseeprivatereplies,
|
||||
string $orderby = 'created ASC'
|
||||
) : array {
|
||||
$alias = $this->get_table_alias();
|
||||
$wheresql = $alias . '.discussion = ?';
|
||||
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$wheresql = "{$alias}.discussion = :discussionid {$privatewhere}";
|
||||
$orderbysql = $alias . '.' . $orderby;
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, $orderbysql);
|
||||
$records = $this->get_db()->get_records_sql($sql, [$discussionid]);
|
||||
$records = $this->get_db()->get_records_sql($sql, array_merge([
|
||||
'discussionid' => $discussionid,
|
||||
], $privateparams));
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
@@ -113,22 +129,28 @@ class post extends db_table_vault {
|
||||
/**
|
||||
* Get the list of posts for the given discussions.
|
||||
*
|
||||
* @param stdClass $user The user to check the unread count for
|
||||
* @param int[] $discussionids The list of discussion ids to load posts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_from_discussion_ids(array $discussionids) : array {
|
||||
public function get_from_discussion_ids(stdClass $user, array $discussionids, bool $canseeprivatereplies) : array {
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids);
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids, SQL_PARAMS_NAMED);
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$wheresql = "{$alias}.discussion {$insql}";
|
||||
$wheresql = "{$alias}.discussion {$insql} {$privatewhere}";
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, '');
|
||||
$records = $this->get_db()->get_records_sql($sql, $params);
|
||||
$records = $this->get_db()->get_records_sql($sql, array_merge($params, $privateparams));
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
@@ -139,21 +161,43 @@ class post extends db_table_vault {
|
||||
*
|
||||
* The return value will be a flat array of posts in the requested order.
|
||||
*
|
||||
* @param stdClass $user The user to check the unread count for
|
||||
* @param post_entity $post The post to load replies for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @param string $orderby How to order the replies
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_replies_to_post(post_entity $post, string $orderby = 'created ASC') : array {
|
||||
public function get_replies_to_post(
|
||||
stdClass $user,
|
||||
post_entity $post,
|
||||
bool $canseeprivatereplies,
|
||||
string $orderby = 'created ASC'
|
||||
) : array {
|
||||
$alias = $this->get_table_alias();
|
||||
$params = [$post->get_discussion_id(), $post->get_time_created(), $post->get_id()];
|
||||
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$params = array_merge([
|
||||
'discussionid' => $post->get_discussion_id(),
|
||||
'created' => $post->get_time_created(),
|
||||
'excludepostid' => $post->get_id(),
|
||||
], $privateparams);
|
||||
|
||||
// Unfortunately the best we can do to filter down the query is ignore all posts
|
||||
// that were created before the given post (since they can't be replies).
|
||||
$wheresql = "{$alias}.discussion = ? and {$alias}.created >= ? and {$alias}.id != ?";
|
||||
// We also filter to remove private replies if the user cannot vie them.
|
||||
$wheresql = "{$alias}.discussion = :discussionid
|
||||
AND {$alias}.created >= :created {$privatewhere}
|
||||
AND {$alias}.id != :excludepostid";
|
||||
$orderbysql = $alias . '.' . $orderby;
|
||||
$sql = $this->generate_get_records_sql($wheresql, $orderbysql);
|
||||
$records = $this->get_db()->get_records_sql($sql, $params);
|
||||
$posts = $this->transform_db_records_to_entities($records);
|
||||
$sorter = $this->get_entity_factory()->get_posts_sorter();
|
||||
|
||||
// We need to sort all of the values into the replies tree in order to capture
|
||||
// the full list of descendants.
|
||||
$sortedposts = $sorter->sort_into_children($posts);
|
||||
@@ -196,18 +240,82 @@ class post extends db_table_vault {
|
||||
/**
|
||||
* Get a mapping of replies to the specified discussions.
|
||||
*
|
||||
* @param stdClass $user The user to check the unread count for
|
||||
* @param int[] $discussionids The list of discussions to fetch counts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @return int[] The number of replies for each discussion returned in an associative array
|
||||
*/
|
||||
public function get_reply_count_for_discussion_ids(array $discussionids) : array {
|
||||
public function get_reply_count_for_discussion_ids(stdClass $user, array $discussionids, bool $canseeprivatereplies) : array {
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids);
|
||||
$sql = "SELECT discussion, COUNT(1) FROM {" . self::TABLE . "} p " .
|
||||
"WHERE p.discussion {$insql} AND p.parent > 0 GROUP BY discussion";
|
||||
|
||||
return $this->get_db()->get_records_sql_menu($sql, $params);
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids, SQL_PARAMS_NAMED);
|
||||
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$sql = "SELECT discussion, COUNT(1)
|
||||
FROM {" . self::TABLE . "} p
|
||||
WHERE p.discussion {$insql} AND p.parent > 0 {$privatewhere}
|
||||
GROUP BY discussion";
|
||||
|
||||
return $this->get_db()->get_records_sql_menu($sql, array_merge($params, $privateparams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of replies to the specified discussions.
|
||||
*
|
||||
* @param stdClass $user The user to check the unread count for
|
||||
* @param int $postid The post to collect replies to
|
||||
* @param int $discussionid The list of discussions to fetch counts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @return int The number of replies for each discussion returned in an associative array
|
||||
*/
|
||||
public function get_reply_count_for_post_id_in_discussion_id(
|
||||
stdClass $user, int $postid, int $discussionid, bool $canseeprivatereplies) : int {
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
$table = self::TABLE;
|
||||
|
||||
$sql = "SELECT {$alias}.id, {$alias}.parent
|
||||
FROM {{$table}} {$alias}
|
||||
WHERE p.discussion = :discussionid {$privatewhere}";
|
||||
|
||||
$postparents = $this->get_db()->get_records_sql_menu($sql, array_merge([
|
||||
'discussionid' => $discussionid,
|
||||
], $privateparams));
|
||||
|
||||
return $this->count_children_from_parent_recursively($postparents, $postid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the children whose parent matches the current record recursively.
|
||||
*
|
||||
* @param array $postparents The full mapping of posts.
|
||||
* @param int $postid The ID to check for
|
||||
* @return int $count
|
||||
*/
|
||||
private function count_children_from_parent_recursively(array $postparents, int $postid) : int {
|
||||
if (!isset($postparents[$postid])) {
|
||||
// Post not found at all.
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
foreach ($postparents as $pid => $parentid) {
|
||||
if ($postid == $parentid) {
|
||||
$count += $this->count_children_from_parent_recursively($postparents, $pid) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,20 +323,26 @@ class post extends db_table_vault {
|
||||
*
|
||||
* @param stdClass $user The user to fetch counts for
|
||||
* @param int[] $discussionids The list of discussions to fetch counts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @return int[] The count of unread posts for each discussion returned in an associative array
|
||||
*/
|
||||
public function get_unread_count_for_discussion_ids(stdClass $user, array $discussionids) : array {
|
||||
public function get_unread_count_for_discussion_ids(stdClass $user, array $discussionids, bool $canseeprivatereplies) : array {
|
||||
global $CFG;
|
||||
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT p.discussion, COUNT(p.id) FROM {" . self::TABLE . "} p
|
||||
LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = :userid
|
||||
WHERE p.discussion {$insql} AND p.modified > :cutofftime AND r.id IS NULL
|
||||
WHERE p.discussion {$insql} AND p.modified > :cutofftime AND r.id IS NULL {$privatewhere}
|
||||
GROUP BY p.discussion";
|
||||
|
||||
$params['userid'] = $user->id;
|
||||
@@ -236,16 +350,19 @@ class post extends db_table_vault {
|
||||
->sub(new \DateInterval("P{$CFG->forum_oldpostdays}D"))
|
||||
->format('U') / 60) * 60;
|
||||
|
||||
return $this->get_db()->get_records_sql_menu($sql, $params);
|
||||
return $this->get_db()->get_records_sql_menu($sql, array_merge($params, $privateparams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of the most recent post in each discussion based on post creation time.
|
||||
*
|
||||
* @param stdClass $user The user to fetch counts for
|
||||
* @param int[] $discussionids The list of discussions to fetch counts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @return int[] The post id of the most recent post for each discussions returned in an associative array
|
||||
*/
|
||||
public function get_latest_post_id_for_discussion_ids(array $discussionids) : array {
|
||||
public function get_latest_post_id_for_discussion_ids(
|
||||
stdClass $user, array $discussionids, bool $canseeprivatereplies) : array {
|
||||
global $CFG;
|
||||
|
||||
if (empty($discussionids)) {
|
||||
@@ -255,6 +372,11 @@ class post extends db_table_vault {
|
||||
$alias = $this->get_table_alias();
|
||||
list($insql, $params) = $this->get_db()->get_in_or_equal($discussionids, SQL_PARAMS_NAMED);
|
||||
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$sql = "
|
||||
SELECT p.discussion, MAX(p.id)
|
||||
FROM {" . self::TABLE . "} p
|
||||
@@ -264,8 +386,31 @@ class post extends db_table_vault {
|
||||
WHERE mp.discussion {$insql}
|
||||
GROUP BY mp.discussion
|
||||
) lp ON lp.discussion = p.discussion AND lp.created = p.created
|
||||
WHERE 1 = 1 {$privatewhere}
|
||||
GROUP BY p.discussion";
|
||||
|
||||
return $this->get_db()->get_records_sql_menu($sql, $params);
|
||||
return $this->get_db()->get_records_sql_menu($sql, array_merge($params, $privateparams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL where and additional parameters to use to restrict posts to private reply posts.
|
||||
*
|
||||
* @param stdClass $user The user to fetch counts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @return array The SQL WHERE clause, and parameters to use in the SQL.
|
||||
*/
|
||||
private function get_private_reply_sql(stdClass $user, bool $canseeprivatereplies) {
|
||||
$params = [];
|
||||
$privatewhere = '';
|
||||
if (!$canseeprivatereplies) {
|
||||
$privatewhere = ' AND (p.privatereplyto = :privatereplyto OR p.userid = :privatereplyfrom OR p.privatereplyto = 0)';
|
||||
$params['privatereplyto'] = $user->id;
|
||||
$params['privatereplyfrom'] = $user->id;
|
||||
}
|
||||
|
||||
return [
|
||||
'where' => $privatewhere,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,8 @@ class forum_post implements \renderable, \templatable {
|
||||
* @return array Data ready for use in a mustache template
|
||||
*/
|
||||
protected function export_for_template_text(\mod_forum_renderer $renderer) {
|
||||
return array(
|
||||
$data = $this->export_for_template_shared($renderer);
|
||||
return $data + array(
|
||||
'id' => html_entity_decode($this->post->id),
|
||||
'coursename' => html_entity_decode($this->get_coursename()),
|
||||
'courselink' => html_entity_decode($this->get_courselink()),
|
||||
@@ -193,7 +194,8 @@ class forum_post implements \renderable, \templatable {
|
||||
* @return array Data ready for use in a mustache template
|
||||
*/
|
||||
protected function export_for_template_html(\mod_forum_renderer $renderer) {
|
||||
return array(
|
||||
$data = $this->export_for_template_shared($renderer);
|
||||
return $data + array(
|
||||
'id' => $this->post->id,
|
||||
'coursename' => $this->get_coursename(),
|
||||
'courselink' => $this->get_courselink(),
|
||||
@@ -207,7 +209,17 @@ class forum_post implements \renderable, \templatable {
|
||||
// Format some components according to the renderer.
|
||||
'message' => $renderer->format_message_text($this->cm, $this->post),
|
||||
'attachments' => $renderer->format_message_attachments($this->cm, $this->post),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param \mod_forum_renderer $renderer The render to be used for formatting the message and attachments
|
||||
* @return stdClass Data ready for use in a mustache template
|
||||
*/
|
||||
protected function export_for_template_shared(\mod_forum_renderer $renderer) {
|
||||
return array(
|
||||
'canreply' => $this->canreply,
|
||||
'permalink' => $this->get_permalink(),
|
||||
'firstpost' => $this->get_is_firstpost(),
|
||||
@@ -224,6 +236,8 @@ class forum_post implements \renderable, \templatable {
|
||||
'authorpicture' => $this->get_author_picture($renderer),
|
||||
|
||||
'grouppicture' => $this->get_group_picture($renderer),
|
||||
|
||||
'isprivatereply' => !empty($this->post->privatereplyto),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ class mod_forum_post_form extends moodleform {
|
||||
$subscribe = $this->_customdata['subscribe'];
|
||||
$edit = $this->_customdata['edit'];
|
||||
$thresholdwarning = $this->_customdata['thresholdwarning'];
|
||||
$canreplyprivately = $this->_customdata['canreplyprivately'];
|
||||
|
||||
$mform->addElement('header', 'general', '');//fill in the data depending on page params later using set_data
|
||||
|
||||
@@ -147,6 +148,17 @@ class mod_forum_post_form extends moodleform {
|
||||
$mform->addElement('checkbox', 'mailnow', get_string('mailnow', 'forum'));
|
||||
}
|
||||
|
||||
if ((empty($post->id) && $canreplyprivately) || (!empty($post) && !empty($post->privatereplyto))) {
|
||||
// Only show the option to change private reply settings if this is a new post and the user can reply
|
||||
// privately, or if this is already private reply, in which case the state is shown but is not editable.
|
||||
$mform->addElement('checkbox', 'isprivatereply', get_string('privatereply', 'forum'));
|
||||
$mform->addHelpButton('isprivatereply', 'privatereply', 'forum');
|
||||
if (!empty($post->privatereplyto)) {
|
||||
$mform->setDefault('isprivatereply', 1);
|
||||
$mform->freeze('isprivatereply');
|
||||
}
|
||||
}
|
||||
|
||||
if ($groupmode = groups_get_activity_groupmode($cm, $course)) {
|
||||
$groupdata = groups_get_activity_allowed_groups($cm);
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ class provider implements
|
||||
'subject' => 'privacy:metadata:forum_posts:subject',
|
||||
'message' => 'privacy:metadata:forum_posts:message',
|
||||
'userid' => 'privacy:metadata:forum_posts:userid',
|
||||
'privatereplyto' => 'privacy:metadata:forum_posts:privatereplyto',
|
||||
], 'privacy:metadata:forum_posts');
|
||||
|
||||
// The 'forum_queue' table contains user data, but it is only a temporary cache of other data.
|
||||
@@ -620,6 +621,7 @@ class provider implements
|
||||
WHERE f.id ${foruminsql} AND
|
||||
(
|
||||
p.userid = :postuserid OR
|
||||
p.privatereplyto = :privatereplyrecipient OR
|
||||
fr.id IS NOT NULL OR
|
||||
{$ratingsql->userwhere}
|
||||
)
|
||||
@@ -629,6 +631,7 @@ class provider implements
|
||||
$params = [
|
||||
'postuserid' => $userid,
|
||||
'readuserid' => $userid,
|
||||
'privatereplyrecipient' => $userid,
|
||||
];
|
||||
$params += $forumparams;
|
||||
$params += $ratingsql->params;
|
||||
@@ -666,11 +669,18 @@ class provider implements
|
||||
LEFT JOIN {forum_read} fr ON fr.postid = p.id AND fr.userid = :readuserid
|
||||
{$ratingsql->join} AND {$ratingsql->userwhere}
|
||||
WHERE d.id = :discussionid
|
||||
AND (
|
||||
p.privatereplyto = 0
|
||||
OR p.privatereplyto = :privatereplyrecipient
|
||||
OR p.userid = :privatereplyauthor
|
||||
)
|
||||
";
|
||||
|
||||
$params = [
|
||||
'discussionid' => $discussionid,
|
||||
'readuserid' => $userid,
|
||||
'privatereplyrecipient' => $userid,
|
||||
'privatereplyauthor' => $userid,
|
||||
];
|
||||
$params += $ratingsql->params;
|
||||
|
||||
@@ -685,6 +695,7 @@ class provider implements
|
||||
$post->hasdata = $post->hasdata || !empty($post->hasratings);
|
||||
$post->hasdata = $post->hasdata || $post->readflag;
|
||||
$post->hasdata = $post->hasdata || ($post->userid == $USER->id);
|
||||
$post->hasdata = $post->hasdata || ($post->privatereplyto == $USER->id);
|
||||
|
||||
if (0 == $post->parent) {
|
||||
$structure->children[$post->id] = $post;
|
||||
@@ -756,6 +767,10 @@ class provider implements
|
||||
'author_was_you' => transform::yesno($post->userid == $userid),
|
||||
];
|
||||
|
||||
if (!empty($post->privatereplyto)) {
|
||||
$postdata->privatereply = transform::yesno(true);
|
||||
}
|
||||
|
||||
$postdata->message = writer::with_context($context)
|
||||
->rewrite_pluginfile_urls($postarea, 'mod_forum', 'post', $post->id, $post->message);
|
||||
|
||||
|
||||
@@ -165,6 +165,26 @@ $capabilities = array(
|
||||
)
|
||||
),
|
||||
|
||||
'mod/forum:postprivatereply' => array(
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'teacher' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW
|
||||
)
|
||||
),
|
||||
|
||||
'mod/forum:readprivatereplies' => array(
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'teacher' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW
|
||||
)
|
||||
),
|
||||
|
||||
'mod/forum:createattachment' => array(
|
||||
|
||||
'riskbitmask' => RISK_SPAM,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="mod/forum/db" VERSION="20180329" COMMENT="XMLDB file for Moodle mod/forum"
|
||||
<XMLDB PATH="mod/forum/db" VERSION="20190311" COMMENT="XMLDB file for Moodle mod/forum"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -81,6 +81,7 @@
|
||||
<FIELD NAME="totalscore" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="mailnow" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="deleted" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="privatereplyto" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
@@ -188,4 +189,4 @@
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
|
||||
@@ -37,6 +37,16 @@ $functions = array(
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
|
||||
),
|
||||
|
||||
'mod_forum_get_discussion_posts' => array(
|
||||
'classname' => 'mod_forum_external',
|
||||
'methodname' => 'get_discussion_posts',
|
||||
'classpath' => 'mod/forum/externallib.php',
|
||||
'description' => 'Returns a list of forum posts for a discussion.',
|
||||
'type' => 'read',
|
||||
'capabilities' => 'mod/forum:viewdiscussion, mod/forum:viewqandawithoutposting',
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
|
||||
),
|
||||
|
||||
'mod_forum_get_forum_discussion_posts' => array(
|
||||
'classname' => 'mod_forum_external',
|
||||
'methodname' => 'get_forum_discussion_posts',
|
||||
|
||||
@@ -104,5 +104,19 @@ function xmldb_forum_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.6.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2019031200) {
|
||||
// Define field privatereplyto to be added to forum_posts.
|
||||
$table = new xmldb_table('forum_posts');
|
||||
$field = new xmldb_field('privatereplyto', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'mailnow');
|
||||
|
||||
// Conditionally launch add field privatereplyto.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Forum savepoint reached.
|
||||
upgrade_mod_savepoint(true, 2019031200, 'forum');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1440,7 +1440,7 @@ function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $
|
||||
$getuserlastmodified = ($displayformat == 'header');
|
||||
|
||||
$discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage);
|
||||
if (!$$discussions) {
|
||||
if (!$discussions) {
|
||||
echo '<div class="forumnodiscuss">';
|
||||
if ($forum->type == 'news') {
|
||||
echo '('.get_string('nonews', 'forum').')';
|
||||
@@ -1620,3 +1620,34 @@ function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $
|
||||
echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of replies to the specified post.
|
||||
*
|
||||
* @param object $post
|
||||
* @param bool $children
|
||||
* @return int
|
||||
* @deprecated since Moodle 3.7
|
||||
* @todo MDL-65252 This will be removed in Moodle 4.1
|
||||
*/
|
||||
function forum_count_replies($post, $children = true) {
|
||||
global $USER;
|
||||
debugging('forum_count_replies has been deprecated. Please use the Post vault instead.', DEBUG_DEVELOPER);
|
||||
|
||||
if (!$children) {
|
||||
return $DB->count_records('forum_posts', array('parent' => $post->id));
|
||||
}
|
||||
|
||||
$entityfactory = mod_forum\local\container::get_entity_factory();
|
||||
$postentity = $entityfactory->get_post_from_stdclass($post);
|
||||
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
|
||||
return $postvault->get_reply_count_for_post_id_in_discussion_id(
|
||||
$USER,
|
||||
$postentity->get_id(),
|
||||
$postentity->get_discussion_id(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ echo $OUTPUT->heading(format_string($discussion->get_name()), 3, 'discussionname
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$discussionrenderer = $rendererfactory->get_discussion_renderer($forum, $discussion, $displaymode);
|
||||
$orderpostsby = $displaymode == FORUM_MODE_FLATNEWEST ? 'created DESC' : 'created ASC';
|
||||
$replies = $postvault->get_replies_to_post($post, $orderpostsby);
|
||||
$replies = $postvault->get_replies_to_post($USER, $post, $capabilitymanager->can_view_any_private_reply($USER), $orderpostsby);
|
||||
$postids = array_map(function($post) {
|
||||
return $post->get_id();
|
||||
}, array_merge([$post], array_values($replies)));
|
||||
|
||||
+119
-1
@@ -156,6 +156,103 @@ class mod_forum_external extends external_api {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the forum posts in the specified discussion.
|
||||
*
|
||||
* @param int $discussionid
|
||||
* @param string $sortby
|
||||
* @param string $sortdirection
|
||||
* @return array
|
||||
*/
|
||||
public static function get_discussion_posts(int $discussionid, ?string $sortby, ?string $sortdirection) {
|
||||
global $USER;
|
||||
// Validate the parameter.
|
||||
$params = self::validate_parameters(self::get_discussion_posts_parameters(), [
|
||||
'discussionid' => $discussionid,
|
||||
'sortby' => $sortby,
|
||||
'sortdirection' => $sortdirection,
|
||||
]);
|
||||
$warnings = [];
|
||||
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$discussion = $discussionvault->get_from_id($params['discussionid']);
|
||||
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
$forum = $forumvault->get_from_id($discussion->get_forum_id());
|
||||
|
||||
$sortby = $params['sortby'];
|
||||
$sortdirection = $params['sortdirection'];
|
||||
$sortallowedvalues = ['id', 'created', 'modified'];
|
||||
$directionallowedvalues = ['ASC', 'DESC'];
|
||||
|
||||
if (!in_array(strtolower($sortby), $sortallowedvalues)) {
|
||||
throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $sortby . '),' .
|
||||
'allowed values are: ' . implode(', ', $sortallowedvalues));
|
||||
}
|
||||
|
||||
$sortdirection = strtoupper($sortdirection);
|
||||
if (!in_array($sortdirection, $directionallowedvalues)) {
|
||||
throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
|
||||
'allowed values are: ' . implode(',', $directionallowedvalues));
|
||||
}
|
||||
|
||||
$managerfactory = mod_forum\local\container::get_manager_factory();
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forum);
|
||||
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$posts = $postvault->get_from_discussion_id(
|
||||
$USER,
|
||||
$discussion->get_id(),
|
||||
$capabilitymanager->can_view_any_private_reply($USER),
|
||||
"{$sortby} {$sortdirection}"
|
||||
);
|
||||
|
||||
$builderfactory = mod_forum\local\container::get_builder_factory();
|
||||
$postbuilder = $builderfactory->get_exported_posts_builder();
|
||||
|
||||
$legacydatamapper = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
|
||||
return [
|
||||
'posts' => $postbuilder->build($USER, [$forum], [$discussion], $posts),
|
||||
'ratinginfo' => \core_rating\external\util::get_rating_info(
|
||||
$legacydatamapper->get_forum_data_mapper()->to_legacy_object($forum),
|
||||
$forum->get_context(),
|
||||
'mod_forum',
|
||||
'post',
|
||||
$legacydatamapper->get_post_data_mapper()->to_legacy_objects($posts)
|
||||
),
|
||||
'warnings' => $warnings,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the post parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function get_discussion_posts_parameters() {
|
||||
return new external_function_parameters ([
|
||||
'discussionid' => new external_value(PARAM_INT, 'The ID of the discussion from which to fetch posts.', VALUE_REQUIRED),
|
||||
'sortby' => new external_value(PARAM_ALPHA, 'Sort by this element: id, created or modified', VALUE_DEFAULT, 'created'),
|
||||
'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the post return format.
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function get_discussion_posts_returns() {
|
||||
return new external_single_structure([
|
||||
'posts' => new external_multiple_structure(\mod_forum\local\exporters\post::get_read_structure()),
|
||||
'ratinginfo' => \core_rating\external\util::external_ratings_structure(),
|
||||
'warnings' => new external_warnings()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the parameters for get_forum_discussion_posts.
|
||||
*
|
||||
@@ -182,6 +279,7 @@ class mod_forum_external extends external_api {
|
||||
*
|
||||
* @return array the forum post details
|
||||
* @since Moodle 2.7
|
||||
* @todo MDL-65252 This will be removed in Moodle 4.1
|
||||
*/
|
||||
public static function get_forum_discussion_posts($discussionid, $sortby = "created", $sortdirection = "DESC") {
|
||||
global $CFG, $DB, $USER, $PAGE;
|
||||
@@ -267,6 +365,8 @@ class mod_forum_external extends external_api {
|
||||
$post->postread = true;
|
||||
}
|
||||
|
||||
$post->isprivatereply = !empty($post->privatereplyto);
|
||||
|
||||
$post->canreply = $canreply;
|
||||
if (!empty($post->children)) {
|
||||
$post->children = array_keys($post->children);
|
||||
@@ -364,6 +464,7 @@ class mod_forum_external extends external_api {
|
||||
'userfullname' => new external_value(PARAM_TEXT, 'Post author full name'),
|
||||
'userpictureurl' => new external_value(PARAM_URL, 'Post author picture.', VALUE_OPTIONAL),
|
||||
'deleted' => new external_value(PARAM_BOOL, 'This post has been removed.'),
|
||||
'isprivatereply' => new external_value(PARAM_BOOL, 'The post is a private reply'),
|
||||
), 'post'
|
||||
)
|
||||
),
|
||||
@@ -373,6 +474,15 @@ class mod_forum_external extends external_api {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the get_forum_discussion_posts web service as deprecated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function get_forum_discussion_posts_is_deprecated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the parameters for get_forum_discussions_paginated.
|
||||
*
|
||||
@@ -468,7 +578,8 @@ class mod_forum_external extends external_api {
|
||||
}
|
||||
}
|
||||
// The forum function returns the replies for all the discussions in a given forum.
|
||||
$replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage);
|
||||
$canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
|
||||
$replies = forum_count_discussion_replies($forumid, $sort, -1, $page, $perpage, $canseeprivatereplies);
|
||||
|
||||
foreach ($alldiscussions as $discussion) {
|
||||
|
||||
@@ -771,6 +882,7 @@ class mod_forum_external extends external_api {
|
||||
'name' => new external_value(PARAM_ALPHANUM,
|
||||
'The allowed keys (value format) are:
|
||||
discussionsubscribe (bool); subscribe to the discussion?, default to true
|
||||
private (bool); make this reply private to the author of the parent post, default to false.
|
||||
inlineattachmentsid (int); the draft file area id for inline attachments
|
||||
attachmentsid (int); the draft file area id for attachments
|
||||
'),
|
||||
@@ -806,6 +918,7 @@ class mod_forum_external extends external_api {
|
||||
'options' => $options
|
||||
)
|
||||
);
|
||||
|
||||
$warnings = array();
|
||||
|
||||
if (!$parent = forum_get_post_full($params['postid'])) {
|
||||
@@ -826,6 +939,7 @@ class mod_forum_external extends external_api {
|
||||
// Validate options.
|
||||
$options = array(
|
||||
'discussionsubscribe' => true,
|
||||
'private' => false,
|
||||
'inlineattachmentsid' => 0,
|
||||
'attachmentsid' => null
|
||||
);
|
||||
@@ -835,6 +949,9 @@ class mod_forum_external extends external_api {
|
||||
case 'discussionsubscribe':
|
||||
$value = clean_param($option['value'], PARAM_BOOL);
|
||||
break;
|
||||
case 'private':
|
||||
$value = clean_param($option['value'], PARAM_BOOL);
|
||||
break;
|
||||
case 'inlineattachmentsid':
|
||||
$value = clean_param($option['value'], PARAM_INT);
|
||||
break;
|
||||
@@ -868,6 +985,7 @@ class mod_forum_external extends external_api {
|
||||
$post->messagetrust = trusttext_trusted($context);
|
||||
$post->itemid = $options['inlineattachmentsid'];
|
||||
$post->attachments = $options['attachmentsid'];
|
||||
$post->isprivatereply = $options['private'];
|
||||
$post->deleted = 0;
|
||||
$fakemform = $post->attachments;
|
||||
if ($postid = forum_add_new_post($post, $fakemform)) {
|
||||
|
||||
@@ -60,6 +60,7 @@ $string['cannotaddteacherforumto'] = 'Could not add converted teacher forum inst
|
||||
$string['cannotcreatediscussion'] = 'Could not create new discussion';
|
||||
$string['cannotcreateinstanceforteacher'] = 'Could not create new course module instance for the teacher forum';
|
||||
$string['cannotdeletepost'] = 'You can\'t delete this post!';
|
||||
$string['cannotdeletediscussioninsinglediscussion'] = 'You cannot delete the first post in a single discussion';
|
||||
$string['cannoteditposts'] = 'You can\'t edit other people\'s posts!';
|
||||
$string['cannotfinddiscussion'] = 'Could not find the discussion in this forum';
|
||||
$string['cannotfindfirstpost'] = 'Could not find the first post in this forum';
|
||||
@@ -248,6 +249,8 @@ $string['forumposts'] = 'Forum posts';
|
||||
$string['forum:rate'] = 'Rate posts';
|
||||
$string['forum:replynews'] = 'Reply to announcements';
|
||||
$string['forum:replypost'] = 'Reply to posts';
|
||||
$string['forum:postprivatereply'] = 'Reply privately to posts';
|
||||
$string['forum:readprivatereplies'] = 'View private replies';
|
||||
$string['forums'] = 'Forums';
|
||||
$string['forum:splitdiscussions'] = 'Split discussions';
|
||||
$string['forum:startdiscussion'] = 'Start new discussions';
|
||||
@@ -406,6 +409,7 @@ $string['page-mod-forum-discuss'] = 'Forum module discussion thread page';
|
||||
$string['parent'] = 'Show parent';
|
||||
$string['parentofthispost'] = 'Parent of this post';
|
||||
$string['permalink'] = 'Permalink';
|
||||
$string['postisprivatereply'] = 'This post was made privately and is not visible to all users.';
|
||||
$string['posttomygroups'] = 'Post a copy to all groups';
|
||||
$string['posttomygroups_help'] = 'Posts a copy of this message to all groups you have access to. Participants in groups you do not have access to will not see this post';
|
||||
$string['prevdiscussiona'] = 'Previous discussion: {$a}';
|
||||
@@ -460,6 +464,7 @@ $string['privacy:metadata:forum_posts:parent'] = 'The parent post that was repli
|
||||
$string['privacy:metadata:forum_posts:subject'] = 'The subject of the forum post.';
|
||||
$string['privacy:metadata:forum_posts:totalscore'] = 'The message of the forum post.';
|
||||
$string['privacy:metadata:forum_posts:userid'] = 'The ID of the user who authored the forum post.';
|
||||
$string['privacy:metadata:forum_posts:privatereplyto'] = 'The ID of the user this reply was sent to.';
|
||||
$string['privacy:metadata:forum_queue'] = 'Temporary log of posts that will be mailed in digest form';
|
||||
$string['privacy:metadata:forum_queue:discussionid'] = 'Forum discussion ID';
|
||||
$string['privacy:metadata:forum_queue:postid'] = 'Forum post ID';
|
||||
@@ -487,6 +492,8 @@ $string['privacy:request:delete:discussion:name'] = 'Delete at the request of th
|
||||
$string['privacy:request:delete:post:message'] = 'The content of this post has been deleted at the request of its author.';
|
||||
$string['privacy:request:delete:post:subject'] = 'Delete at the request of the author';
|
||||
$string['privacy:subscribedtoforum'] = 'You are subscribed to this forum.';
|
||||
$string['privatereply'] = 'Reply privately';
|
||||
$string['privatereply_help'] = 'A private reply can only be viewed by the author of the post being replied to, and any users with the capability to view private replies.';
|
||||
$string['processingdigest'] = 'Processing email digest for user {$a}';
|
||||
$string['processingpost'] = 'Processing post {$a}';
|
||||
$string['prune'] = 'Split';
|
||||
|
||||
+96
-46
@@ -813,6 +813,10 @@ function forum_print_recent_activity($course, $viewfullnames, $timestart) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!forum_post_is_visible_privately($post, $cm)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the user can see the discussion.
|
||||
if (forum_is_user_group_discussion($cm, $post->groupid)) {
|
||||
$printposts[] = $post;
|
||||
@@ -1038,15 +1042,12 @@ function forum_get_post_full($postid) {
|
||||
/**
|
||||
* Gets all posts in discussion including top parent.
|
||||
*
|
||||
* @global object
|
||||
* @global object
|
||||
* @global object
|
||||
* @param int $discussionid
|
||||
* @param string $sort
|
||||
* @param bool $tracking does user track the forum?
|
||||
* @return array of posts
|
||||
* @param int $discussionid The Discussion to fetch.
|
||||
* @param string $sort The sorting to apply.
|
||||
* @param bool $tracking Whether the user tracks this forum.
|
||||
* @return array The posts in the discussion.
|
||||
*/
|
||||
function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
|
||||
function forum_get_all_discussion_posts($discussionid, $sort, $tracking = false) {
|
||||
global $CFG, $DB, $USER;
|
||||
|
||||
$tr_sel = "";
|
||||
@@ -1525,17 +1526,17 @@ function forum_get_firstpost_from_discussion($discussionid) {
|
||||
/**
|
||||
* Returns an array of counts of replies to each discussion
|
||||
*
|
||||
* @global object
|
||||
* @global object
|
||||
* @param int $forumid
|
||||
* @param string $forumsort
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
* @param int $perpage
|
||||
* @return array
|
||||
* @param int $forumid
|
||||
* @param string $forumsort
|
||||
* @param int $limit
|
||||
* @param int $page
|
||||
* @param int $perpage
|
||||
* @param boolean $canseeprivatereplies Whether the current user can see private replies.
|
||||
* @return array
|
||||
*/
|
||||
function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
|
||||
global $CFG, $DB;
|
||||
function forum_count_discussion_replies($forumid, $forumsort = "", $limit = -1, $page = -1, $perpage = 0,
|
||||
$canseeprivatereplies = false) {
|
||||
global $CFG, $DB, $USER;
|
||||
|
||||
if ($limit > 0) {
|
||||
$limitfrom = 0;
|
||||
@@ -1559,21 +1560,33 @@ function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $pag
|
||||
$groupby = str_replace('asc', '', $groupby);
|
||||
}
|
||||
|
||||
$params = ['forumid' => $forumid];
|
||||
|
||||
if (!$canseeprivatereplies) {
|
||||
$privatewhere = ' AND (p.privatereplyto = :currentuser1 OR p.userid = :currentuser2 OR p.privatereplyto = 0)';
|
||||
$params['currentuser1'] = $USER->id;
|
||||
$params['currentuser2'] = $USER->id;
|
||||
} else {
|
||||
$privatewhere = '';
|
||||
}
|
||||
|
||||
if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
|
||||
$sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
|
||||
FROM {forum_posts} p
|
||||
JOIN {forum_discussions} d ON p.discussion = d.id
|
||||
WHERE p.parent > 0 AND d.forum = ?
|
||||
WHERE p.parent > 0 AND d.forum = :forumid
|
||||
$privatewhere
|
||||
GROUP BY p.discussion";
|
||||
return $DB->get_records_sql($sql, array($forumid));
|
||||
return $DB->get_records_sql($sql, $params);
|
||||
|
||||
} else {
|
||||
$sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
|
||||
FROM {forum_posts} p
|
||||
JOIN {forum_discussions} d ON p.discussion = d.id
|
||||
WHERE d.forum = ?
|
||||
WHERE d.forum = :forumid
|
||||
$privatewhere
|
||||
GROUP BY p.discussion $groupby $orderby";
|
||||
return $DB->get_records_sql($sql, array($forumid), $limitfrom, $limitnum);
|
||||
return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3093,10 +3106,25 @@ function forum_add_new_post($post, $mform, $unused = null) {
|
||||
$forum = $DB->get_record('forum', array('id' => $discussion->forum));
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$context = context_module::instance($cm->id);
|
||||
$privatereplyto = 0;
|
||||
|
||||
// Check whether private replies should be enabled for this post.
|
||||
if ($post->parent) {
|
||||
$parent = $DB->get_record('forum_posts', array('id' => $post->parent));
|
||||
|
||||
if (!empty($parent->privatereplyto)) {
|
||||
throw new \coding_exception('It should not be possible to reply to a private reply');
|
||||
}
|
||||
|
||||
if (!empty($post->isprivatereply) && forum_user_can_reply_privately($context, $parent)) {
|
||||
$privatereplyto = $parent->userid;
|
||||
}
|
||||
}
|
||||
|
||||
$post->created = $post->modified = time();
|
||||
$post->mailed = FORUM_MAILED_PENDING;
|
||||
$post->userid = $USER->id;
|
||||
$post->privatereplyto = $privatereplyto;
|
||||
$post->attachment = "";
|
||||
if (!isset($post->totalscore)) {
|
||||
$post->totalscore = 0;
|
||||
@@ -3223,6 +3251,7 @@ function forum_add_discussion($discussion, $mform=null, $unused=null, $userid=nu
|
||||
$post = new stdClass();
|
||||
$post->discussion = 0;
|
||||
$post->parent = 0;
|
||||
$post->privatereplyto = 0;
|
||||
$post->userid = $userid;
|
||||
$post->created = $timenow;
|
||||
$post->modified = $timenow;
|
||||
@@ -3449,30 +3478,6 @@ function forum_trigger_content_uploaded_event($post, $cm, $name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @global object
|
||||
* @param object $post
|
||||
* @param bool $children
|
||||
* @return int
|
||||
*/
|
||||
function forum_count_replies($post, $children=true) {
|
||||
global $DB;
|
||||
$count = 0;
|
||||
|
||||
if ($children) {
|
||||
if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
|
||||
foreach ($childposts as $childpost) {
|
||||
$count ++; // For this child
|
||||
$count += forum_count_replies($childpost, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$count += $DB->count_records('forum_posts', array('parent' => $post->id));
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a new post, subscribes or unsubscribes as appropriate.
|
||||
* Returns some text which describes what happened.
|
||||
@@ -3989,6 +3994,10 @@ function forum_user_can_see_post($forum, $discussion, $post, $user = null, $cm =
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!forum_post_is_visible_privately($post, $cm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($cm->uservisible)) {
|
||||
if (!$cm->uservisible) {
|
||||
return false;
|
||||
@@ -6541,3 +6550,44 @@ function mod_forum_get_completion_active_rule_descriptions($cm) {
|
||||
}
|
||||
return $descriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the forum post is a private reply visible to this user.
|
||||
*
|
||||
* @param stdClass $post The post to check.
|
||||
* @param cm_info $cm The context module instance.
|
||||
* @return bool Whether the post is visible in terms of private reply configuration.
|
||||
*/
|
||||
function forum_post_is_visible_privately($post, $cm) {
|
||||
global $USER;
|
||||
|
||||
if (!empty($post->privatereplyto)) {
|
||||
// Allow the user to see the private reply if:
|
||||
// * they hold the permission;
|
||||
// * they are the author; or
|
||||
// * they are the intended recipient.
|
||||
$cansee = false;
|
||||
$cansee = $cansee || ($post->userid == $USER->id);
|
||||
$cansee = $cansee || ($post->privatereplyto == $USER->id);
|
||||
$cansee = $cansee || has_capability('mod/forum:readprivatereplies', context_module::instance($cm->id));
|
||||
return $cansee;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user can reply privately to the parent post.
|
||||
*
|
||||
* @param \context_module $context
|
||||
* @param \stdClass $parent
|
||||
* @return bool
|
||||
*/
|
||||
function forum_user_can_reply_privately(\context_module $context, \stdClass $parent) : bool {
|
||||
if ($parent->privatereplyto) {
|
||||
// You cannot reply privately to a post which is, itself, a private reply.
|
||||
return false;
|
||||
}
|
||||
|
||||
return has_capability('mod/forum:postprivatereply', $context);
|
||||
}
|
||||
|
||||
+330
-255
@@ -50,36 +50,46 @@ $pageparams = array('reply' => $reply, 'forum' => $forum, 'edit' => $edit);
|
||||
|
||||
$sitecontext = context_system::instance();
|
||||
|
||||
if (!isloggedin() or isguestuser()) {
|
||||
$entityfactory = mod_forum\local\container::get_entity_factory();
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$managerfactory = mod_forum\local\container::get_manager_factory();
|
||||
$legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
$urlfactory = mod_forum\local\container::get_url_factory();
|
||||
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
$forumdatamapper = $legacydatamapperfactory->get_forum_data_mapper();
|
||||
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$discussiondatamapper = $legacydatamapperfactory->get_discussion_data_mapper();
|
||||
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$postdatamapper = $legacydatamapperfactory->get_post_data_mapper();
|
||||
|
||||
if (!isloggedin() or isguestuser()) {
|
||||
if (!isloggedin() and !get_local_referer()) {
|
||||
// No referer+not logged in - probably coming in via email See MDL-9052.
|
||||
require_login();
|
||||
}
|
||||
|
||||
if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
if (! $forum = $DB->get_record('forum', array('id' => $forum))) {
|
||||
if (!empty($forum)) {
|
||||
// User is starting a new discussion in a forum.
|
||||
$forumentity = $forumvault->get_from_id($forum);
|
||||
if (empty($forumentity)) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
}
|
||||
} else if (!empty($reply)) { // User is writing a new reply.
|
||||
if (! $parent = forum_get_post_full($reply)) {
|
||||
} else if (!empty($reply)) {
|
||||
// User is writing a new reply.
|
||||
$forumentity = $forumvault->get_from_post_id($reply);
|
||||
if (empty($forumentity)) {
|
||||
print_error('invalidparentpostid', 'forum');
|
||||
}
|
||||
if (! $discussion = $DB->get_record('forum_discussions', array('id' => $parent->discussion))) {
|
||||
print_error('notpartofdiscussion', 'forum');
|
||||
}
|
||||
if (! $forum = $DB->get_record('forum', array('id' => $discussion->forum))) {
|
||||
print_error('invalidforumid');
|
||||
}
|
||||
}
|
||||
if (! $course = $DB->get_record('course', array('id' => $forum->course))) {
|
||||
print_error('invalidcourseid');
|
||||
}
|
||||
|
||||
if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) { // For the logs.
|
||||
print_error('invalidcoursemodule');
|
||||
} else {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$forum = $forumdatamapper->to_legacy_object($forumentity);
|
||||
$modcontext = $forumentity->get_context();
|
||||
$course = $forumentity->get_course_record();
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
print_error("invalidcoursemodule");
|
||||
}
|
||||
|
||||
$PAGE->set_cm($cm, $course, $forum);
|
||||
@@ -96,25 +106,31 @@ if (!isloggedin() or isguestuser()) {
|
||||
|
||||
require_login(0, false); // Script is useless unless they're logged in.
|
||||
|
||||
$entityfactory = mod_forum\local\container::get_entity_factory();
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$canreplyprivately = false;
|
||||
|
||||
if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
if (! $forum = $DB->get_record("forum", array("id" => $forum))) {
|
||||
if (!empty($forum)) {
|
||||
// User is starting a new discussion in a forum.
|
||||
$forumentity = $forumvault->get_from_id($forum);
|
||||
if (empty($forumentity)) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
}
|
||||
if (! $course = $DB->get_record("course", array("id" => $forum->course))) {
|
||||
print_error('invalidcourseid');
|
||||
}
|
||||
if (! $cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forumentity);
|
||||
$forum = $forumdatamapper->to_legacy_object($forumentity);
|
||||
$course = $forumentity->get_course_record();
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
print_error("invalidcoursemodule");
|
||||
}
|
||||
|
||||
// Retrieve the contexts.
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$modcontext = $forumentity->get_context();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
|
||||
if (! forum_user_can_post_discussion($forum, $groupid, -1, $cm)) {
|
||||
if ($forumentity->is_in_group_mode() && null === $groupid) {
|
||||
$groupid = groups_get_activity_group($cm);
|
||||
}
|
||||
|
||||
if (!$capabilitymanager->can_create_discussions($USER, $groupid)) {
|
||||
if (!isguestuser()) {
|
||||
if (!is_enrolled($coursecontext)) {
|
||||
if (enrol_selfenrol_available($course->id)) {
|
||||
@@ -130,7 +146,12 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
}
|
||||
|
||||
if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $modcontext)) {
|
||||
print_error("activityiscurrentlyhidden");
|
||||
redirect(
|
||||
$urlfactory->get_course_url_from_forum($forumentity),
|
||||
get_string('activityiscurrentlyhidden'),
|
||||
null,
|
||||
\core\output\notice::NOTIFY_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$SESSION->fromurl = get_local_referer(false);
|
||||
@@ -147,42 +168,45 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
$post->message = '';
|
||||
$post->messageformat = editors_get_preferred_format();
|
||||
$post->messagetrust = 0;
|
||||
|
||||
if (isset($groupid)) {
|
||||
$post->groupid = $groupid;
|
||||
} else {
|
||||
$post->groupid = groups_get_activity_group($cm);
|
||||
}
|
||||
$post->groupid = $groupid;
|
||||
|
||||
// Unsetting this will allow the correct return URL to be calculated later.
|
||||
unset($SESSION->fromdiscussion);
|
||||
|
||||
} else if (!empty($reply)) { // User is writing a new reply.
|
||||
} else if (!empty($reply)) {
|
||||
// User is writing a new reply.
|
||||
|
||||
if (! $parent = forum_get_post_full($reply)) {
|
||||
$parententity = $postvault->get_from_id($reply);
|
||||
if (empty($parententity)) {
|
||||
print_error('invalidparentpostid', 'forum');
|
||||
}
|
||||
if (! $discussion = $DB->get_record("forum_discussions", array("id" => $parent->discussion))) {
|
||||
|
||||
$discussionentity = $discussionvault->get_from_id($parententity->get_discussion_id());
|
||||
if (empty($discussionentity)) {
|
||||
print_error('notpartofdiscussion', 'forum');
|
||||
}
|
||||
if (! $forum = $DB->get_record("forum", array("id" => $discussion->forum))) {
|
||||
|
||||
$forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
|
||||
if (empty($forumentity)) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
}
|
||||
if (! $course = $DB->get_record("course", array("id" => $discussion->course))) {
|
||||
print_error('invalidcourseid');
|
||||
}
|
||||
if (! $cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forumentity);
|
||||
$parent = $postdatamapper->to_legacy_object($parententity);
|
||||
$discussion = $discussiondatamapper->to_legacy_object($discussionentity);
|
||||
$forum = $forumdatamapper->to_legacy_object($forumentity);
|
||||
$course = $forumentity->get_course_record();
|
||||
$modcontext = $forumentity->get_context();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
print_error('invalidcoursemodule');
|
||||
}
|
||||
|
||||
// Ensure lang, theme, etc. is set up properly. MDL-6926.
|
||||
$PAGE->set_cm($cm, $course, $forum);
|
||||
|
||||
// Retrieve the contexts.
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
|
||||
if (! forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext)) {
|
||||
if (!$capabilitymanager->can_reply_to_post($USER, $discussionentity, $parententity)) {
|
||||
if (!isguestuser()) {
|
||||
if (!is_enrolled($coursecontext)) { // User is a guest here!
|
||||
$SESSION->wantsurl = qualified_me();
|
||||
@@ -215,8 +239,11 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
print_error("activityiscurrentlyhidden");
|
||||
}
|
||||
|
||||
// Load up the $post variable.
|
||||
if ($parententity->is_private_reply()) {
|
||||
print_error('cannotreplytoprivatereply', 'forum');
|
||||
}
|
||||
|
||||
// Load up the $post variable.
|
||||
$post = new stdClass();
|
||||
$post->course = $course->id;
|
||||
$post->forum = $forum->id;
|
||||
@@ -225,6 +252,8 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
$post->subject = $parent->subject;
|
||||
$post->userid = $USER->id;
|
||||
$post->message = '';
|
||||
$post->parentpostauthor = $parent->userid;
|
||||
$canreplyprivately = $capabilitymanager->can_reply_privately_to_post($USER, $parententity);
|
||||
|
||||
$post->groupid = ($discussion->groupid == -1) ? 0 : $discussion->groupid;
|
||||
|
||||
@@ -236,30 +265,38 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
// Unsetting this will allow the correct return URL to be calculated later.
|
||||
unset($SESSION->fromdiscussion);
|
||||
|
||||
} else if (!empty($edit)) { // User is editing their own post.
|
||||
} else if (!empty($edit)) {
|
||||
// User is editing their own post.
|
||||
|
||||
if (! $post = forum_get_post_full($edit)) {
|
||||
$postentity = $postvault->get_from_id($edit);
|
||||
if (empty($postentity)) {
|
||||
print_error('invalidpostid', 'forum');
|
||||
}
|
||||
if ($post->parent) {
|
||||
if (! $parent = forum_get_post_full($post->parent)) {
|
||||
print_error('invalidparentpostid', 'forum');
|
||||
}
|
||||
if ($postentity->has_parent()) {
|
||||
$parententity = $postvault->get_from_id($postentity->get_parent_id());
|
||||
$parent = $postdatamapper->to_legacy_object($parententity);
|
||||
}
|
||||
|
||||
if (! $discussion = $DB->get_record("forum_discussions", array("id" => $post->discussion))) {
|
||||
$discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
|
||||
if (empty($discussionentity)) {
|
||||
print_error('notpartofdiscussion', 'forum');
|
||||
}
|
||||
if (! $forum = $DB->get_record("forum", array("id" => $discussion->forum))) {
|
||||
|
||||
$forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
|
||||
if (empty($forumentity)) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
}
|
||||
if (! $course = $DB->get_record("course", array("id" => $discussion->course))) {
|
||||
print_error('invalidcourseid');
|
||||
}
|
||||
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forumentity);
|
||||
$post = $postdatamapper->to_legacy_object($postentity);
|
||||
$discussion = $discussiondatamapper->to_legacy_object($discussionentity);
|
||||
$forum = $forumdatamapper->to_legacy_object($forumentity);
|
||||
$course = $forumentity->get_course_record();
|
||||
$modcontext = $forumentity->get_context();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
print_error('invalidcoursemodule');
|
||||
} else {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
}
|
||||
|
||||
$PAGE->set_cm($cm, $course, $forum);
|
||||
@@ -275,72 +312,92 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
print_error('cannoteditposts', 'forum');
|
||||
}
|
||||
|
||||
|
||||
// Load up the $post variable.
|
||||
$post->edit = $edit;
|
||||
$post->course = $course->id;
|
||||
$post->forum = $forum->id;
|
||||
$post->groupid = ($discussion->groupid == -1) ? 0 : $discussion->groupid;
|
||||
if ($postentity->has_parent()) {
|
||||
$canreplyprivately = forum_user_can_reply_privately($modcontext, $parent);
|
||||
}
|
||||
|
||||
$post = trusttext_pre_edit($post, 'message', $modcontext);
|
||||
|
||||
// Unsetting this will allow the correct return URL to be calculated later.
|
||||
unset($SESSION->fromdiscussion);
|
||||
|
||||
} else if (!empty($delete)) { // User is deleting a post.
|
||||
} else if (!empty($delete)) {
|
||||
// User is deleting a post.
|
||||
|
||||
if (! $post = forum_get_post_full($delete)) {
|
||||
$postentity = $postvault->get_from_id($delete);
|
||||
if (empty($postentity)) {
|
||||
print_error('invalidpostid', 'forum');
|
||||
}
|
||||
if (! $discussion = $DB->get_record("forum_discussions", array("id" => $post->discussion))) {
|
||||
|
||||
$discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
|
||||
if (empty($discussionentity)) {
|
||||
print_error('notpartofdiscussion', 'forum');
|
||||
}
|
||||
if (! $forum = $DB->get_record("forum", array("id" => $discussion->forum))) {
|
||||
|
||||
$forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
|
||||
if (empty($forumentity)) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
}
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $forum->course)) {
|
||||
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forumentity);
|
||||
$post = $postdatamapper->to_legacy_object($postentity);
|
||||
$discussion = $discussiondatamapper->to_legacy_object($discussionentity);
|
||||
$forum = $forumdatamapper->to_legacy_object($forumentity);
|
||||
$course = $forumentity->get_course_record();
|
||||
$modcontext = $forumentity->get_context();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
print_error('invalidcoursemodule');
|
||||
}
|
||||
if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
|
||||
print_error('invalidcourseid');
|
||||
}
|
||||
|
||||
require_login($course, false, $cm);
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
|
||||
$postentity = $entityfactory->get_post_from_stdclass($post);
|
||||
$discussionentity = $entityfactory->get_discussion_from_stdclass($discussion);
|
||||
$forumentity = $entityfactory->get_forum_from_stdclass($forum, $modcontext, $cm, $course);
|
||||
|
||||
if ( !(($post->userid == $USER->id && has_capability('mod/forum:deleteownpost', $modcontext))
|
||||
|| has_capability('mod/forum:deleteanypost', $modcontext)) ) {
|
||||
print_error('cannotdeletepost', 'forum');
|
||||
if (!$capabilitymanager->can_delete_post($USER, $discussionentity, $postentity)) {
|
||||
redirect(
|
||||
$urlfactory->get_discussion_view_url_from_discussion($discussionentity),
|
||||
get_string('cannotdeletepost', 'forum'),
|
||||
null,
|
||||
\core\output\notice::NOTIFY_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$replycount = $postvault->get_reply_count_for_post_id_in_discussion_id(
|
||||
$USER, $postentity->get_id(), $discussionentity->get_id(), true);
|
||||
|
||||
$replycount = forum_count_replies($post);
|
||||
|
||||
if (!empty($confirm) && confirm_sesskey()) { // User has confirmed the delete.
|
||||
if (!empty($confirm) && confirm_sesskey()) {
|
||||
// User has confirmed the delete.
|
||||
// Check user capability to delete post.
|
||||
$timepassed = time() - $post->created;
|
||||
if (($timepassed > $CFG->maxeditingtime) && !has_capability('mod/forum:deleteanypost', $modcontext)) {
|
||||
print_error("cannotdeletepost", "forum",
|
||||
forum_go_back_to(new moodle_url("/mod/forum/discuss.php", array('d' => $post->discussion))));
|
||||
}
|
||||
|
||||
if ($post->totalscore) {
|
||||
notice(get_string('couldnotdeleteratings', 'rating'),
|
||||
forum_go_back_to(new moodle_url("/mod/forum/discuss.php", array('d' => $post->discussion))));
|
||||
|
||||
redirect(
|
||||
$urlfactory->get_discussion_view_url_from_discussion($discussionentity),
|
||||
get_string('couldnotdeleteratings', 'rating'),
|
||||
null,
|
||||
\core\output\notice::NOTIFY_ERROR
|
||||
);
|
||||
} else if ($replycount && !has_capability('mod/forum:deleteanypost', $modcontext)) {
|
||||
print_error("couldnotdeletereplies", "forum",
|
||||
forum_go_back_to(new moodle_url("/mod/forum/discuss.php", array('d' => $post->discussion))));
|
||||
|
||||
redirect(
|
||||
$urlfactory->get_discussion_view_url_from_discussion($discussionentity),
|
||||
get_string('couldnotdeletereplies', 'rating'),
|
||||
null,
|
||||
\core\output\notice::NOTIFY_ERROR
|
||||
);
|
||||
} else {
|
||||
if (! $post->parent) { // Post is a discussion topic as well, so delete discussion.
|
||||
if (!$postentity->has_parent()) {
|
||||
// Post is a discussion topic as well, so delete discussion.
|
||||
if ($forum->type == 'single') {
|
||||
notice("Sorry, but you are not allowed to delete that discussion!",
|
||||
forum_go_back_to(new moodle_url("/mod/forum/discuss.php", array('d' => $post->discussion))));
|
||||
redirect(
|
||||
$urlfactory->get_discussion_view_url_from_discussion($discussionentity),
|
||||
get_string('cannotdeletediscussioninsinglediscussion', 'rating'),
|
||||
null,
|
||||
\core\output\notice::NOTIFY_ERROR
|
||||
);
|
||||
}
|
||||
forum_delete_discussion($discussion, false, $course, $cm, $forum);
|
||||
|
||||
@@ -356,41 +413,45 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
$event->add_record_snapshot('forum_discussions', $discussion);
|
||||
$event->trigger();
|
||||
|
||||
$message = get_string('eventdiscussiondeleted', 'forum');
|
||||
redirect(
|
||||
new moodle_url('/mod/forum/view.php', ['f' => $discussion->forum]),
|
||||
$message,
|
||||
$urlfactory->get_forum_view_url_from_forum($forumentity),
|
||||
get_string('eventdiscussiondeleted', 'forum'),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_SUCCESS
|
||||
);
|
||||
|
||||
} else if (forum_delete_post($post, has_capability('mod/forum:deleteanypost', $modcontext),
|
||||
$course, $cm, $forum)) {
|
||||
} else {
|
||||
$deleted = forum_delete_post($post, has_capability('mod/forum:deleteanypost', $modcontext), $course, $cm, $forum);
|
||||
|
||||
if (!$deleted) {
|
||||
redirect(
|
||||
$urlfactory->get_discussion_view_url_from_post($postentity),
|
||||
get_string('errorwhiledelete', 'forum'),
|
||||
null,
|
||||
\core\output\notice::NOTIFY_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
if ($forum->type == 'single') {
|
||||
// Single discussion forums are an exception. We show
|
||||
// the forum itself since it only has one discussion
|
||||
// thread.
|
||||
$discussionurl = new moodle_url("/mod/forum/view.php", array('f' => $forum->id));
|
||||
// Single discussion forums are an exception.
|
||||
// We show the forum itself since it only has one discussion thread.
|
||||
$discussionurl = $urlfactory->get_forum_view_url_from_forum($forumentity);
|
||||
} else {
|
||||
$discussionurl = new moodle_url("/mod/forum/discuss.php", array('d' => $discussion->id));
|
||||
$discussionurl = $urlfactory->get_discussion_view_url_from_discussion($discussionentity);
|
||||
}
|
||||
|
||||
$message = get_string('eventpostdeleted', 'forum');
|
||||
redirect(
|
||||
forum_go_back_to($discussionurl),
|
||||
$message,
|
||||
get_string('eventpostdeleted', 'forum'),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_SUCCESS
|
||||
);
|
||||
} else {
|
||||
print_error('errorwhiledelete', 'forum');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else { // User just asked to delete something.
|
||||
|
||||
} else {
|
||||
// User just asked to delete something.
|
||||
forum_set_return();
|
||||
$PAGE->navbar->add(get_string('delete', 'forum'));
|
||||
$PAGE->set_title($course->shortname);
|
||||
@@ -398,9 +459,14 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
|
||||
if ($replycount) {
|
||||
if (!has_capability('mod/forum:deleteanypost', $modcontext)) {
|
||||
print_error("couldnotdeletereplies", "forum",
|
||||
forum_go_back_to(new moodle_url('/mod/forum/discuss.php', array('d' => $post->discussion), 'p'.$post->id)));
|
||||
redirect(
|
||||
forum_go_back_to($urlfactory->get_view_post_url_from_post($postentity)),
|
||||
get_string('couldnotdeletereplies', 'rating'),
|
||||
null,
|
||||
\core\output\notice::NOTIFY_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(format_string($forum->name), 2);
|
||||
echo $OUTPUT->confirm(get_string("deletesureplural", "forum", $replycount + 1),
|
||||
@@ -408,16 +474,21 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id);
|
||||
|
||||
$postentities = [$postentity];
|
||||
|
||||
if (empty($post->edit)) {
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$replies = $postvault->get_replies_to_post($postentity, 'created ASC');
|
||||
$replies = $postvault->get_replies_to_post(
|
||||
$USER,
|
||||
$postentity,
|
||||
// Note: All replies are fetched here as the user has deleteanypost.
|
||||
true,
|
||||
'created ASC'
|
||||
);
|
||||
$postentities = array_merge($postentities, $replies);
|
||||
}
|
||||
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$postsrenderer = $rendererfactory->get_single_discussion_posts_renderer(FORUM_MODE_NESTED, true);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], [$postentity]);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], $postentities);
|
||||
} else {
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(format_string($forum->name), 2);
|
||||
@@ -434,31 +505,51 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
echo $OUTPUT->footer();
|
||||
die;
|
||||
|
||||
} else if (!empty($prune)) {
|
||||
// Pruning.
|
||||
|
||||
} else if (!empty($prune)) { // Pruning.
|
||||
|
||||
if (!$post = forum_get_post_full($prune)) {
|
||||
$postentity = $postvault->get_from_id($prune);
|
||||
if (empty($postentity)) {
|
||||
print_error('invalidpostid', 'forum');
|
||||
}
|
||||
if (!$discussion = $DB->get_record("forum_discussions", array("id" => $post->discussion))) {
|
||||
|
||||
$discussionentity = $discussionvault->get_from_id($postentity->get_discussion_id());
|
||||
if (empty($discussionentity)) {
|
||||
print_error('notpartofdiscussion', 'forum');
|
||||
}
|
||||
if (!$forum = $DB->get_record("forum", array("id" => $discussion->forum))) {
|
||||
|
||||
$forumentity = $forumvault->get_from_id($discussionentity->get_forum_id());
|
||||
if (empty($forumentity)) {
|
||||
print_error('invalidforumid', 'forum');
|
||||
}
|
||||
if ($forum->type == 'single') {
|
||||
print_error('cannotsplit', 'forum');
|
||||
}
|
||||
if (!$post->parent) {
|
||||
print_error('alreadyfirstpost', 'forum');
|
||||
}
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $forum->course)) { // For the logs.
|
||||
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forumentity);
|
||||
$post = $postdatamapper->to_legacy_object($postentity);
|
||||
$discussion = $discussiondatamapper->to_legacy_object($discussionentity);
|
||||
$forum = $forumdatamapper->to_legacy_object($forumentity);
|
||||
$course = $forumentity->get_course_record();
|
||||
$modcontext = $forumentity->get_context();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
|
||||
if (!$cm = get_coursemodule_from_instance("forum", $forum->id, $course->id)) {
|
||||
print_error('invalidcoursemodule');
|
||||
} else {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
}
|
||||
if (!has_capability('mod/forum:splitdiscussions', $modcontext)) {
|
||||
print_error('cannotsplit', 'forum');
|
||||
|
||||
if (!$postentity->has_parent()) {
|
||||
redirect(
|
||||
$urlfactory->get_discussion_view_url_from_discussion($discussionentity),
|
||||
get_string('alreadyfirstpost', 'forum'),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_ERROR
|
||||
);
|
||||
}
|
||||
if (!$capabilitymanager->can_split_post($USER, $discussionentity, $postentity)) {
|
||||
redirect(
|
||||
$urlfactory->get_discussion_view_url_from_discussion($discussionentity),
|
||||
get_string('cannotsplit', 'forum'),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$PAGE->set_cm($cm);
|
||||
@@ -467,7 +558,7 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
$prunemform = new mod_forum_prune_form(null, array('prune' => $prune, 'confirm' => $prune));
|
||||
|
||||
if ($prunemform->is_cancelled()) {
|
||||
redirect(forum_go_back_to(new moodle_url("/mod/forum/discuss.php", array('d' => $post->discussion))));
|
||||
redirect(forum_go_back_to($urlfactory->get_discussion_view_url_from_discussion($discussionentity)));
|
||||
} else if ($fromform = $prunemform->get_data()) {
|
||||
// User submits the data.
|
||||
$newdiscussion = new stdClass();
|
||||
@@ -490,6 +581,7 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
$newpost->subject = $name;
|
||||
|
||||
$DB->update_record("forum_posts", $newpost);
|
||||
$postentity = $postvault->get_from_id($postentity->get_id());
|
||||
|
||||
forum_change_discussionid($post->id, $newid);
|
||||
|
||||
@@ -531,10 +623,9 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
$event->add_record_snapshot('forum_discussions', $discussion);
|
||||
$event->trigger();
|
||||
|
||||
$message = get_string('discussionsplit', 'forum');
|
||||
redirect(
|
||||
forum_go_back_to(new moodle_url("/mod/forum/discuss.php", array('d' => $newid))),
|
||||
$message,
|
||||
forum_go_back_to($urlfactory->get_discussion_view_url_from_post($postentity)),
|
||||
get_string('discussionsplit', 'forum'),
|
||||
null,
|
||||
\core\output\notification::NOTIFY_SUCCESS
|
||||
);
|
||||
@@ -567,18 +658,8 @@ if (!empty($forum)) { // User is starting a new discussion in a forum.
|
||||
|
||||
}
|
||||
|
||||
if (!isset($coursecontext)) {
|
||||
// Has not yet been set by post.php.
|
||||
$coursecontext = context_course::instance($forum->course);
|
||||
}
|
||||
|
||||
|
||||
// From now on user must be logged on properly.
|
||||
|
||||
if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) { // For the logs.
|
||||
print_error('invalidcoursemodule');
|
||||
}
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
require_login($course, false, $cm);
|
||||
|
||||
if (isguestuser()) {
|
||||
@@ -586,21 +667,19 @@ if (isguestuser()) {
|
||||
print_error('noguest');
|
||||
}
|
||||
|
||||
if (!isset($forum->maxattachments)) { // TODO - delete this once we add a field to the forum table.
|
||||
$forum->maxattachments = 3;
|
||||
}
|
||||
|
||||
$thresholdwarning = forum_check_throttling($forum, $cm);
|
||||
$mformpost = new mod_forum_post_form('post.php', array('course' => $course,
|
||||
'cm' => $cm,
|
||||
'coursecontext' => $coursecontext,
|
||||
'modcontext' => $modcontext,
|
||||
'forum' => $forum,
|
||||
'post' => $post,
|
||||
'subscribe' => \mod_forum\subscriptions::is_subscribed($USER->id, $forum,
|
||||
null, $cm),
|
||||
'thresholdwarning' => $thresholdwarning,
|
||||
'edit' => $edit), 'post', '', array('id' => 'mformforum'));
|
||||
$mformpost = new mod_forum_post_form('post.php', [
|
||||
'course' => $course,
|
||||
'cm' => $cm,
|
||||
'coursecontext' => $coursecontext,
|
||||
'modcontext' => $modcontext,
|
||||
'forum' => $forum,
|
||||
'post' => $post,
|
||||
'subscribe' => \mod_forum\subscriptions::is_subscribed($USER->id, $forum, null, $cm),
|
||||
'thresholdwarning' => $thresholdwarning,
|
||||
'edit' => $edit,
|
||||
'canreplyprivately' => $canreplyprivately,
|
||||
], 'post', '', array('id' => 'mformforum'));
|
||||
|
||||
$draftitemid = file_get_submitted_draft_itemid('attachments');
|
||||
$postid = empty($post->id) ? null : $post->id;
|
||||
@@ -694,16 +773,16 @@ $mformpost->set_data(
|
||||
);
|
||||
|
||||
if ($mformpost->is_cancelled()) {
|
||||
if (!isset($discussion->id) || $forum->type === 'qanda') {
|
||||
// Q and A forums don't have a discussion page, so treat them like a new thread..
|
||||
redirect(new moodle_url('/mod/forum/view.php', array('f' => $forum->id)));
|
||||
if (!isset($discussion->id) || $forum->type === 'single') {
|
||||
// Single forums don't have a discussion page.
|
||||
redirect($urlfactory->get_forum_view_url_from_forum($forumentity));
|
||||
} else {
|
||||
redirect(new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id)));
|
||||
redirect($urlfactory->get_discussion_view_url_from_discussion($discussionentity));
|
||||
}
|
||||
} else if ($fromform = $mformpost->get_data()) {
|
||||
|
||||
if (empty($SESSION->fromurl)) {
|
||||
$errordestination = "$CFG->wwwroot/mod/forum/view.php?f=$forum->id";
|
||||
$errordestination = $urlfactory->get_forum_view_url_from_forum($forumentity);
|
||||
} else {
|
||||
$errordestination = $SESSION->fromurl;
|
||||
}
|
||||
@@ -717,43 +796,44 @@ if ($mformpost->is_cancelled()) {
|
||||
// Clean message text.
|
||||
$fromform = trusttext_pre_edit($fromform, 'message', $modcontext);
|
||||
|
||||
if ($fromform->edit) { // Updating a post.
|
||||
if ($fromform->edit) {
|
||||
// Updating a post.
|
||||
unset($fromform->groupid);
|
||||
$fromform->id = $fromform->edit;
|
||||
$message = '';
|
||||
|
||||
// Fix for bug #4314.
|
||||
if (!$realpost = $DB->get_record('forum_posts', array('id' => $fromform->id))) {
|
||||
$realpost = new stdClass();
|
||||
$realpost->userid = -1;
|
||||
if (!$capabilitymanager->can_edit_post($USER, $discussionentity, $postentity)) {
|
||||
redirect(
|
||||
$urlfactory->get_view_post_url_from_post($postentity),
|
||||
get_string('cannotupdatepost', 'forum'),
|
||||
null,
|
||||
\core\output\notification::ERROR
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// If user has edit any post capability
|
||||
// or has either startnewdiscussion or reply capability and is editting own post
|
||||
// then he can proceed
|
||||
// MDL-7066.
|
||||
if ( !(($realpost->userid == $USER->id && (has_capability('mod/forum:replypost', $modcontext)
|
||||
|| has_capability('mod/forum:startdiscussion', $modcontext))) ||
|
||||
has_capability('mod/forum:editanypost', $modcontext)) ) {
|
||||
print_error('cannotupdatepost', 'forum');
|
||||
}
|
||||
|
||||
// If the user has access to all groups and they are changing the group, then update the post.
|
||||
if (isset($fromform->groupinfo) && has_capability('mod/forum:movediscussions', $modcontext)) {
|
||||
if (isset($fromform->groupinfo) && $capabilitymanager->can_move_discussions($USER)) {
|
||||
// If the user has access to all groups and they are changing the group, then update the post.
|
||||
if (empty($fromform->groupinfo)) {
|
||||
$fromform->groupinfo = -1;
|
||||
}
|
||||
|
||||
if (!forum_user_can_post_discussion($forum, $fromform->groupinfo, null, $cm, $modcontext)) {
|
||||
print_error('cannotupdatepost', 'forum');
|
||||
if (!$capabilitymanager->can_create_discussions($USER, $fromform->groupinfo)) {
|
||||
redirect(
|
||||
$urlfactory->get_view_post_url_from_post($postentity),
|
||||
get_string('cannotupdatepost', 'forum'),
|
||||
null,
|
||||
\core\output\notification::ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$DB->set_field('forum_discussions', 'groupid', $fromform->groupinfo, array('firstpost' => $fromform->id));
|
||||
if ($discussionentity->get_group_id() != $fromform->groupinfo) {
|
||||
$DB->set_field('forum_discussions', 'groupid', $fromform->groupinfo, array('firstpost' => $fromform->id));
|
||||
}
|
||||
}
|
||||
|
||||
// When editing first post/discussion.
|
||||
if (!$fromform->parent) {
|
||||
if (has_capability('mod/forum:pindiscussions', $modcontext)) {
|
||||
if ($postentity->has_parent()) {
|
||||
if ($capabilitymanager->can_pin_discussions($USER)) {
|
||||
// Can change pinned if we have capability.
|
||||
$fromform->pinned = !empty($fromform->pinned) ? FORUM_DISCUSSION_PINNED : FORUM_DISCUSSION_UNPINNED;
|
||||
} else {
|
||||
@@ -761,35 +841,33 @@ if ($mformpost->is_cancelled()) {
|
||||
unset($fromform->pinned);
|
||||
}
|
||||
}
|
||||
$updatepost = $fromform; // Realpost.
|
||||
$updatepost = $fromform;
|
||||
$updatepost->forum = $forum->id;
|
||||
if (!forum_update_post($updatepost, $mformpost)) {
|
||||
print_error("couldnotupdate", "forum", $errordestination);
|
||||
}
|
||||
|
||||
// MDL-11818.
|
||||
if (($forum->type == 'single') && ($updatepost->parent == '0')) {
|
||||
if ('single' == $forumentity->get_type() && !$postentity->has_parent()) {
|
||||
// Updating first post of single discussion type -> updating forum intro.
|
||||
$forum->intro = $updatepost->message;
|
||||
$forum->timemodified = time();
|
||||
$DB->update_record("forum", $forum);
|
||||
}
|
||||
|
||||
if ($realpost->userid == $USER->id) {
|
||||
if ($USER->id === $postentity->get_author_id()) {
|
||||
$message .= get_string("postupdated", "forum");
|
||||
} else {
|
||||
$realuser = $DB->get_record('user', array('id' => $realpost->userid));
|
||||
$realuser = \core_user::get_user($postentity->get_author_id());
|
||||
$message .= get_string("editedpostupdated", "forum", fullname($realuser));
|
||||
}
|
||||
|
||||
$subscribemessage = forum_post_subscription($fromform, $forum, $discussion);
|
||||
if ($forum->type == 'single') {
|
||||
// Single discussion forums are an exception. We show
|
||||
// the forum itself since it only has one discussion
|
||||
// thread.
|
||||
$discussionurl = new moodle_url("/mod/forum/view.php", array('f' => $forum->id));
|
||||
if ('single' == $forumentity->get_type()) {
|
||||
// Single discussion forums are an exception.
|
||||
// We show the forum itself since it only has one discussion thread.
|
||||
$discussionurl = $urlfactory->get_forum_view_url_from_forum($forumentity);
|
||||
} else {
|
||||
$discussionurl = new moodle_url("/mod/forum/discuss.php", array('d' => $discussion->id), 'p' . $fromform->id);
|
||||
$discussionurl = $urlfactory->get_view_post_url_from_post($postentity);
|
||||
}
|
||||
|
||||
$params = array(
|
||||
@@ -802,8 +880,8 @@ if ($mformpost->is_cancelled()) {
|
||||
)
|
||||
);
|
||||
|
||||
if ($realpost->userid !== $USER->id) {
|
||||
$params['relateduserid'] = $realpost->userid;
|
||||
if ($USER->id !== $postentity->get_author_id()) {
|
||||
$params['relateduserid'] = $postentity->get_author_id();
|
||||
}
|
||||
|
||||
$event = \mod_forum\event\post_updated::create($params);
|
||||
@@ -817,7 +895,8 @@ if ($mformpost->is_cancelled()) {
|
||||
\core\output\notification::NOTIFY_SUCCESS
|
||||
);
|
||||
|
||||
} else if ($fromform->discussion) { // Adding a new post to an existing discussion
|
||||
} else if ($fromform->discussion) {
|
||||
// Adding a new post to an existing discussion
|
||||
// Before we add this we must check that the user will not exceed the blocking threshold.
|
||||
forum_check_blocking_threshold($thresholdwarning);
|
||||
|
||||
@@ -826,6 +905,7 @@ if ($mformpost->is_cancelled()) {
|
||||
$addpost = $fromform;
|
||||
$addpost->forum = $forum->id;
|
||||
if ($fromform->id = forum_add_new_post($addpost, $mformpost)) {
|
||||
$postentity = $postvault->get_from_id($fromform->id);
|
||||
$fromform->deleted = 0;
|
||||
$subscribemessage = forum_post_subscription($fromform, $forum, $discussion);
|
||||
|
||||
@@ -837,12 +917,11 @@ if ($mformpost->is_cancelled()) {
|
||||
}
|
||||
|
||||
if ($forum->type == 'single') {
|
||||
// Single discussion forums are an exception. We show
|
||||
// the forum itself since it only has one discussion
|
||||
// thread.
|
||||
$discussionurl = new moodle_url("/mod/forum/view.php", array('f' => $forum->id), 'p'.$fromform->id);
|
||||
// Single discussion forums are an exception.
|
||||
// We show the forum itself since it only has one discussion thread.
|
||||
$discussionurl = $urlfactory->get_forum_view_url_from_forum($forumentity);
|
||||
} else {
|
||||
$discussionurl = new moodle_url("/mod/forum/discuss.php", array('d' => $discussion->id), 'p'.$fromform->id);
|
||||
$discussionurl = $urlfactory->get_view_post_url_from_post($postentity);
|
||||
}
|
||||
|
||||
$params = array(
|
||||
@@ -878,7 +957,8 @@ if ($mformpost->is_cancelled()) {
|
||||
}
|
||||
exit;
|
||||
|
||||
} else { // Adding a new discussion.
|
||||
} else {
|
||||
// Adding a new discussion.
|
||||
// The location to redirect to after successfully posting.
|
||||
$redirectto = new moodle_url('/mod/forum/view.php', array('f' => $fromform->forum));
|
||||
|
||||
@@ -893,11 +973,10 @@ if ($mformpost->is_cancelled()) {
|
||||
}
|
||||
$discussion->timestart = $fromform->timestart;
|
||||
$discussion->timeend = $fromform->timeend;
|
||||
$discussion->pinned = FORUM_DISCUSSION_UNPINNED;
|
||||
|
||||
if (has_capability('mod/forum:pindiscussions', $modcontext) && !empty($fromform->pinned)) {
|
||||
if (!empty($fromform->pinned) && $capabilitymanager->can_pin_discussions($USER)) {
|
||||
$discussion->pinned = FORUM_DISCUSSION_PINNED;
|
||||
} else {
|
||||
$discussion->pinned = FORUM_DISCUSSION_UNPINNED;
|
||||
}
|
||||
|
||||
$allowedgroups = array();
|
||||
@@ -912,7 +991,7 @@ if ($mformpost->is_cancelled()) {
|
||||
// Note: all groups are returned when in visible groups mode so we must manually filter.
|
||||
$allowedgroups = groups_get_activity_allowed_groups($cm);
|
||||
foreach ($allowedgroups as $groupid => $group) {
|
||||
if (forum_user_can_post_discussion($forum, $groupid, -1, $cm, $modcontext)) {
|
||||
if ($capabilitymanager->can_create_discussions($USER, $groupid)) {
|
||||
$groupstopostto[] = $groupid;
|
||||
}
|
||||
}
|
||||
@@ -933,7 +1012,7 @@ if ($mformpost->is_cancelled()) {
|
||||
forum_check_blocking_threshold($thresholdwarning);
|
||||
|
||||
foreach ($groupstopostto as $group) {
|
||||
if (!forum_user_can_post_discussion($forum, $group, -1, $cm, $modcontext)) {
|
||||
if (!$capabilitymanager->can_create_discussions($USER, $groupid)) {
|
||||
print_error('cannotcreatediscussion', 'forum');
|
||||
}
|
||||
|
||||
@@ -983,21 +1062,15 @@ if ($mformpost->is_cancelled()) {
|
||||
}
|
||||
|
||||
|
||||
// This section is only shown after all checks are in place, and the forumentity and any relevant discussion and post
|
||||
// entity are available.
|
||||
|
||||
// To get here they need to edit a post, and the $post
|
||||
// variable will be loaded with all the particulars,
|
||||
// so bring up the form.
|
||||
|
||||
// Vars $course, $forum are defined. $discussion is for edit and reply only.
|
||||
|
||||
if ($post->discussion) {
|
||||
if (! $toppost = $DB->get_record("forum_posts", array("discussion" => $post->discussion, "parent" => 0))) {
|
||||
print_error('cannotfindparentpost', 'forum', '', $post->id);
|
||||
}
|
||||
if (!empty($discussionentity)) {
|
||||
$titlesubject = format_string($discussionentity->get_name(), true);
|
||||
} else if ('news' == $forumentity->get_type()) {
|
||||
$titlesubject = get_string("addanewtopic", "forum");
|
||||
} else {
|
||||
$toppost = new stdClass();
|
||||
$toppost->subject = ($forum->type == "news") ? get_string("addanewtopic", "forum") :
|
||||
get_string("addanewdiscussion", "forum");
|
||||
$titlesubject = get_string("addanewdiscussion", "forum");
|
||||
}
|
||||
|
||||
if (empty($post->edit)) {
|
||||
@@ -1010,20 +1083,22 @@ if (empty($discussion->name)) {
|
||||
}
|
||||
$discussion->name = $forum->name;
|
||||
}
|
||||
if ($forum->type == 'single') {
|
||||
|
||||
$strdiscussionname = '';
|
||||
if ('single' == $forumentity->get_type()) {
|
||||
// There is only one discussion thread for this forum type. We should
|
||||
// not show the discussion name (same as forum name in this case) in
|
||||
// the breadcrumbs.
|
||||
$strdiscussionname = '';
|
||||
} else {
|
||||
} else if (!empty($discussionentity)) {
|
||||
// Show the discussion name in the breadcrumbs.
|
||||
$strdiscussionname = format_string($discussion->name).':';
|
||||
$strdiscussionname = format_string($discussionentity->get_name()) . ': ';
|
||||
}
|
||||
|
||||
$forcefocus = empty($reply) ? null : 'message';
|
||||
|
||||
if (!empty($discussion->id)) {
|
||||
$PAGE->navbar->add(format_string($toppost->subject, true), "discuss.php?d=$discussion->id");
|
||||
$PAGE->navbar->add($titlesubject, $urlfactory->get_discussion_view_url_from_discussion($discussionentity));
|
||||
}
|
||||
|
||||
if ($post->parent) {
|
||||
@@ -1034,25 +1109,27 @@ if ($edit) {
|
||||
$PAGE->navbar->add(get_string('edit', 'forum'));
|
||||
}
|
||||
|
||||
$PAGE->set_title("$course->shortname: $strdiscussionname ".format_string($toppost->subject));
|
||||
$PAGE->set_title("{$course->shortname}: {$strdiscussionname}{$titlesubject}");
|
||||
$PAGE->set_heading($course->fullname);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(format_string($forum->name), 2);
|
||||
|
||||
// Checkup.
|
||||
if (!empty($parent) && !forum_user_can_see_post($forum, $discussion, $post, null, $cm)) {
|
||||
if (!empty($parententity) && !$capabilitymanager->can_view_post($USER, $discussionentity, $parententity)) {
|
||||
print_error('cannotreply', 'forum');
|
||||
}
|
||||
if (empty($parent) && empty($edit) && !forum_user_can_post_discussion($forum, $groupid, -1, $cm, $modcontext)) {
|
||||
|
||||
if (empty($parententity) && empty($edit) && !$capabilitymanager->can_create_discussions($USER, $groupid)) {
|
||||
print_error('cannotcreatediscussion', 'forum');
|
||||
}
|
||||
|
||||
if ($forum->type == 'qanda'
|
||||
&& !has_capability('mod/forum:viewqandawithoutposting', $modcontext)
|
||||
&& !empty($discussion->id)
|
||||
&& !forum_user_has_posted($forum->id, $discussion->id, $USER->id)) {
|
||||
echo $OUTPUT->notification(get_string('qandanotify', 'forum'));
|
||||
if (!empty($discussionentity) && 'qanda' == $forumentity->get_type()) {
|
||||
$displaywarning = $capabilitymanager->must_post_before_viewing_discussion($USER, $discussionentity);
|
||||
$displaywarning = $displaywarning && !forum_user_has_posted($forumentity->get_id(), $discussionentity->get_id(), $USER->id);
|
||||
if ($displaywarning) {
|
||||
echo $OUTPUT->notification(get_string('qandanotify', 'forum'));
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a warning message and we are not editing a post we need to handle the warning.
|
||||
@@ -1061,27 +1138,24 @@ if (!empty($thresholdwarning) && !$edit) {
|
||||
forum_check_blocking_threshold($thresholdwarning);
|
||||
}
|
||||
|
||||
if (!empty($parent)) {
|
||||
if (!$discussion = $DB->get_record('forum_discussions', array('id' => $parent->discussion))) {
|
||||
print_error('notpartofdiscussion', 'forum');
|
||||
}
|
||||
|
||||
$postentity = $entityfactory->get_post_from_stdclass($parent);
|
||||
$discussionentity = $entityfactory->get_discussion_from_stdclass($discussion);
|
||||
$forumentity = $entityfactory->get_forum_from_stdclass($forum, $modcontext, $cm, $course);
|
||||
$postentities = [$postentity];
|
||||
if (!empty($parententity)) {
|
||||
$postentities = [$parententity];
|
||||
|
||||
if (empty($post->edit)) {
|
||||
if ($forum->type != 'qanda' || forum_user_can_see_discussion($forum, $discussion, $modcontext)) {
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$replies = $postvault->get_replies_to_post($postentity, 'created ASC');
|
||||
if ('qanda' != $forumentity->get_type() || forum_user_can_see_discussion($forum, $discussion, $modcontext)) {
|
||||
$replies = $postvault->get_replies_to_post(
|
||||
$USER,
|
||||
$parententity,
|
||||
$capabilitymanager->can_view_any_private_reply($USER),
|
||||
'created ASC'
|
||||
);
|
||||
$postentities = array_merge($postentities, $replies);
|
||||
}
|
||||
}
|
||||
|
||||
$rendererfactory = mod_forum\local\container::get_renderer_factory();
|
||||
$postsrenderer = $rendererfactory->get_single_discussion_posts_renderer(FORUM_MODE_THREADED, true);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], [$postentity]);
|
||||
echo $postsrenderer->render($USER, [$forumentity], [$discussionentity], $postentities);
|
||||
} else {
|
||||
if (!empty($forum->intro)) {
|
||||
echo $OUTPUT->box(format_module_intro('forum', $forum, $cm->id), 'generalbox', 'intro');
|
||||
@@ -1098,9 +1172,10 @@ if (!empty($formheading)) {
|
||||
echo $OUTPUT->heading($formheading, 2, array('class' => 'accesshide'));
|
||||
}
|
||||
|
||||
$data = new StdClass();
|
||||
if (isset($postid)) {
|
||||
$data->tags = core_tag_tag::get_item_tags_array('mod_forum', 'forum_posts', $postid);
|
||||
if (!empty($postentity)) {
|
||||
$data = (object) [
|
||||
'tags' => core_tag_tag::get_item_tags_array('mod_forum', 'forum_posts', $postentity->get_id())
|
||||
];
|
||||
$mformpost->set_data($data);
|
||||
}
|
||||
|
||||
|
||||
@@ -204,6 +204,8 @@ function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
|
||||
* @return string the SQL query to be used to get the Post details from the forum table of the database
|
||||
*/
|
||||
function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
|
||||
global $USER;
|
||||
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
|
||||
// Get group enforcement SQL.
|
||||
@@ -224,6 +226,15 @@ function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
|
||||
$newsince = '';
|
||||
}
|
||||
|
||||
$canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $modcontext);
|
||||
if (!$canseeprivatereplies) {
|
||||
$privatewhere = ' AND (p.privatereplyto = :currentuser1 OR p.userid = :currentuser2 OR p.privatereplyto = 0)';
|
||||
$params['currentuser1'] = $USER->id;
|
||||
$params['currentuser2'] = $USER->id;
|
||||
} else {
|
||||
$privatewhere = '';
|
||||
}
|
||||
|
||||
$usernamefields = get_all_user_name_fields(true, 'u');
|
||||
$sql = "SELECT p.id AS postid,
|
||||
d.id AS discussionid,
|
||||
@@ -245,6 +256,7 @@ function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
|
||||
WHERE d.forum = {$forum->id} AND
|
||||
p.discussion = d.id AND p.deleted <> 1 AND
|
||||
u.id = p.userid $newsince
|
||||
$privatewhere
|
||||
$groupselect
|
||||
ORDER BY p.created desc";
|
||||
|
||||
|
||||
@@ -327,3 +327,7 @@ span.unread {
|
||||
.path-mod-forum article .nav .nav-link + .nav-link {
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.privatereplyinfo {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@
|
||||
{{{html.authorsubheading}}}
|
||||
</address>
|
||||
{{/isdeleted}}
|
||||
{{#isprivatereply}}
|
||||
<div class="privatereplyinfo">
|
||||
{{#str}}postisprivatereply, forum{{/str}}
|
||||
</div>
|
||||
{{/isprivatereply}}
|
||||
</header>
|
||||
<div>
|
||||
{{{message}}}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
* permalink
|
||||
* unsubscribeforumlink
|
||||
* unsubscribediscussionlink
|
||||
* isprivatereply
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
@@ -92,6 +93,11 @@
|
||||
"date": {{# quote }}{{ postdate }}{{/ quote }}
|
||||
} {{/ str }}
|
||||
</div>
|
||||
{{# isprivatereply }}
|
||||
<div class="privatereplyinfo">
|
||||
{{# str }} postisprivatereply, forum {{/ str }}
|
||||
</div>
|
||||
{{/ isprivatereply }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
* unsubscribeforumlink
|
||||
* unsubscribediscussionlink
|
||||
* forumindexlink
|
||||
* isprivatereply
|
||||
}}
|
||||
{{{ coursename }}} -> {{# str }} forums, forum {{/ str }} -> {{{ forumname }}}{{# showdiscussionname }} -> {{{ discussionname }}} {{/ showdiscussionname }}
|
||||
{{ permalink }}
|
||||
@@ -49,6 +50,9 @@
|
||||
"name": {{# quote }}{{{ authorfullname }}}{{/ quote }},
|
||||
"date": {{# quote}}{{ postdate }}{{/ quote }}
|
||||
} {{/ str }}
|
||||
{{# isprivatereply }}
|
||||
{{# str }} postisprivatereply, forum {{/ str }}
|
||||
{{/ isprivatereply }}
|
||||
---------------------------------------------------------------------
|
||||
{{{ message }}}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
@mod @mod_forum
|
||||
Feature: Forum posts can be replied to in private
|
||||
In order to post feedback to my students
|
||||
As a Teacher
|
||||
I need to be able to reply privately to students
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| teacher2 | Teacher | 2 | teacher2@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| student2 | Student | 2 | student2@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Science 101 | C1 | 0 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| forum | Study discussions | Test forum description | C1 | forum |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| teacher2 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
And I log in as "student1"
|
||||
And I am on "Science 101" course homepage
|
||||
And I add a new discussion to "Study discussions" forum with:
|
||||
| Subject | Answers to the homework |
|
||||
| Message | Here are the answers to last night's homework. |
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Science 101" course homepage
|
||||
And I reply "Answers to the homework" post from "Study discussions" forum with:
|
||||
| Message | How about you and I have a meeting after class about plagiarism? |
|
||||
| Reply privately | 1 |
|
||||
|
||||
Scenario: As a teacher I can see my own response
|
||||
Given I follow "Answers to the homework"
|
||||
Then I should see "How about you and I have a meeting after class about plagiarism?"
|
||||
|
||||
Scenario: As a fellow teacher I can see the other teacher's response
|
||||
Given I log out
|
||||
And I log in as "teacher2"
|
||||
And I am on "Science 101" course homepage
|
||||
And I follow "Study discussions"
|
||||
When I follow "Answers to the homework"
|
||||
Then I should see "How about you and I have a meeting after class about plagiarism?"
|
||||
|
||||
Scenario: As the intended recipient I can see my own response
|
||||
Given I log out
|
||||
And I log in as "student1"
|
||||
And I am on "Science 101" course homepage
|
||||
And I follow "Study discussions"
|
||||
When I follow "Answers to the homework"
|
||||
Then I should see "How about you and I have a meeting after class about plagiarism?"
|
||||
|
||||
Scenario: As a non-privileged user I cannot see my own response
|
||||
Given I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Science 101" course homepage
|
||||
And I follow "Study discussions"
|
||||
When I follow "Answers to the homework"
|
||||
Then I should not see "How about you and I have a meeting after class about plagiarism?"
|
||||
@@ -35,6 +35,7 @@ return new class extends phpunit_coverage_info {
|
||||
/** @var array The list of folders relative to the plugin root to whitelist in coverage generation. */
|
||||
protected $whitelistfolders = [
|
||||
'classes/local',
|
||||
'externallib.php',
|
||||
];
|
||||
|
||||
/** @var array The list of files relative to the plugin root to whitelist in coverage generation. */
|
||||
|
||||
@@ -89,6 +89,7 @@ class mod_forum_entities_discussion_summary_testcase extends advanced_testcase {
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ class mod_forum_entities_discussion_testcase extends advanced_testcase {
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
$notfirstpost = new post_entity(
|
||||
@@ -90,6 +91,7 @@ class mod_forum_entities_discussion_testcase extends advanced_testcase {
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class mod_forum_entities_post_read_receipt_collection_testcase extends advanced_
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
$post = new post_entity(
|
||||
@@ -74,6 +75,7 @@ class mod_forum_entities_post_read_receipt_collection_testcase extends advanced_
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
$collection = new collection_entity([
|
||||
|
||||
@@ -58,6 +58,7 @@ class mod_forum_entities_post_testcase extends advanced_testcase {
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
|
||||
'userfullname' => fullname($user3),
|
||||
'userpictureurl' => '',
|
||||
'deleted' => false,
|
||||
'isprivatereply' => false,
|
||||
);
|
||||
|
||||
$expectedposts['posts'][] = array(
|
||||
@@ -346,6 +347,7 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
|
||||
'userfullname' => fullname($user2),
|
||||
'userpictureurl' => '',
|
||||
'deleted' => false,
|
||||
'isprivatereply' => false,
|
||||
);
|
||||
|
||||
// Test a discussion with two additional posts (total 3 posts).
|
||||
@@ -418,6 +420,308 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get forum posts
|
||||
*
|
||||
* Tests is similar to the get_forum_discussion_posts only utilizing the new return structure and entities
|
||||
*/
|
||||
public function test_mod_forum_get_discussion_posts() {
|
||||
global $CFG, $PAGE;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Set the CFG variable to allow track forums.
|
||||
$CFG->forum_trackreadposts = true;
|
||||
|
||||
$urlfactory = mod_forum\local\container::get_url_factory();
|
||||
$legacyfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
$entityfactory = mod_forum\local\container::get_entity_factory();
|
||||
|
||||
// Create a user who can track forums.
|
||||
$record = new stdClass();
|
||||
$record->trackforums = true;
|
||||
$user1 = self::getDataGenerator()->create_user($record);
|
||||
// Create a bunch of other users to post.
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
$user2entity = $entityfactory->get_author_from_stdclass($user2);
|
||||
$exporteduser2 = [
|
||||
'id' => (int) $user2->id,
|
||||
'fullname' => fullname($user2),
|
||||
'groups' => [],
|
||||
'urls' => [
|
||||
'profile' => $urlfactory->get_author_profile_url($user2entity),
|
||||
'profileimage' => $urlfactory->get_author_profile_image_url($user2entity),
|
||||
]
|
||||
];
|
||||
$user2->fullname = $exporteduser2['fullname'];
|
||||
|
||||
$user3 = self::getDataGenerator()->create_user(['fullname' => "Mr Pants 1"]);
|
||||
$user3entity = $entityfactory->get_author_from_stdclass($user3);
|
||||
$exporteduser3 = [
|
||||
'id' => (int) $user3->id,
|
||||
'fullname' => fullname($user3),
|
||||
'groups' => [],
|
||||
'urls' => [
|
||||
'profile' => $urlfactory->get_author_profile_url($user3entity),
|
||||
'profileimage' => $urlfactory->get_author_profile_image_url($user3entity),
|
||||
]
|
||||
];
|
||||
$user3->fullname = $exporteduser3['fullname'];
|
||||
$forumgenerator = self::getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Set the first created user to the test user.
|
||||
self::setUser($user1);
|
||||
|
||||
// Create course to add the module.
|
||||
$course1 = self::getDataGenerator()->create_course();
|
||||
|
||||
// Forum with tracking off.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->trackingtype = FORUM_TRACKING_OFF;
|
||||
$forum1 = self::getDataGenerator()->create_module('forum', $record);
|
||||
$forum1context = context_module::instance($forum1->cmid);
|
||||
|
||||
// Forum with tracking enabled.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$forum2 = self::getDataGenerator()->create_module('forum', $record);
|
||||
$forum2cm = get_coursemodule_from_id('forum', $forum2->cmid);
|
||||
$forum2context = context_module::instance($forum2->cmid);
|
||||
|
||||
// Add discussions to the forums.
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user1->id;
|
||||
$record->forum = $forum1->id;
|
||||
$discussion1 = $forumgenerator->create_discussion($record);
|
||||
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user2->id;
|
||||
$record->forum = $forum1->id;
|
||||
$discussion2 = $forumgenerator->create_discussion($record);
|
||||
|
||||
$record = new stdClass();
|
||||
$record->course = $course1->id;
|
||||
$record->userid = $user2->id;
|
||||
$record->forum = $forum2->id;
|
||||
$discussion3 = $forumgenerator->create_discussion($record);
|
||||
|
||||
// Add 2 replies to the discussion 1 from different users.
|
||||
$record = new stdClass();
|
||||
$record->discussion = $discussion1->id;
|
||||
$record->parent = $discussion1->firstpost;
|
||||
$record->userid = $user2->id;
|
||||
$discussion1reply1 = $forumgenerator->create_post($record);
|
||||
$filename = 'shouldbeanimage.jpg';
|
||||
// Add a fake inline image to the post.
|
||||
$filerecordinline = array(
|
||||
'contextid' => $forum1context->id,
|
||||
'component' => 'mod_forum',
|
||||
'filearea' => 'post',
|
||||
'itemid' => $discussion1reply1->id,
|
||||
'filepath' => '/',
|
||||
'filename' => $filename,
|
||||
);
|
||||
$fs = get_file_storage();
|
||||
$timepost = time();
|
||||
$fs->create_file_from_string($filerecordinline, 'image contents (not really)');
|
||||
|
||||
$record->parent = $discussion1reply1->id;
|
||||
$record->userid = $user3->id;
|
||||
$discussion1reply2 = $forumgenerator->create_post($record);
|
||||
|
||||
// Enrol the user in the course.
|
||||
$enrol = enrol_get_plugin('manual');
|
||||
// Following line enrol and assign default role id to the user.
|
||||
// So the user automatically gets mod/forum:viewdiscussion on all forums of the course.
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course1->id);
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course1->id);
|
||||
|
||||
// Delete one user, to test that we still receive posts by this user.
|
||||
delete_user($user3);
|
||||
|
||||
// Create what we expect to be returned when querying the discussion.
|
||||
$expectedposts = array(
|
||||
'posts' => array(),
|
||||
'ratinginfo' => array(
|
||||
'contextid' => $forum1context->id,
|
||||
'component' => 'mod_forum',
|
||||
'ratingarea' => 'post',
|
||||
'canviewall' => null,
|
||||
'canviewany' => null,
|
||||
'scales' => array(),
|
||||
'ratings' => array(),
|
||||
),
|
||||
'warnings' => array(),
|
||||
);
|
||||
|
||||
// User pictures are initially empty, we should get the links once the external function is called.
|
||||
$isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion);
|
||||
$isolatedurl->params(['parent' => $discussion1reply2->id]);
|
||||
$expectedposts['posts'][] = array(
|
||||
'id' => $discussion1reply2->id,
|
||||
'discussionid' => $discussion1reply2->discussion,
|
||||
'parentid' => $discussion1reply2->parent,
|
||||
'hasparent' => true,
|
||||
'timecreated' => $discussion1reply2->created,
|
||||
'subject' => $discussion1reply2->subject,
|
||||
'message' => file_rewrite_pluginfile_urls($discussion1reply2->message, 'pluginfile.php',
|
||||
$forum1context->id, 'mod_forum', 'post', $discussion1reply2->id),
|
||||
'messageformat' => 1, // This value is usually changed by external_format_text() function.
|
||||
'unread' => null,
|
||||
'isdeleted' => false,
|
||||
'isprivatereply' => false,
|
||||
'haswordcount' => false,
|
||||
'wordcount' => null,
|
||||
'author'=> $exporteduser3,
|
||||
'attachments' => [],
|
||||
'tags' => [],
|
||||
'html' => [
|
||||
'rating' => null,
|
||||
'taglist' => null,
|
||||
'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser3, $discussion1reply2->created)
|
||||
],
|
||||
'capabilities' => [
|
||||
'view' => 1,
|
||||
'edit' => 0,
|
||||
'delete' => 0,
|
||||
'split' => 0,
|
||||
'reply' => 1,
|
||||
'export' => 0,
|
||||
'controlreadstatus' => 0
|
||||
],
|
||||
'urls' => [
|
||||
'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->id),
|
||||
'viewisolated' => $isolatedurl->out(false),
|
||||
'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply2->discussion, $discussion1reply2->parent),
|
||||
'edit' => null,
|
||||
'delete' =>null,
|
||||
'split' => null,
|
||||
'reply' => (new moodle_url('/mod/forum/post.php#mformforum', [
|
||||
'reply' => $discussion1reply2->id
|
||||
]))->out(false),
|
||||
'export' => null,
|
||||
'markasread' => null,
|
||||
'markasunread' => null,
|
||||
'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply2->discussion),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
$isolatedurl = $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion);
|
||||
$isolatedurl->params(['parent' => $discussion1reply1->id]);
|
||||
$expectedposts['posts'][] = array(
|
||||
'id' => $discussion1reply1->id,
|
||||
'discussionid' => $discussion1reply1->discussion,
|
||||
'parentid' => $discussion1reply1->parent,
|
||||
'hasparent' => true,
|
||||
'timecreated' => $discussion1reply1->created,
|
||||
'subject' => $discussion1reply1->subject,
|
||||
'message' => file_rewrite_pluginfile_urls($discussion1reply1->message, 'pluginfile.php',
|
||||
$forum1context->id, 'mod_forum', 'post', $discussion1reply1->id),
|
||||
'messageformat' => 1, // This value is usually changed by external_format_text() function.
|
||||
'unread' => null,
|
||||
'isdeleted' => false,
|
||||
'isprivatereply' => false,
|
||||
'haswordcount' => false,
|
||||
'wordcount' => null,
|
||||
'author'=> $exporteduser2,
|
||||
'attachments' => [],
|
||||
'tags' => [],
|
||||
'html' => [
|
||||
'rating' => null,
|
||||
'taglist' => null,
|
||||
'authorsubheading' => $forumgenerator->get_author_subheading_html((object)$exporteduser2, $discussion1reply1->created)
|
||||
],
|
||||
'capabilities' => [
|
||||
'view' => 1,
|
||||
'edit' => 0,
|
||||
'delete' => 0,
|
||||
'split' => 0,
|
||||
'reply' => 1,
|
||||
'export' => 0,
|
||||
'controlreadstatus' => 0
|
||||
],
|
||||
'urls' => [
|
||||
'view' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->id),
|
||||
'viewisolated' => $isolatedurl->out(false),
|
||||
'viewparent' => $urlfactory->get_view_post_url_from_post_id($discussion1reply1->discussion, $discussion1reply1->parent),
|
||||
'edit' => null,
|
||||
'delete' =>null,
|
||||
'split' => null,
|
||||
'reply' => (new moodle_url('/mod/forum/post.php#mformforum', [
|
||||
'reply' => $discussion1reply1->id
|
||||
]))->out(false),
|
||||
'export' => null,
|
||||
'markasread' => null,
|
||||
'markasunread' => null,
|
||||
'discuss' => $urlfactory->get_discussion_view_url_from_discussion_id($discussion1reply1->discussion),
|
||||
],
|
||||
);
|
||||
|
||||
// Test a discussion with two additional posts (total 3 posts).
|
||||
$posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
|
||||
$this->assertEquals(3, count($posts['posts']));
|
||||
|
||||
// Unset the initial discussion post.
|
||||
array_pop($posts['posts']);
|
||||
$this->assertEquals($expectedposts, $posts);
|
||||
|
||||
// Check we receive the unread count correctly on tracked forum.
|
||||
forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
|
||||
$result = mod_forum_external::get_forums_by_courses(array($course1->id));
|
||||
$result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
|
||||
foreach ($result as $f) {
|
||||
if ($f['id'] == $forum2->id) {
|
||||
$this->assertEquals(1, $f['unreadpostscount']);
|
||||
}
|
||||
}
|
||||
|
||||
// Test discussion without additional posts. There should be only one post (the one created by the discussion).
|
||||
$posts = mod_forum_external::get_discussion_posts($discussion2->id, 'modified', 'DESC');
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
|
||||
$this->assertEquals(1, count($posts['posts']));
|
||||
|
||||
// Test discussion tracking on not tracked forum.
|
||||
$result = mod_forum_external::view_forum_discussion($discussion1->id);
|
||||
$result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
|
||||
$this->assertTrue($result['status']);
|
||||
$this->assertEmpty($result['warnings']);
|
||||
|
||||
// Test posts have not been marked as read.
|
||||
$posts = mod_forum_external::get_discussion_posts($discussion1->id, 'modified', 'DESC');
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
|
||||
foreach ($posts['posts'] as $post) {
|
||||
$this->assertNull($post['unread']);
|
||||
}
|
||||
|
||||
// Test discussion tracking on tracked forum.
|
||||
$result = mod_forum_external::view_forum_discussion($discussion3->id);
|
||||
$result = external_api::clean_returnvalue(mod_forum_external::view_forum_discussion_returns(), $result);
|
||||
$this->assertTrue($result['status']);
|
||||
$this->assertEmpty($result['warnings']);
|
||||
|
||||
// Test posts have been marked as read.
|
||||
$posts = mod_forum_external::get_discussion_posts($discussion3->id, 'modified', 'DESC');
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_discussion_posts_returns(), $posts);
|
||||
foreach ($posts['posts'] as $post) {
|
||||
$this->assertFalse($post['unread']);
|
||||
}
|
||||
|
||||
// Check we receive 0 unread posts.
|
||||
forum_tp_count_forum_unread_posts($forum2cm, $course1, true); // Reset static cache.
|
||||
$result = mod_forum_external::get_forums_by_courses(array($course1->id));
|
||||
$result = external_api::clean_returnvalue(mod_forum_external::get_forums_by_courses_returns(), $result);
|
||||
foreach ($result as $f) {
|
||||
if ($f['id'] == $forum2->id) {
|
||||
$this->assertEquals(0, $f['unreadpostscount']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get forum posts
|
||||
*/
|
||||
@@ -1301,4 +1605,98 @@ class mod_forum_external_testcase extends externallib_advanced_testcase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test add_discussion_post
|
||||
*/
|
||||
public function test_add_discussion_post_private() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
self::setAdminUser();
|
||||
|
||||
// Create course to add the module.
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
|
||||
// Standard forum.
|
||||
$record = new stdClass();
|
||||
$record->course = $course->id;
|
||||
$forum = self::getDataGenerator()->create_module('forum', $record);
|
||||
$cm = get_coursemodule_from_id('forum', $forum->cmid, 0, false, MUST_EXIST);
|
||||
$forumcontext = context_module::instance($forum->cmid);
|
||||
$generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Create an enrol users.
|
||||
$student1 = self::getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
|
||||
$student2 = self::getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
|
||||
$teacher1 = self::getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($teacher1->id, $course->id, 'editingteacher');
|
||||
$teacher2 = self::getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($teacher2->id, $course->id, 'editingteacher');
|
||||
|
||||
// Add a new discussion to the forum.
|
||||
self::setUser($student1);
|
||||
$record = new stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $student1->id;
|
||||
$record->forum = $forum->id;
|
||||
$discussion = $generator->create_discussion($record);
|
||||
|
||||
// Have the teacher reply privately.
|
||||
self::setUser($teacher1);
|
||||
$post = mod_forum_external::add_discussion_post($discussion->firstpost, 'some subject', 'some text here...', [
|
||||
[
|
||||
'name' => 'private',
|
||||
'value' => true,
|
||||
],
|
||||
]);
|
||||
$post = external_api::clean_returnvalue(mod_forum_external::add_discussion_post_returns(), $post);
|
||||
$privatereply = $DB->get_record('forum_posts', array('id' => $post['postid']));
|
||||
$this->assertEquals($student1->id, $privatereply->privatereplyto);
|
||||
// Bump the time of the private reply to ensure order.
|
||||
$privatereply->created++;
|
||||
$privatereply->modified = $privatereply->created;
|
||||
$DB->update_record('forum_posts', $privatereply);
|
||||
|
||||
// The teacher will receive their private reply.
|
||||
self::setUser($teacher1);
|
||||
$posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
|
||||
$this->assertEquals(2, count($posts['posts']));
|
||||
$this->assertTrue($posts['posts'][0]['isprivatereply']);
|
||||
|
||||
// Another teacher on the course will also receive the private reply.
|
||||
self::setUser($teacher2);
|
||||
$posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
|
||||
$this->assertEquals(2, count($posts['posts']));
|
||||
$this->assertTrue($posts['posts'][0]['isprivatereply']);
|
||||
|
||||
// The student will receive the private reply.
|
||||
self::setUser($student1);
|
||||
$posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
|
||||
$this->assertEquals(2, count($posts['posts']));
|
||||
$this->assertTrue($posts['posts'][0]['isprivatereply']);
|
||||
|
||||
// Another student will not receive the private reply.
|
||||
self::setUser($student2);
|
||||
$posts = mod_forum_external::get_forum_discussion_posts($discussion->id);
|
||||
$posts = external_api::clean_returnvalue(mod_forum_external::get_forum_discussion_posts_returns(), $posts);
|
||||
$this->assertEquals(1, count($posts['posts']));
|
||||
$this->assertFalse($posts['posts'][0]['isprivatereply']);
|
||||
|
||||
// A user cannot reply to a private reply.
|
||||
self::setUser($teacher2);
|
||||
$this->expectException('coding_exception');
|
||||
$post = mod_forum_external::add_discussion_post($privatereply->id, 'some subject', 'some text here...', [
|
||||
'options' => [
|
||||
'name' => 'private',
|
||||
'value' => false,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +304,10 @@ class mod_forum_generator extends testing_module_generator {
|
||||
$record['deleted'] = 0;
|
||||
}
|
||||
|
||||
if (!isset($record['privatereplyto'])) {
|
||||
$record['privatereplyto'] = 0;
|
||||
}
|
||||
|
||||
$record = (object) $record;
|
||||
|
||||
// Add the post.
|
||||
@@ -346,4 +350,22 @@ class mod_forum_generator extends testing_module_generator {
|
||||
}
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracted from exporter/post.php
|
||||
*
|
||||
* Get the HTML to display as a subheading in a post.
|
||||
*
|
||||
* @param stdClass $exportedauthor The exported author object
|
||||
* @param int $timecreated The post time created timestamp if it's to be displayed
|
||||
* @return string
|
||||
*/
|
||||
public function get_author_subheading_html(stdClass $exportedauthor, int $timecreated) : string {
|
||||
$fullname = $exportedauthor->fullname;
|
||||
$profileurl = $exportedauthor->urls['profile'] ?? null;
|
||||
$formatteddate = userdate($timecreated, get_string('strftimedaydatetime', 'core_langconfig'));
|
||||
$name = $profileurl ? "<a href=\"{$profileurl}\">{$fullname}</a>" : $fullname;
|
||||
$date = "<time>{$formatteddate}</time>";
|
||||
return get_string('bynameondate', 'mod_forum', ['name' => $name, 'date' => $date]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ trait mod_forum_tests_generator_trait {
|
||||
// Retrieve the post which was created by create_discussion.
|
||||
$post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
|
||||
|
||||
return array($discussion, $post);
|
||||
return [$discussion, $post];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,24 +108,26 @@ trait mod_forum_tests_generator_trait {
|
||||
* @param stdClass $forum The forum to post in
|
||||
* @param stdClass $discussion The discussion to post in
|
||||
* @param stdClass $author The author to post as
|
||||
* @param array $options Additional options to pass to `create_post`
|
||||
* @return stdClass The forum post
|
||||
*/
|
||||
protected function helper_post_to_discussion($forum, $discussion, $author) {
|
||||
protected function helper_post_to_discussion($forum, $discussion, $author, array $options = []) {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Add a post to the discussion.
|
||||
$record = new stdClass();
|
||||
$record->course = $forum->course;
|
||||
$strre = get_string('re', 'forum');
|
||||
$record->subject = $strre . ' ' . $discussion->subject;
|
||||
$record->userid = $author->id;
|
||||
$record->forum = $forum->id;
|
||||
$record->discussion = $discussion->id;
|
||||
$record->mailnow = 1;
|
||||
$record = array_merge([
|
||||
'course' => $forum->course,
|
||||
'subject' => "{$strre} {$discussion->subject}",
|
||||
'userid' => $author->id,
|
||||
'forum' => $forum->id,
|
||||
'discussion' => $discussion->id,
|
||||
'mailnow' => 1,
|
||||
], $options);
|
||||
|
||||
$post = $generator->create_post($record);
|
||||
$post = $generator->create_post((object) $record);
|
||||
|
||||
return $post;
|
||||
}
|
||||
@@ -135,25 +137,38 @@ trait mod_forum_tests_generator_trait {
|
||||
*
|
||||
* @param stdClass $parent The post being replied to
|
||||
* @param stdClass $author The author to post as
|
||||
* @param array $options Additional options to pass to `create_post`
|
||||
* @return stdClass The forum post
|
||||
*/
|
||||
protected function helper_reply_to_post($parent, $author) {
|
||||
protected function helper_reply_to_post($parent, $author, array $options = []) {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Add a post to the discussion.
|
||||
$strre = get_string('re', 'forum');
|
||||
$record = (object) [
|
||||
$record = (object) array_merge([
|
||||
'discussion' => $parent->discussion,
|
||||
'parent' => $parent->id,
|
||||
'userid' => $author->id,
|
||||
'mailnow' => 1,
|
||||
'subject' => $strre . ' ' . $parent->subject,
|
||||
];
|
||||
], $options);
|
||||
|
||||
$post = $generator->create_post($record);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the role id from it's shortname.
|
||||
*
|
||||
* @param string $roleshortname
|
||||
* @return int
|
||||
*/
|
||||
protected function get_role_id($roleshortname) {
|
||||
global $DB;
|
||||
|
||||
return $DB->get_field('role', 'id', ['shortname' => $roleshortname]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Helper functions used by several tests.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2018 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
|
||||
/**
|
||||
* Helper functions used by several tests.
|
||||
*
|
||||
* @copyright 2018 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait helper {
|
||||
|
||||
/**
|
||||
* Helper to create the required number of users in the specified
|
||||
* course.
|
||||
* Users are enrolled as students.
|
||||
*
|
||||
* @param stdClass $course The course object
|
||||
* @param integer $count The number of users to create
|
||||
* @return array The users created
|
||||
*/
|
||||
protected function helper_create_users($course, $count) {
|
||||
$users = array();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
||||
$users[] = $user;
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new discussion and post within the specified forum, as the
|
||||
* specified author.
|
||||
*
|
||||
* @param stdClass $forum The forum to post in
|
||||
* @param stdClass $author The author to post as
|
||||
* @return array An array containing the discussion object, and the post object
|
||||
*/
|
||||
protected function helper_post_to_forum($forum, $author) {
|
||||
global $DB;
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Create a discussion in the forum, and then add a post to that discussion.
|
||||
$record = new stdClass();
|
||||
$record->course = $forum->course;
|
||||
$record->userid = $author->id;
|
||||
$record->forum = $forum->id;
|
||||
$discussion = $generator->create_discussion($record);
|
||||
|
||||
// Retrieve the post which was created by create_discussion.
|
||||
$post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
|
||||
|
||||
return array($discussion, $post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the post time for the specified post by $factor.
|
||||
*
|
||||
* @param stdClass $post The post to update
|
||||
* @param int $factor The amount to update by
|
||||
*/
|
||||
protected function helper_update_post_time($post, $factor) {
|
||||
global $DB;
|
||||
|
||||
// Update the post to have a created in the past.
|
||||
$DB->set_field('forum_posts', 'created', $post->created + $factor, array('id' => $post->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the subscription time for the specified user/discussion by $factor.
|
||||
*
|
||||
* @param stdClass $user The user to update
|
||||
* @param stdClass $discussion The discussion to update for this user
|
||||
* @param int $factor The amount to update by
|
||||
*/
|
||||
protected function helper_update_subscription_time($user, $discussion, $factor) {
|
||||
global $DB;
|
||||
|
||||
$sub = $DB->get_record('forum_discussion_subs', array('userid' => $user->id, 'discussion' => $discussion->id));
|
||||
|
||||
// Update the subscription to have a preference in the past.
|
||||
$DB->set_field('forum_discussion_subs', 'preference', $sub->preference + $factor, array('id' => $sub->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new post within an existing discussion, as the specified author.
|
||||
*
|
||||
* @param stdClass $forum The forum to post in
|
||||
* @param stdClass $discussion The discussion to post in
|
||||
* @param stdClass $author The author to post as
|
||||
* @return stdClass The forum post
|
||||
*/
|
||||
protected function helper_post_to_discussion($forum, $discussion, $author) {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Add a post to the discussion.
|
||||
$record = new stdClass();
|
||||
$record->course = $forum->course;
|
||||
$strre = get_string('re', 'forum');
|
||||
$record->subject = $strre . ' ' . $discussion->subject;
|
||||
$record->userid = $author->id;
|
||||
$record->forum = $forum->id;
|
||||
$record->discussion = $discussion->id;
|
||||
$record->mailnow = 1;
|
||||
|
||||
$post = $generator->create_post($record);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new post within an existing discussion, as the specified author.
|
||||
*
|
||||
* @param stdClass $parent The post being replied to
|
||||
* @param stdClass $author The author to post as
|
||||
* @return stdClass The forum post
|
||||
*/
|
||||
protected function helper_reply_to_post($parent, $author) {
|
||||
global $DB;
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Add a post to the discussion.
|
||||
$strre = get_string('re', 'forum');
|
||||
$record = (object) [
|
||||
'discussion' => $parent->discussion,
|
||||
'parent' => $parent->id,
|
||||
'userid' => $author->id,
|
||||
'mailnow' => 1,
|
||||
'subject' => $strre . ' ' . $parent->subject,
|
||||
];
|
||||
|
||||
$post = $generator->create_post($record);
|
||||
|
||||
return $post;
|
||||
}
|
||||
}
|
||||
@@ -1712,6 +1712,81 @@ class mod_forum_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals($discussionid, $row->discussion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the reply count when used with private replies.
|
||||
*/
|
||||
public function test_forum_count_discussion_replies_private() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
|
||||
$context = context_module::instance($forum->cmid);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id);
|
||||
|
||||
$teacher = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($teacher->id, $course->id);
|
||||
|
||||
$privilegeduser = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($privilegeduser->id, $course->id, 'editingteacher');
|
||||
|
||||
$otheruser = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
// Create a discussion with some replies.
|
||||
$record = new stdClass();
|
||||
$record->course = $forum->course;
|
||||
$record->forum = $forum->id;
|
||||
$record->userid = $student->id;
|
||||
$discussion = $generator->create_discussion($record);
|
||||
$replycount = 5;
|
||||
$replyto = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
|
||||
|
||||
// Create a couple of standard replies.
|
||||
$post = new stdClass();
|
||||
$post->userid = $student->id;
|
||||
$post->discussion = $discussion->id;
|
||||
$post->parent = $replyto->id;
|
||||
|
||||
for ($i = 0; $i < $replycount; $i++) {
|
||||
$post = $generator->create_post($post);
|
||||
}
|
||||
|
||||
// Create a private reply post from the teacher back to the student.
|
||||
$reply = new stdClass();
|
||||
$reply->userid = $teacher->id;
|
||||
$reply->discussion = $discussion->id;
|
||||
$reply->parent = $replyto->id;
|
||||
$reply->privatereplyto = $replyto->userid;
|
||||
$generator->create_post($reply);
|
||||
|
||||
// The user is the author of the private reply.
|
||||
$this->setUser($teacher->id);
|
||||
$counts = forum_count_discussion_replies($forum->id);
|
||||
$this->assertEquals($replycount + 1, $counts[$discussion->id]->replies);
|
||||
|
||||
// The user is the intended recipient.
|
||||
$this->setUser($student->id);
|
||||
$counts = forum_count_discussion_replies($forum->id);
|
||||
$this->assertEquals($replycount + 1, $counts[$discussion->id]->replies);
|
||||
|
||||
// The user is not the author or recipient, but does have the readprivatereplies capability.
|
||||
$this->setUser($privilegeduser->id);
|
||||
$counts = forum_count_discussion_replies($forum->id, "", -1, -1, 0, true);
|
||||
$this->assertEquals($replycount + 1, $counts[$discussion->id]->replies);
|
||||
|
||||
// The user is not allowed to view this post.
|
||||
$this->setUser($otheruser->id);
|
||||
$counts = forum_count_discussion_replies($forum->id);
|
||||
$this->assertEquals($replycount, $counts[$discussion->id]->replies);
|
||||
}
|
||||
|
||||
public function test_discussion_pinned_sort() {
|
||||
list($forum, $discussionids) = $this->create_multiple_discussions_with_replies(10, 5);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
@@ -3519,4 +3594,49 @@ class mod_forum_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals(mod_forum_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
|
||||
$this->assertEquals(mod_forum_get_completion_active_rule_descriptions(new stdClass()), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the forum_post_is_visible_privately function used in private replies.
|
||||
*/
|
||||
public function test_forum_post_is_visible_privately() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
|
||||
$context = context_module::instance($forum->cmid);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
|
||||
$author = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($author->id, $course->id);
|
||||
|
||||
$recipient = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($recipient->id, $course->id);
|
||||
|
||||
$privilegeduser = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($privilegeduser->id, $course->id, 'editingteacher');
|
||||
|
||||
$otheruser = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($otheruser->id, $course->id);
|
||||
|
||||
// Fake a post - this does not need to be persisted to the DB.
|
||||
$post = new \stdClass();
|
||||
$post->userid = $author->id;
|
||||
$post->privatereplyto = $recipient->id;
|
||||
|
||||
// The user is the author.
|
||||
$this->setUser($author->id);
|
||||
$this->assertTrue(forum_post_is_visible_privately($post, $cm));
|
||||
|
||||
// The user is the intended recipient.
|
||||
$this->setUser($recipient->id);
|
||||
$this->assertTrue(forum_post_is_visible_privately($post, $cm));
|
||||
|
||||
// The user is not the author or recipient, but does have the readprivatereplies capability.
|
||||
$this->setUser($privilegeduser->id);
|
||||
$this->assertTrue(forum_post_is_visible_privately($post, $cm));
|
||||
|
||||
// The user is not allowed to view this post.
|
||||
$this->setUser($otheruser->id);
|
||||
$this->assertFalse(forum_post_is_visible_privately($post, $cm));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +331,56 @@ class mod_forum_mail_testcase extends advanced_testcase {
|
||||
$this->send_notifications_and_assert($recipient, [$post]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that private replies are not sent to users with an automatic subscription unless they are an expected
|
||||
* recipient.
|
||||
*/
|
||||
public function test_automatic_with_private_reply() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a course, with a forum.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
'forcesubscribe' => FORUM_INITIALSUBSCRIBE,
|
||||
]);
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $student);
|
||||
$reply = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
// The private reply is queued to all messages as reply visibility may change between queueing, and sending.
|
||||
$expect = [
|
||||
(object) [
|
||||
'userid' => $student->id,
|
||||
'messages' => 2,
|
||||
],
|
||||
(object) [
|
||||
'userid' => $otherstudent->id,
|
||||
'messages' => 2,
|
||||
],
|
||||
(object) [
|
||||
'userid' => $teacher->id,
|
||||
'messages' => 2,
|
||||
],
|
||||
(object) [
|
||||
'userid' => $otherteacher->id,
|
||||
'messages' => 2,
|
||||
],
|
||||
];
|
||||
$this->queue_tasks_and_assert($expect);
|
||||
|
||||
// The actual messages sent will respect private replies.
|
||||
$this->send_notifications_and_assert($student, [$post, $reply]);
|
||||
$this->send_notifications_and_assert($teacher, [$post, $reply]);
|
||||
$this->send_notifications_and_assert($otherteacher, [$post, $reply]);
|
||||
$this->send_notifications_and_assert($otherstudent, [$post]);
|
||||
}
|
||||
|
||||
public function test_optional() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ use mod_forum\local\managers\capability as capability_manager;
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \mod_forum\local\managers\capability
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
// Make use of the test generator trait.
|
||||
@@ -157,6 +159,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_subscribe_to_forum.
|
||||
*
|
||||
* @covers ::can_subscribe_to_forum
|
||||
*/
|
||||
public function test_can_subscribe_to_forum() {
|
||||
$this->resetAfterTest();
|
||||
@@ -171,6 +175,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_create_discussions.
|
||||
*
|
||||
* @covers ::can_create_discussions
|
||||
*/
|
||||
public function test_can_create_discussions() {
|
||||
$this->resetAfterTest();
|
||||
@@ -238,6 +244,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_access_all_groups.
|
||||
*
|
||||
* @covers ::can_access_all_groups
|
||||
*/
|
||||
public function test_can_access_all_groups() {
|
||||
$this->resetAfterTest();
|
||||
@@ -255,6 +263,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_access_group.
|
||||
*
|
||||
* @covers ::can_access_group
|
||||
*/
|
||||
public function test_can_access_group() {
|
||||
$this->resetAfterTest();
|
||||
@@ -277,6 +287,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_view_discussions.
|
||||
*
|
||||
* @covers ::can_view_discussions
|
||||
*/
|
||||
public function test_can_view_discussions() {
|
||||
$this->resetAfterTest();
|
||||
@@ -294,6 +306,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_move_discussions.
|
||||
*
|
||||
* @covers ::can_move_discussions
|
||||
*/
|
||||
public function test_can_move_discussions() {
|
||||
$this->resetAfterTest();
|
||||
@@ -316,6 +330,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_pin_discussions.
|
||||
*
|
||||
* @covers ::can_pin_discussions
|
||||
*/
|
||||
public function test_can_pin_discussions() {
|
||||
$this->resetAfterTest();
|
||||
@@ -333,6 +349,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_split_discussions.
|
||||
*
|
||||
* @covers ::can_split_discussions
|
||||
*/
|
||||
public function test_can_split_discussions() {
|
||||
$this->resetAfterTest();
|
||||
@@ -355,6 +373,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_export_discussions.
|
||||
*
|
||||
* @covers ::can_export_discussions
|
||||
*/
|
||||
public function test_can_export_discussions() {
|
||||
global $CFG;
|
||||
@@ -378,6 +398,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_manually_control_post_read_status.
|
||||
*
|
||||
* @covers ::can_manually_control_post_read_status
|
||||
*/
|
||||
public function test_can_manually_control_post_read_status() {
|
||||
global $CFG, $DB;
|
||||
@@ -405,6 +427,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test must_post_before_viewing_discussion.
|
||||
*
|
||||
* @covers ::must_post_before_viewing_discussion
|
||||
*/
|
||||
public function test_must_post_before_viewing_discussion() {
|
||||
$this->resetAfterTest();
|
||||
@@ -434,6 +458,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_subscribe_to_discussion.
|
||||
*
|
||||
* @covers ::can_subscribe_to_discussion
|
||||
*/
|
||||
public function test_can_subscribe_to_discussion() {
|
||||
$this->resetAfterTest();
|
||||
@@ -449,6 +475,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_move_discussion.
|
||||
*
|
||||
* @covers ::can_move_discussion
|
||||
*/
|
||||
public function test_can_move_discussion() {
|
||||
$this->resetAfterTest();
|
||||
@@ -472,6 +500,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_pin_discussion.
|
||||
*
|
||||
* @covers ::can_pin_discussion
|
||||
*/
|
||||
public function test_can_pin_discussion() {
|
||||
$this->resetAfterTest();
|
||||
@@ -490,6 +520,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_post_in_discussion.
|
||||
*
|
||||
* @covers ::can_post_in_discussion
|
||||
*/
|
||||
public function test_can_post_in_discussion() {
|
||||
$this->resetAfterTest();
|
||||
@@ -594,6 +626,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_edit_post.
|
||||
*
|
||||
* @covers ::can_edit_post
|
||||
*/
|
||||
public function test_can_edit_post() {
|
||||
global $CFG;
|
||||
@@ -676,6 +710,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_delete_post.
|
||||
*
|
||||
* @covers ::can_delete_post
|
||||
*/
|
||||
public function test_can_delete_post() {
|
||||
global $CFG;
|
||||
@@ -740,6 +776,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_split_post.
|
||||
*
|
||||
* @covers ::can_split_post
|
||||
*/
|
||||
public function test_can_split_post() {
|
||||
$this->resetAfterTest();
|
||||
@@ -777,10 +815,20 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
// Can't split a single discussion forum.
|
||||
$this->assertFalse($capabilitymanager->can_split_post($user, $discussion, $post));
|
||||
|
||||
// Make the post a private reply.
|
||||
$post = $this->entityfactory->get_post_from_stdclass(
|
||||
(object) array_merge((array) $this->postrecord, ['parent' => 5, 'privatereplyto' => $user->id])
|
||||
);
|
||||
|
||||
// Can't split at a private reply.
|
||||
$this->assertFalse($capabilitymanager->can_split_post($user, $discussion, $post));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can_reply_to_post.
|
||||
*
|
||||
* @covers ::can_reply_to_post
|
||||
*/
|
||||
public function test_can_reply_to_post() {
|
||||
$this->resetAfterTest();
|
||||
@@ -882,10 +930,111 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
$this->getDataGenerator()->create_group_member(['userid' => $user->id, 'groupid' => $group->id]);
|
||||
|
||||
$this->assertTrue($capabilitymanager->can_reply_to_post($user, $discussion, $post));
|
||||
|
||||
// Make the post a private reply.
|
||||
$post = $this->entityfactory->get_post_from_stdclass(
|
||||
(object) array_merge((array) $this->postrecord, ['parent' => 5, 'privatereplyto' => $user->id])
|
||||
);
|
||||
|
||||
// Can't reply to a a private reply.
|
||||
$this->assertFalse($capabilitymanager->can_reply_to_post($user, $discussion, $post));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that can_reply_privately_to_post works as expected.
|
||||
*
|
||||
* @covers ::can_reply_privately_to_post
|
||||
*/
|
||||
public function test_can_reply_privately_to_post() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$forum = $this->create_forum();
|
||||
$discussion = $this->discussion;
|
||||
$user = $this->user;
|
||||
$post = $this->post;
|
||||
$capabilitymanager = $this->managerfactory->get_capability_manager($forum);
|
||||
|
||||
// Without the capability, and with a standard post, it is not possible to reply privately.
|
||||
$this->prevent_capability('mod/forum:postprivatereply');
|
||||
$this->assertFalse($capabilitymanager->can_reply_privately_to_post($this->user, $post));
|
||||
|
||||
// With the capability, and a standard post, it is possible to reply privately.
|
||||
$this->give_capability('mod/forum:postprivatereply');
|
||||
$this->assertTrue($capabilitymanager->can_reply_privately_to_post($this->user, $post));
|
||||
|
||||
// Make the post a private reply.
|
||||
$post = $this->entityfactory->get_post_from_stdclass(
|
||||
(object) array_merge((array) $this->postrecord, ['parent' => 5, 'privatereplyto' => $user->id])
|
||||
);
|
||||
|
||||
// Can't ever reply to a a private reply.
|
||||
$this->assertFalse($capabilitymanager->can_reply_privately_to_post($user, $post));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that can_view_post works as expected.
|
||||
*
|
||||
* @covers ::can_view_post
|
||||
*/
|
||||
public function test_can_view_post() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$forum = $this->create_forum();
|
||||
$capabilitymanager = $this->managerfactory->get_capability_manager($forum);
|
||||
|
||||
$user = $this->user;
|
||||
$otheruser = $this->getDataGenerator()->create_user();
|
||||
|
||||
$discussion = $this->discussion;
|
||||
$post = $this->post;
|
||||
|
||||
$privatepost = $this->entityfactory->get_post_from_stdclass(
|
||||
(object) array_merge((array) $this->postrecord, ['parent' => $post->get_id(), 'privatereplyto' => $otheruser->id])
|
||||
);
|
||||
|
||||
$this->prevent_capability('mod/forum:readprivatereplies');
|
||||
$this->assertFalse($capabilitymanager->can_view_post($user, $discussion, $privatepost));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that can_view_post_shell considers private replies correctly.
|
||||
*
|
||||
* @covers ::can_view_post_shell
|
||||
*/
|
||||
public function test_can_view_post_shell() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$forum = $this->create_forum();
|
||||
$capabilitymanager = $this->managerfactory->get_capability_manager($forum);
|
||||
|
||||
$user = $this->user;
|
||||
$otheruser = $this->getDataGenerator()->create_user();
|
||||
|
||||
$discussion = $this->discussion;
|
||||
$post = $this->post;
|
||||
$privatepost = $this->entityfactory->get_post_from_stdclass(
|
||||
(object) array_merge((array) $this->postrecord, ['parent' => $post->get_id(), 'privatereplyto' => $otheruser->id])
|
||||
);
|
||||
$privateposttome = $this->entityfactory->get_post_from_stdclass(
|
||||
(object) array_merge((array) $this->postrecord, ['parent' => $post->get_id(), 'privatereplyto' => $user->id])
|
||||
);
|
||||
|
||||
// Can always view public replies, and those to me.
|
||||
$this->prevent_capability('mod/forum:readprivatereplies');
|
||||
$this->assertTrue($capabilitymanager->can_view_post_shell($this->user, $post));
|
||||
$this->assertTrue($capabilitymanager->can_view_post_shell($this->user, $privateposttome));
|
||||
$this->assertFalse($capabilitymanager->can_view_post_shell($this->user, $privatepost));
|
||||
|
||||
$this->give_capability('mod/forum:readprivatereplies');
|
||||
$this->assertTrue($capabilitymanager->can_view_post_shell($this->user, $post));
|
||||
$this->assertTrue($capabilitymanager->can_view_post_shell($this->user, $privateposttome));
|
||||
$this->assertTrue($capabilitymanager->can_view_post_shell($this->user, $privatepost));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test can_export_post.
|
||||
*
|
||||
* @covers ::can_export_post
|
||||
*/
|
||||
public function test_can_export_post() {
|
||||
global $CFG;
|
||||
@@ -922,6 +1071,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_view_participants.
|
||||
*
|
||||
* @covers ::can_view_participants
|
||||
*/
|
||||
public function test_can_view_participants() {
|
||||
$this->resetAfterTest();
|
||||
@@ -961,6 +1112,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_view_hidden_posts.
|
||||
*
|
||||
* @covers ::can_view_hidden_posts
|
||||
*/
|
||||
public function test_can_view_hidden_posts() {
|
||||
$this->resetAfterTest();
|
||||
@@ -978,6 +1131,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_manage_forum.
|
||||
*
|
||||
* @covers ::can_manage_forum
|
||||
*/
|
||||
public function test_can_manage_forum() {
|
||||
$this->resetAfterTest();
|
||||
@@ -995,6 +1150,8 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test can_manage_tags.
|
||||
*
|
||||
* @covers ::can_manage_tags
|
||||
*/
|
||||
public function test_can_manage_tags() {
|
||||
global $DB;
|
||||
@@ -1012,4 +1169,21 @@ class mod_forum_managers_capability_testcase extends advanced_testcase {
|
||||
assign_capability('moodle/tag:manage', CAP_ALLOW, $roleid, $context->id, true);
|
||||
$this->assertTrue($capabilitymanager->can_manage_tags($user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the can_view_any_private_reply works as expected.
|
||||
*
|
||||
* @covers ::can_view_any_private_reply
|
||||
*/
|
||||
public function test_can_view_any_private_reply() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$forum = $this->create_forum();
|
||||
$capabilitymanager = $this->managerfactory->get_capability_manager($forum);
|
||||
|
||||
$this->give_capability('mod/forum:readprivatereplies');
|
||||
$this->assertTrue($capabilitymanager->can_view_any_private_reply($this->user));
|
||||
$this->prevent_capability('mod/forum:readprivatereplies');
|
||||
$this->assertFalse($capabilitymanager->can_view_any_private_reply($this->user));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
|
||||
require_once(__DIR__ . '/helper.php');
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
require_once($CFG->dirroot . '/rating/lib.php');
|
||||
|
||||
use \mod_forum\privacy\provider;
|
||||
@@ -45,7 +45,7 @@ class mod_forum_privacy_provider_testcase extends \core_privacy\tests\provider_t
|
||||
|
||||
// Include the mod_forum test helpers.
|
||||
// This includes functions to create forums, users, discussions, and posts.
|
||||
use helper;
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
// Include the privacy helper trait for the ratings API.
|
||||
use \core_rating\phpunit\privacy_helper;
|
||||
@@ -394,6 +394,93 @@ class mod_forum_privacy_provider_testcase extends \core_privacy\tests\provider_t
|
||||
$this->assertEquals(0, $post->deleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test private reply in a range of scenarios.
|
||||
*/
|
||||
public function test_user_private_reply() {
|
||||
global $DB;
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$context = \context_module::instance($cm->id);
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $student);
|
||||
$reply = $this->helper_reply_to_post($post, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
// Testing as user $student.
|
||||
$this->setUser($student);
|
||||
|
||||
// Retrieve all contexts - only this context should be returned.
|
||||
$contextlist = $this->get_contexts_for_userid($student->id, 'mod_forum');
|
||||
$this->assertCount(1, $contextlist);
|
||||
$this->assertEquals($context, $contextlist->current());
|
||||
|
||||
// Export all of the data for the context.
|
||||
$this->export_context_data_for_user($student->id, $context, 'mod_forum');
|
||||
$writer = \core_privacy\local\request\writer::with_context($context);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
|
||||
// The initial post and reply will be included.
|
||||
$this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
|
||||
$this->assert_post_data($reply, $writer->get_data($this->get_subcontext($forum, $discussion, $reply)), $writer);
|
||||
|
||||
// Testing as user $teacher.
|
||||
\core_privacy\local\request\writer::reset();
|
||||
$this->setUser($teacher);
|
||||
|
||||
// Retrieve all contexts - only this context should be returned.
|
||||
$contextlist = $this->get_contexts_for_userid($teacher->id, 'mod_forum');
|
||||
$this->assertCount(1, $contextlist);
|
||||
$this->assertEquals($context, $contextlist->current());
|
||||
|
||||
// Export all of the data for the context.
|
||||
$this->export_context_data_for_user($teacher->id, $context, 'mod_forum');
|
||||
$writer = \core_privacy\local\request\writer::with_context($context);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
|
||||
// The reply will be included.
|
||||
$this->assert_post_data($post, $writer->get_data($this->get_subcontext($forum, $discussion, $post)), $writer);
|
||||
$this->assert_post_data($reply, $writer->get_data($this->get_subcontext($forum, $discussion, $reply)), $writer);
|
||||
|
||||
// Testing as user $otherteacher.
|
||||
// The user was not involved in any of the conversation.
|
||||
\core_privacy\local\request\writer::reset();
|
||||
$this->setUser($otherteacher);
|
||||
|
||||
// Retrieve all contexts - only this context should be returned.
|
||||
$contextlist = $this->get_contexts_for_userid($otherteacher->id, 'mod_forum');
|
||||
$this->assertCount(0, $contextlist);
|
||||
|
||||
// Export all of the data for the context.
|
||||
$this->export_context_data_for_user($otherteacher->id, $context, 'mod_forum');
|
||||
$writer = \core_privacy\local\request\writer::with_context($context);
|
||||
|
||||
// The user has none of the discussion.
|
||||
$this->assertEmpty($writer->get_data($this->get_subcontext($forum, $discussion)));
|
||||
|
||||
// Testing as user $otherstudent.
|
||||
// The user was not involved in any of the conversation.
|
||||
\core_privacy\local\request\writer::reset();
|
||||
$this->setUser($otherstudent);
|
||||
|
||||
// Retrieve all contexts - only this context should be returned.
|
||||
$contextlist = $this->get_contexts_for_userid($otherstudent->id, 'mod_forum');
|
||||
$this->assertCount(0, $contextlist);
|
||||
|
||||
// Export all of the data for the context.
|
||||
$this->export_context_data_for_user($otherstudent->id, $context, 'mod_forum');
|
||||
$writer = \core_privacy\local\request\writer::with_context($context);
|
||||
|
||||
// The user has none of the discussion.
|
||||
$this->assertEmpty($writer->get_data($this->get_subcontext($forum, $discussion)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the rating of another users content will have only the
|
||||
* rater's information returned.
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Tests for private reply functionality.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
require_once($CFG->dirroot . '/mod/forum/locallib.php');
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
|
||||
/**
|
||||
* Tests for private reply functionality.
|
||||
*
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class private_replies extends advanced_testcase {
|
||||
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
/**
|
||||
* Setup before tests.
|
||||
*/
|
||||
public function setUp() {
|
||||
// We must clear the subscription caches. This has to be done both before each test, and after in case of other
|
||||
// tests using these functions.
|
||||
\mod_forum\subscriptions::reset_forum_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down after tests.
|
||||
*/
|
||||
public function tearDown() {
|
||||
// We must clear the subscription caches. This has to be done both before each test, and after in case of other
|
||||
// tests using these functions.
|
||||
\mod_forum\subscriptions::reset_forum_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_post_is_visible_privately function reports that a post is visible to a user when another
|
||||
* user wrote the post, and it is not private.
|
||||
*/
|
||||
public function test_forum_post_is_visible_privately_not_private() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student] = $this->helper_create_users($course, 1, 'student');
|
||||
[$teacher] = $this->helper_create_users($course, 1, 'teacher');
|
||||
[$discussion] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$post = $this->helper_post_to_discussion($forum, $discussion, $teacher);
|
||||
|
||||
$this->setUser($student);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$this->assertTrue(forum_post_is_visible_privately($post, $cm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_post_is_visible_privately function reports that a post is visible to a user when another
|
||||
* user wrote the post, and the user under test is the intended recipient.
|
||||
*/
|
||||
public function test_forum_post_is_visible_privately_private_to_user() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student] = $this->helper_create_users($course, 1, 'student');
|
||||
[$teacher] = $this->helper_create_users($course, 1, 'teacher');
|
||||
[$discussion] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$post = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
$this->setUser($student);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$this->assertTrue(forum_post_is_visible_privately($post, $cm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_post_is_visible_privately function reports that a post is visible to a user when another
|
||||
* user wrote the post, and the user under test is a role with the view capability.
|
||||
*/
|
||||
public function test_forum_post_is_visible_privately_private_to_user_view_as_teacher() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student] = $this->helper_create_users($course, 1, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
[$discussion] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$post = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
$this->setUser($otherteacher);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$this->assertTrue(forum_post_is_visible_privately($post, $cm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_post_is_visible_privately function reports that a post is not visible to a user when
|
||||
* another user wrote the post, and the user under test is a role without the view capability.
|
||||
*/
|
||||
public function test_forum_post_is_visible_privately_private_to_user_view_as_other_student() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher] = $this->helper_create_users($course, 1, 'teacher');
|
||||
[$discussion] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$post = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
$this->setUser($otherstudent);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$this->assertFalse(forum_post_is_visible_privately($post, $cm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_post_is_visible_privately function reports that a post is visible to a user who wrote a
|
||||
* private reply, but not longer holds the view capability.
|
||||
*/
|
||||
public function test_forum_post_is_visible_privately_private_to_user_view_as_author() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student] = $this->helper_create_users($course, 1, 'student');
|
||||
[$teacher] = $this->helper_create_users($course, 1, 'teacher');
|
||||
[$discussion] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$post = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
unassign_capability('mod/forum:readprivatereplies', $this->get_role_id('teacher'));
|
||||
|
||||
$this->setUser($teacher);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$this->assertTrue(forum_post_is_visible_privately($post, $cm));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_user_can_reply_privately returns true for a teacher replying to a forum post.
|
||||
*/
|
||||
public function test_forum_user_can_reply_privately_as_teacher() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student] = $this->helper_create_users($course, 1, 'student');
|
||||
[$teacher] = $this->helper_create_users($course, 1, 'teacher');
|
||||
[, $post] = $this->helper_post_to_forum($forum, $student);
|
||||
|
||||
$this->setUser($teacher);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$context = \context_module::instance($cm->id);
|
||||
$this->assertTrue(forum_user_can_reply_privately($context, $post));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_user_can_reply_privately returns true for a teacher replying to a forum post.
|
||||
*/
|
||||
public function test_forum_user_can_reply_privately_as_student() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[, $post] = $this->helper_post_to_forum($forum, $student);
|
||||
|
||||
$this->setUser($otherstudent);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$context = \context_module::instance($cm->id);
|
||||
$this->assertFalse(forum_user_can_reply_privately($context, $post));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the forum_user_can_reply_privately returns false where the parent post is already a private reply.
|
||||
*/
|
||||
public function test_forum_user_can_reply_privately_parent_is_already_private() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student] = $this->helper_create_users($course, 1, 'student');
|
||||
[$teacher] = $this->helper_create_users($course, 1, 'teacher');
|
||||
[$discussion] = $this->helper_post_to_forum($forum, $student);
|
||||
$post = $this->helper_post_to_discussion($forum, $discussion, $teacher, ['privatereplyto' => $student->id]);
|
||||
|
||||
$this->setUser($teacher);
|
||||
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
||||
$context = \context_module::instance($cm->id);
|
||||
$this->assertFalse(forum_user_can_reply_privately($context, $post));
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once(__DIR__ . '/helper.php');
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
require_once("{$CFG->dirroot}/mod/forum/rsslib.php");
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ require_once("{$CFG->dirroot}/mod/forum/rsslib.php");
|
||||
class mod_forum_rsslib_testcase extends advanced_testcase {
|
||||
// Include the mod_forum test helpers.
|
||||
// This includes functions to create forums, users, discussions, and posts.
|
||||
use helper;
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
/**
|
||||
* Ensure that deleted posts are not included.
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/mod/forum/lib.php');
|
||||
require_once(__DIR__ . '/helper.php');
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
require_once("{$CFG->dirroot}/mod/forum/lib.php");
|
||||
|
||||
class mod_forum_subscriptions_testcase extends advanced_testcase {
|
||||
// Include the mod_forum test helpers.
|
||||
// This includes functions to create forums, users, discussions, and posts.
|
||||
use helper;
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
/**
|
||||
* Test setUp.
|
||||
|
||||
@@ -24,32 +24,31 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/generator_trait.php');
|
||||
|
||||
/**
|
||||
* The forum vault tests.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Ryan Wyllie <ryan@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \mod_forum\local\vaults\forum
|
||||
*/
|
||||
class mod_forum_vaults_forum_testcase extends advanced_testcase {
|
||||
/** @var \mod_forum\local\vaults\discussion */
|
||||
private $vault;
|
||||
|
||||
/**
|
||||
* Set up function for tests.
|
||||
*/
|
||||
public function setUp() {
|
||||
$vaultfactory = \mod_forum\local\container::get_vault_factory();
|
||||
$this->vault = $vaultfactory->get_forum_vault();
|
||||
}
|
||||
// Make use of the test generator trait.
|
||||
use mod_forum_tests_generator_trait;
|
||||
|
||||
/**
|
||||
* Test get_from_id.
|
||||
*
|
||||
* @covers ::get_from_id
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_id() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$vault = $this->vault;
|
||||
$vaultfactory = \mod_forum\local\container::get_vault_factory();
|
||||
$vault = $vaultfactory->get_forum_vault();
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$user = $datagenerator->create_user();
|
||||
$course = $datagenerator->create_course();
|
||||
@@ -62,11 +61,15 @@ class mod_forum_vaults_forum_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_from_course_module_id.
|
||||
*
|
||||
* @covers ::get_from_course_module_id
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_course_module_id() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$vault = $this->vault;
|
||||
$vaultfactory = \mod_forum\local\container::get_vault_factory();
|
||||
$vault = $vaultfactory->get_forum_vault();
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$user = $datagenerator->create_user();
|
||||
$course = $datagenerator->create_course();
|
||||
@@ -85,11 +88,15 @@ class mod_forum_vaults_forum_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_from_course_module_ids.
|
||||
*
|
||||
* @covers ::get_from_course_module_ids
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_course_module_ids() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$vault = $this->vault;
|
||||
$vaultfactory = \mod_forum\local\container::get_vault_factory();
|
||||
$vault = $vaultfactory->get_forum_vault();
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$user = $datagenerator->create_user();
|
||||
$course = $datagenerator->create_course();
|
||||
@@ -117,4 +124,36 @@ class mod_forum_vaults_forum_testcase extends advanced_testcase {
|
||||
$this->assertCount(1, $entities);
|
||||
$this->assertEquals($forum1->id, $entities[0]->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_from_post_id.
|
||||
*
|
||||
* @covers ::get_from_post_id
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_post_id() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$vaultfactory = \mod_forum\local\container::get_vault_factory();
|
||||
$vault = $vaultfactory->get_forum_vault();
|
||||
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$user = $datagenerator->create_user();
|
||||
$course = $datagenerator->create_course();
|
||||
$forum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $user);
|
||||
$reply = $this->helper_reply_to_post($post, $user);
|
||||
|
||||
$otherforum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
[$otherdiscussion, $otherpost] = $this->helper_post_to_forum($otherforum, $user);
|
||||
$otherreply = $this->helper_reply_to_post($otherpost, $user);
|
||||
|
||||
$entity = $vault->get_from_post_id($post->id);
|
||||
$this->assertEquals($forum->id, $entity->get_id());
|
||||
|
||||
$entity = $vault->get_from_post_id($reply->id);
|
||||
$this->assertEquals($forum->id, $entity->get_id());
|
||||
|
||||
$this->assertEmpty($vault->get_from_post_id(-1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,17 +91,63 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$post3 = $this->helper_reply_to_post($post1, $user);
|
||||
[$discussion2, $post4] = $this->helper_post_to_forum($forum, $user);
|
||||
|
||||
$entities = array_values($this->vault->get_from_discussion_id($discussion1->id));
|
||||
$entities = array_values($this->vault->get_from_discussion_id($user, $discussion1->id, false));
|
||||
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($post1->id, $entities[0]->get_id());
|
||||
$this->assertEquals($post2->id, $entities[1]->get_id());
|
||||
$this->assertEquals($post3->id, $entities[2]->get_id());
|
||||
|
||||
$entities = array_values($this->vault->get_from_discussion_id($discussion1->id + 1000));
|
||||
$entities = array_values($this->vault->get_from_discussion_id($user, $discussion1->id + 1000, false));
|
||||
$this->assertCount(0, $entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that selecting posts in a discussion only returns posts that the user can see, when considering private
|
||||
* replies.
|
||||
*
|
||||
* @covers ::get_from_discussion_id
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_discussion_id_private_replies() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$reply = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
// The user is the author.
|
||||
$entities = array_values($this->vault->get_from_discussion_id($teacher, $discussion->id, true));
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
$this->assertEquals($reply->id, $entities[1]->get_id());
|
||||
|
||||
// The user is the intended recipient.
|
||||
$entities = array_values($this->vault->get_from_discussion_id($student, $discussion->id, false));
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
$this->assertEquals($reply->id, $entities[1]->get_id());
|
||||
|
||||
// The user is another teacher..
|
||||
$entities = array_values($this->vault->get_from_discussion_id($otherteacher, $discussion->id, true));
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
$this->assertEquals($reply->id, $entities[1]->get_id());
|
||||
|
||||
// The user is a different student.
|
||||
$entities = array_values($this->vault->get_from_discussion_id($otherstudent, $discussion->id, false));
|
||||
$this->assertCount(1, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_from_discussion_ids when no discussion ids were provided.
|
||||
*
|
||||
@@ -115,7 +161,7 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$course = $datagenerator->create_course();
|
||||
$forum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
$this->assertEquals([], $this->vault->get_from_discussion_ids([]));
|
||||
$this->assertEquals([], $this->vault->get_from_discussion_ids($user, [], false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,7 +182,7 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$post3 = $this->helper_reply_to_post($post1, $user);
|
||||
[$discussion2, $post4] = $this->helper_post_to_forum($forum, $user);
|
||||
|
||||
$entities = array_values($this->vault->get_from_discussion_ids([$discussion1->id]));
|
||||
$entities = array_values($this->vault->get_from_discussion_ids($user, [$discussion1->id], false));
|
||||
usort($entities, function($a, $b) {
|
||||
return $a <=> $b;
|
||||
});
|
||||
@@ -145,7 +191,7 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$this->assertEquals($post2->id, $entities[1]->get_id());
|
||||
$this->assertEquals($post3->id, $entities[2]->get_id());
|
||||
|
||||
$entities = array_values($this->vault->get_from_discussion_ids([$discussion1->id, $discussion2->id]));
|
||||
$entities = array_values($this->vault->get_from_discussion_ids($user, [$discussion1->id, $discussion2->id], false));
|
||||
usort($entities, function($a, $b) {
|
||||
return $a <=> $b;
|
||||
});
|
||||
@@ -156,6 +202,59 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$this->assertEquals($post4->id, $entities[3]->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that selecting posts in a discussion only returns posts that the user can see, when considering private
|
||||
* replies.
|
||||
*
|
||||
* @covers ::get_from_discussion_ids
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_discussion_ids_private_replies() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$reply = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
[$otherdiscussion, $otherpost] = $this->helper_post_to_forum($forum, $teacher);
|
||||
|
||||
// The user is the author.
|
||||
$entities = array_values($this->vault->get_from_discussion_ids($teacher, [$discussion->id, $otherdiscussion->id], true));
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
$this->assertEquals($reply->id, $entities[1]->get_id());
|
||||
$this->assertEquals($otherpost->id, $entities[2]->get_id());
|
||||
|
||||
// The user is the intended recipient.
|
||||
$entities = array_values($this->vault->get_from_discussion_ids($student, [$discussion->id, $otherdiscussion->id], false));
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
$this->assertEquals($reply->id, $entities[1]->get_id());
|
||||
$this->assertEquals($otherpost->id, $entities[2]->get_id());
|
||||
|
||||
// The user is another teacher..
|
||||
$entities = array_values(
|
||||
$this->vault->get_from_discussion_ids($otherteacher, [$discussion->id, $otherdiscussion->id], true));
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
$this->assertEquals($reply->id, $entities[1]->get_id());
|
||||
$this->assertEquals($otherpost->id, $entities[2]->get_id());
|
||||
|
||||
// The user is a different student.
|
||||
$entities = array_values(
|
||||
$this->vault->get_from_discussion_ids($otherstudent, [$discussion->id, $otherdiscussion->id], false));
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($post->id, $entities[0]->get_id());
|
||||
$this->assertEquals($otherpost->id, $entities[1]->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_replies_to_post.
|
||||
*
|
||||
@@ -191,20 +290,181 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$post3 = $entityfactory->get_post_from_stdclass($post3);
|
||||
$post4 = $entityfactory->get_post_from_stdclass($post4);
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($post1);
|
||||
$entities = $this->vault->get_replies_to_post($user, $post1, false);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($post2->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($post3->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($post4->get_id(), $entities[2]->get_id());
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($post2);
|
||||
$entities = $this->vault->get_replies_to_post($user, $post2, false);
|
||||
$this->assertCount(1, $entities);
|
||||
$this->assertEquals($post4->get_id(), $entities[0]->get_id());
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($post3);
|
||||
$entities = $this->vault->get_replies_to_post($user, $post3, false);
|
||||
$this->assertCount(0, $entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_replies_to_post with private replies.
|
||||
*
|
||||
* @covers ::get_replies_to_post
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_replies_to_post_private_replies() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
// Generate a structure:
|
||||
// Initial post p [student]
|
||||
// -> Reply pa [otherstudent]
|
||||
// ---> Reply paa [student]
|
||||
// ---> Private Reply pab [teacher]
|
||||
// -> Private Reply pb [teacher]
|
||||
// -> Reply pc [otherstudent]
|
||||
// ---> Reply pca [student]
|
||||
// -----> Reply pcaa [otherstudent]
|
||||
// -------> Private Reply pcaaa [teacher].
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
|
||||
[$discussion, $p] = $this->helper_post_to_forum($forum, $student);
|
||||
|
||||
$pa = $this->helper_reply_to_post($p, $otherstudent);
|
||||
$paa = $this->helper_reply_to_post($pa, $student);
|
||||
$pab = $this->helper_reply_to_post($pa, $teacher, ['privatereplyto' => $otherstudent->id]);
|
||||
|
||||
$pb = $this->helper_reply_to_post($p, $teacher, ['privatereplyto' => $student->id]);
|
||||
|
||||
$pc = $this->helper_reply_to_post($p, $otherteacher);
|
||||
$pca = $this->helper_reply_to_post($pc, $student);
|
||||
$pcaa = $this->helper_reply_to_post($pca, $otherstudent);
|
||||
$pcaaa = $this->helper_reply_to_post($pcaa, $teacher, ['privatereplyto' => $otherstudent->id]);
|
||||
|
||||
$entityfactory = \mod_forum\local\container::get_entity_factory();
|
||||
$ep = $entityfactory->get_post_from_stdclass($p);
|
||||
$epa = $entityfactory->get_post_from_stdclass($pa);
|
||||
$epaa = $entityfactory->get_post_from_stdclass($paa);
|
||||
$epab = $entityfactory->get_post_from_stdclass($pab);
|
||||
$epb = $entityfactory->get_post_from_stdclass($pb);
|
||||
$epc = $entityfactory->get_post_from_stdclass($pc);
|
||||
$epca = $entityfactory->get_post_from_stdclass($pca);
|
||||
$epcaa = $entityfactory->get_post_from_stdclass($pcaa);
|
||||
$epcaaa = $entityfactory->get_post_from_stdclass($pcaaa);
|
||||
|
||||
// As `student`, you should see all public posts, plus all private replies intended for you.
|
||||
$entities = $this->vault->get_replies_to_post($student, $ep, false);
|
||||
$this->assertCount(6, $entities);
|
||||
$this->assertEquals($epa->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epaa->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($epb->get_id(), $entities[2]->get_id());
|
||||
$this->assertEquals($epc->get_id(), $entities[3]->get_id());
|
||||
$this->assertEquals($epca->get_id(), $entities[4]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[5]->get_id());
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($student, $epa, false);
|
||||
$this->assertCount(1, $entities);
|
||||
$this->assertEquals($epaa->get_id(), $entities[0]->get_id());
|
||||
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($student, $epaa, false));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($student, $epab, false));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($student, $epb, false));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($student, $epcaa, false));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($student, $epcaaa, false));
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($student, $epc, false);
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($epca->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[1]->get_id());
|
||||
|
||||
// As `otherstudent`, you should see all public posts, plus all private replies intended for you.
|
||||
$entities = $this->vault->get_replies_to_post($otherstudent, $ep, false);
|
||||
$this->assertCount(7, $entities);
|
||||
$this->assertEquals($epa->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epaa->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($epab->get_id(), $entities[2]->get_id());
|
||||
$this->assertEquals($epc->get_id(), $entities[3]->get_id());
|
||||
$this->assertEquals($epca->get_id(), $entities[4]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[5]->get_id());
|
||||
$this->assertEquals($epcaaa->get_id(), $entities[6]->get_id());
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($otherstudent, $epa, false);
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($epaa->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epab->get_id(), $entities[1]->get_id());
|
||||
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherstudent, $epaa, false));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherstudent, $epab, false));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherstudent, $epb, false));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherstudent, $epcaaa, false));
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($otherstudent, $epc, false);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($epca->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($epcaaa->get_id(), $entities[2]->get_id());
|
||||
|
||||
// The teacher who authored the private replies can see all.
|
||||
$entities = $this->vault->get_replies_to_post($teacher, $ep, true);
|
||||
$this->assertCount(8, $entities);
|
||||
$this->assertEquals($epa->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epaa->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($epab->get_id(), $entities[2]->get_id());
|
||||
$this->assertEquals($epb->get_id(), $entities[3]->get_id());
|
||||
$this->assertEquals($epc->get_id(), $entities[4]->get_id());
|
||||
$this->assertEquals($epca->get_id(), $entities[5]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[6]->get_id());
|
||||
$this->assertEquals($epcaaa->get_id(), $entities[7]->get_id());
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($teacher, $epa, true);
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($epaa->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epab->get_id(), $entities[1]->get_id());
|
||||
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($teacher, $epaa, true));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($teacher, $epab, true));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($teacher, $epb, true));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($teacher, $epcaaa, true));
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($teacher, $epc, true);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($epca->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($epcaaa->get_id(), $entities[2]->get_id());
|
||||
|
||||
// Any other teacher can also see all.
|
||||
$entities = $this->vault->get_replies_to_post($otherteacher, $ep, true);
|
||||
$this->assertCount(8, $entities);
|
||||
$this->assertEquals($epa->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epaa->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($epab->get_id(), $entities[2]->get_id());
|
||||
$this->assertEquals($epb->get_id(), $entities[3]->get_id());
|
||||
$this->assertEquals($epc->get_id(), $entities[4]->get_id());
|
||||
$this->assertEquals($epca->get_id(), $entities[5]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[6]->get_id());
|
||||
$this->assertEquals($epcaaa->get_id(), $entities[7]->get_id());
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($otherteacher, $epa, true);
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertEquals($epaa->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epab->get_id(), $entities[1]->get_id());
|
||||
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherteacher, $epaa, true));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherteacher, $epab, true));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherteacher, $epb, true));
|
||||
$this->assertEmpty($this->vault->get_replies_to_post($otherteacher, $epcaaa, true));
|
||||
|
||||
$entities = $this->vault->get_replies_to_post($otherteacher, $epc, true);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertEquals($epca->get_id(), $entities[0]->get_id());
|
||||
$this->assertEquals($epcaa->get_id(), $entities[1]->get_id());
|
||||
$this->assertEquals($epcaaa->get_id(), $entities[2]->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_reply_count_for_discussion_ids when no discussion ids were provided.
|
||||
*
|
||||
@@ -218,8 +478,7 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$course = $datagenerator->create_course();
|
||||
$forum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids([]);
|
||||
$this->assertCount(0, $counts);
|
||||
$this->assertCount(0, $this->vault->get_reply_count_for_discussion_ids($user, [], false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,31 +502,168 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$post6 = $this->helper_reply_to_post($post5, $user);
|
||||
[$discussion3, $post7] = $this->helper_post_to_forum($forum, $user);
|
||||
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids([$discussion1->id]);
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids($user, [$discussion1->id], false);
|
||||
$this->assertCount(1, $counts);
|
||||
$this->assertEquals(3, $counts[$discussion1->id]);
|
||||
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids([$discussion1->id, $discussion2->id]);
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids($user, [$discussion1->id, $discussion2->id], false);
|
||||
$this->assertCount(2, $counts);
|
||||
$this->assertEquals(3, $counts[$discussion1->id]);
|
||||
$this->assertEquals(1, $counts[$discussion2->id]);
|
||||
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids([$discussion1->id, $discussion2->id, $discussion3->id]);
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids($user, [
|
||||
$discussion1->id,
|
||||
$discussion2->id,
|
||||
$discussion3->id
|
||||
], false);
|
||||
$this->assertCount(2, $counts);
|
||||
$this->assertEquals(3, $counts[$discussion1->id]);
|
||||
$this->assertEquals(1, $counts[$discussion2->id]);
|
||||
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids([
|
||||
$counts = $this->vault->get_reply_count_for_discussion_ids($user, [
|
||||
$discussion1->id,
|
||||
$discussion2->id,
|
||||
$discussion3->id,
|
||||
$discussion3->id + 1000
|
||||
]);
|
||||
], false);
|
||||
$this->assertCount(2, $counts);
|
||||
$this->assertEquals(3, $counts[$discussion1->id]);
|
||||
$this->assertEquals(1, $counts[$discussion2->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_reply_count_for_discussion_ids.
|
||||
*
|
||||
* @covers ::get_reply_count_for_discussion_ids
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_reply_count_for_discussion_ids_private_replies() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
// Generate a structure:
|
||||
// Initial post p [student]
|
||||
// -> Reply pa [otherstudent]
|
||||
// ---> Reply paa [student]
|
||||
// ---> Private Reply pab [teacher]
|
||||
// -> Private Reply pb [teacher]
|
||||
// -> Reply pc [otherstudent]
|
||||
// ---> Reply pca [student]
|
||||
// -----> Reply pcaa [otherstudent]
|
||||
// -------> Private Reply pcaaa [teacher].
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
|
||||
[$discussion, $p] = $this->helper_post_to_forum($forum, $student);
|
||||
|
||||
$pa = $this->helper_reply_to_post($p, $otherstudent);
|
||||
$paa = $this->helper_reply_to_post($pa, $student);
|
||||
$pab = $this->helper_reply_to_post($pa, $teacher, ['privatereplyto' => $otherstudent->id]);
|
||||
|
||||
$pb = $this->helper_reply_to_post($p, $teacher, ['privatereplyto' => $student->id]);
|
||||
|
||||
$pc = $this->helper_reply_to_post($p, $otherteacher);
|
||||
$pca = $this->helper_reply_to_post($pc, $student);
|
||||
$pcaa = $this->helper_reply_to_post($pca, $otherstudent);
|
||||
$pcaaa = $this->helper_reply_to_post($pcaa, $teacher, ['privatereplyto' => $otherstudent->id]);
|
||||
|
||||
$this->assertEquals([$discussion->id => 6],
|
||||
$this->vault->get_reply_count_for_discussion_ids($student, [$discussion->id], false));
|
||||
$this->assertEquals([$discussion->id => 7],
|
||||
$this->vault->get_reply_count_for_discussion_ids($otherstudent, [$discussion->id], false));
|
||||
$this->assertEquals([$discussion->id => 8],
|
||||
$this->vault->get_reply_count_for_discussion_ids($teacher, [$discussion->id], true));
|
||||
$this->assertEquals([$discussion->id => 8],
|
||||
$this->vault->get_reply_count_for_discussion_ids($otherteacher, [$discussion->id], true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_reply_count_for_discussion_id.
|
||||
*
|
||||
* @covers ::get_reply_count_for_post_id_in_discussion_id
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_reply_count_for_post_id_in_discussion_id() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$user = $datagenerator->create_user();
|
||||
$course = $datagenerator->create_course();
|
||||
$forum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
[$discussion1, $post1] = $this->helper_post_to_forum($forum, $user);
|
||||
$post2 = $this->helper_reply_to_post($post1, $user);
|
||||
$post3 = $this->helper_reply_to_post($post1, $user);
|
||||
$post4 = $this->helper_reply_to_post($post2, $user);
|
||||
[$discussion2, $post5] = $this->helper_post_to_forum($forum, $user);
|
||||
$post6 = $this->helper_reply_to_post($post5, $user);
|
||||
[$discussion3, $post7] = $this->helper_post_to_forum($forum, $user);
|
||||
|
||||
$this->assertEquals(3,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($user, $post1->id, $discussion1->id, false));
|
||||
$this->assertEquals(1,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($user, $post5->id, $discussion2->id, false));
|
||||
$this->assertEquals(0,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($user, $post7->id, $discussion3->id, false));
|
||||
$this->assertEquals(0,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($user, $post7->id + 1000, $discussion3->id, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_reply_count_for_post_id_in_discussion_id.
|
||||
*
|
||||
* @covers ::get_reply_count_for_post_id_in_discussion_id
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_reply_count_for_post_id_in_discussion_id_private_replies() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
// Generate a structure:
|
||||
// Initial post p [student]
|
||||
// -> Reply pa [otherstudent]
|
||||
// ---> Reply paa [student]
|
||||
// ---> Private Reply pab [teacher]
|
||||
// -> Private Reply pb [teacher]
|
||||
// -> Reply pc [otherstudent]
|
||||
// ---> Reply pca [student]
|
||||
// -----> Reply pcaa [otherstudent]
|
||||
// -------> Private Reply pcaaa [teacher].
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
|
||||
[$discussion, $p] = $this->helper_post_to_forum($forum, $student);
|
||||
|
||||
$pa = $this->helper_reply_to_post($p, $otherstudent);
|
||||
$paa = $this->helper_reply_to_post($pa, $student);
|
||||
$pab = $this->helper_reply_to_post($pa, $teacher, ['privatereplyto' => $otherstudent->id]);
|
||||
|
||||
$pb = $this->helper_reply_to_post($p, $teacher, ['privatereplyto' => $student->id]);
|
||||
|
||||
$pc = $this->helper_reply_to_post($p, $otherteacher);
|
||||
$pca = $this->helper_reply_to_post($pc, $student);
|
||||
$pcaa = $this->helper_reply_to_post($pca, $otherstudent);
|
||||
$pcaaa = $this->helper_reply_to_post($pcaa, $teacher, ['privatereplyto' => $otherstudent->id]);
|
||||
|
||||
$this->assertEquals(6,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($student, $p->id, $discussion->id, false));
|
||||
$this->assertEquals(7,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($otherstudent, $p->id, $discussion->id, false));
|
||||
$this->assertEquals(8,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($teacher, $p->id, $discussion->id, true));
|
||||
$this->assertEquals(8,
|
||||
$this->vault->get_reply_count_for_post_id_in_discussion_id($otherteacher, $p->id, $discussion->id, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_unread_count_for_discussion_ids.
|
||||
*
|
||||
@@ -305,11 +701,11 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
forum_tp_add_read_record($user->id, $post4->id);
|
||||
$CFG->forum_oldpostdays = 1;
|
||||
|
||||
$counts = $this->vault->get_unread_count_for_discussion_ids($user, [$discussion1->id]);
|
||||
$counts = $this->vault->get_unread_count_for_discussion_ids($user, [$discussion1->id], false);
|
||||
$this->assertCount(1, $counts);
|
||||
$this->assertEquals(2, $counts[$discussion1->id]);
|
||||
|
||||
$counts = $this->vault->get_unread_count_for_discussion_ids($user, [$discussion1->id, $discussion2->id]);
|
||||
$counts = $this->vault->get_unread_count_for_discussion_ids($user, [$discussion1->id, $discussion2->id], false);
|
||||
$this->assertCount(2, $counts);
|
||||
$this->assertEquals(2, $counts[$discussion1->id]);
|
||||
$this->assertEquals(2, $counts[$discussion2->id]);
|
||||
@@ -318,12 +714,12 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$discussion1->id,
|
||||
$discussion2->id,
|
||||
$discussion2->id + 1000
|
||||
]);
|
||||
], false);
|
||||
$this->assertCount(2, $counts);
|
||||
$this->assertEquals(2, $counts[$discussion1->id]);
|
||||
$this->assertEquals(2, $counts[$discussion2->id]);
|
||||
|
||||
$counts = $this->vault->get_unread_count_for_discussion_ids($otheruser, [$discussion1->id, $discussion2->id]);
|
||||
$counts = $this->vault->get_unread_count_for_discussion_ids($otheruser, [$discussion1->id, $discussion2->id], false);
|
||||
$this->assertCount(2, $counts);
|
||||
$this->assertEquals(4, $counts[$discussion1->id]);
|
||||
$this->assertEquals(2, $counts[$discussion2->id]);
|
||||
@@ -366,27 +762,29 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$post6 = $this->helper_reply_to_post($post5, $user);
|
||||
[$discussion3, $post7] = $this->helper_post_to_forum($forum, $user);
|
||||
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids([$discussion1->id]);
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids($user, [$discussion1->id], false);
|
||||
$this->assertCount(1, $ids);
|
||||
$this->assertEquals($post4->id, $ids[$discussion1->id]);
|
||||
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids([$discussion1->id, $discussion2->id]);
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids($user,
|
||||
[$discussion1->id, $discussion2->id], false);
|
||||
$this->assertCount(2, $ids);
|
||||
$this->assertEquals($post4->id, $ids[$discussion1->id]);
|
||||
$this->assertEquals($post6->id, $ids[$discussion2->id]);
|
||||
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids([$discussion1->id, $discussion2->id, $discussion3->id]);
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids($user,
|
||||
[$discussion1->id, $discussion2->id, $discussion3->id], false);
|
||||
$this->assertCount(3, $ids);
|
||||
$this->assertEquals($post4->id, $ids[$discussion1->id]);
|
||||
$this->assertEquals($post6->id, $ids[$discussion2->id]);
|
||||
$this->assertEquals($post7->id, $ids[$discussion3->id]);
|
||||
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids([
|
||||
$ids = $this->vault->get_latest_post_id_for_discussion_ids($user, [
|
||||
$discussion1->id,
|
||||
$discussion2->id,
|
||||
$discussion3->id,
|
||||
$discussion3->id + 1000
|
||||
]);
|
||||
], false);
|
||||
$this->assertCount(3, $ids);
|
||||
$this->assertEquals($post4->id, $ids[$discussion1->id]);
|
||||
$this->assertEquals($post6->id, $ids[$discussion2->id]);
|
||||
@@ -407,6 +805,6 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$course = $datagenerator->create_course();
|
||||
$forum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
$this->assertEquals([], $this->vault->get_latest_post_id_for_discussion_ids([]));
|
||||
$this->assertEquals([], $this->vault->get_latest_post_id_for_discussion_ids($user, [], false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@ information provided here is intended especially for developers.
|
||||
* Added new forum entities, factories, exporters, renderers, and vaults in the local namespace to better encapsulate the forum data.
|
||||
* Deprecated all of the forum_print_* functions in lib.php.
|
||||
* The forum_print_latest_discussions function has been deprecated and will not be replaced.
|
||||
* The get_forum_discussion_posts web service has been deprecated in favour of get_discussion_posts.
|
||||
* The forum_count_replies function has been deprecated in favour of get_reply_count_for_post_id_in_discussion_id in
|
||||
the Post vault.
|
||||
|
||||
=== 3.6 ===
|
||||
|
||||
* forum_print_post should be surrounded with calls to forum_print_post_start and forum_print_post_end to create the proper HTML structure for the post.
|
||||
|
||||
=== 3.4 ===
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2018120302; // The current module version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2019031300; // The current module version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2018112800; // Requires this Moodle version
|
||||
$plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
+6
-1
@@ -146,7 +146,12 @@ switch ($forum->get_type()) {
|
||||
$hasmultiplediscussions, $displaymode);
|
||||
$post = $postvault->get_from_id($discussion->get_first_post_id());
|
||||
$orderpostsby = $displaymode == FORUM_MODE_FLATNEWEST ? 'created DESC' : 'created ASC';
|
||||
$replies = $postvault->get_replies_to_post($post, $orderpostsby);
|
||||
$replies = $postvault->get_replies_to_post(
|
||||
$USER,
|
||||
$post,
|
||||
$capabilitymanager->can_view_any_private_reply($USER),
|
||||
$orderpostsby
|
||||
);
|
||||
echo $discussionsrenderer->render($USER, $post, $replies);
|
||||
break;
|
||||
case 'blog':
|
||||
|
||||
Reference in New Issue
Block a user