1
0
mirror of https://github.com/moodle/moodle.git synced 2025-04-14 04:52:36 +02:00

Merge branch 'MDL-66301-master' of git://github.com/mickhawkins/moodle

This commit is contained in:
Andrew Nicols 2020-04-06 10:13:01 +08:00
commit b02d376c9b
16 changed files with 518 additions and 158 deletions

@ -58,13 +58,16 @@ class report_downloaded extends \core\event\base {
* @return string
*/
public function get_description() {
if ($this->other['hasviewall']) {
return "The user with id '{$this->userid}' downloaded the summary report for the forum with " .
"course module id '{$this->contextinstanceid}'.";
$whose = $this->other['hasviewall'] ? 'the' : 'their own';
$description = "The user with id '{$this->userid}' downloaded {$whose} summary report for ";
if ($this->other['forumid']) {
$description .= "the forum with course module id '{$this->contextinstanceid}'.";
} else {
return "The user with id '{$this->userid}' downloaded their own summary report for the forum with " .
"course module id '{$this->contextinstanceid}'.";
$description .= "the course with id '{$this->contextinstanceid}'.";
}
return $description;
}
/**
@ -72,8 +75,13 @@ class report_downloaded extends \core\event\base {
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/report/summary/index.php',
['courseid' => $this->courseid, 'forumid' => $this->other['forumid']]);
$params = ['courseid' => $this->courseid];
if (!empty($this->other['forumid'])) {
$params['forumid'] = $this->other['forumid'];
}
return new \moodle_url('/mod/forum/report/summary/index.php', $params);
}
/**

@ -58,14 +58,16 @@ class report_viewed extends \core\event\base {
* @return string
*/
public function get_description() {
if ($this->other['hasviewall']) {
return "The user with id '{$this->userid}' viewed the summary report for the forum with " .
"course module id '{$this->contextinstanceid}'.";
$whose = $this->other['hasviewall'] ? 'the' : 'their own';
$description = "The user with id '{$this->userid}' viewed {$whose} summary report for ";
if ($this->other['forumid']) {
$description .= "the forum with course module id '{$this->contextinstanceid}'.";
} else {
return "The user with id '{$this->userid}' viewed their own summary report for the forum with " .
"course module id '{$this->contextinstanceid}'.";
$description .= "the course with id '{$this->contextinstanceid}'.";
}
return $description;
}
/**
@ -74,8 +76,13 @@ class report_viewed extends \core\event\base {
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/forum/report/summary/index.php',
['courseid' => $this->courseid, 'forumid' => $this->other['forumid']]);
$params = ['courseid' => $this->courseid];
if (!empty($this->other['forumid'])) {
$params['forumid'] = $this->other['forumid'];
}
return new \moodle_url('/mod/forum/report/summary/index.php', $params);
}
/**

@ -42,11 +42,19 @@ defined('MOODLE_INTERNAL') || die();
class filters implements renderable, templatable {
/**
* Course module the report is being run within.
* Course modules the report relates to.
* Array of stdClass objects
*
* @var stdClass $cm
* @var array $cms
*/
protected $cm;
protected $cms;
/**
* Course ID where the report is being generated.
*
* @var int $courseid
*/
protected $courseid;
/**
* Moodle URL used as the form action on the generate button.
@ -98,13 +106,15 @@ class filters implements renderable, templatable {
/**
* Builds renderable filter data.
*
* @param stdClass $cm The course module object.
* @param stdClass $course The course object.
* @param array $cms Array of course module objects.
* @param moodle_url $actionurl The form action URL.
* @param array $filterdata (optional) Associative array of data that has been set on available filters, if any,
* in the format filtertype => [values]
* in the format filtertype => [values]
*/
public function __construct(stdClass $cm, moodle_url $actionurl, array $filterdata = []) {
$this->cm = $cm;
public function __construct(stdClass $course, array $cms, moodle_url $actionurl, array $filterdata = []) {
$this->cms = $cms;
$this->courseid = $course->id;
$this->actionurl = $actionurl;
// Prepare groups filter data.
@ -126,26 +136,54 @@ class filters implements renderable, templatable {
protected function prepare_groups_data(array $groupsdata): void {
global $DB, $USER;
$groupmode = groups_get_activity_groupmode($this->cm);
$context = \context_module::instance($this->cm->id);
$aag = has_capability('moodle/site:accessallgroups', $context);
$groupsavailable = [];
$allowedgroupsobj = [];
$usergroups = groups_get_all_groups($this->courseid, $USER->id);
$coursegroups = groups_get_all_groups($this->courseid);
$forumids = [];
$allgroups = false;
$hasgroups = false;
// Check if any forum gives the user access to all groups and no groups.
foreach ($this->cms as $cm) {
$forumids[] = $cm->instance;
// Only need to check for all groups access if not confirmed by a previous check.
if (!$allgroups) {
$groupmode = groups_get_activity_groupmode($cm);
// If no groups mode enabled on the forum, nothing to prepare.
if (!in_array($groupmode, [VISIBLEGROUPS, SEPARATEGROUPS])) {
continue;
}
$hasgroups = true;
// Fetch for the current cm's forum.
$context = \context_module::instance($cm->id);
$aag = has_capability('moodle/site:accessallgroups', $context);
if ($groupmode == VISIBLEGROUPS || $aag) {
$allgroups = true;
}
}
}
// If no groups mode enabled, nothing to prepare.
if (!in_array($groupmode, [VISIBLEGROUPS, SEPARATEGROUPS])) {
if (!$hasgroups) {
return;
}
if ($groupmode == VISIBLEGROUPS || $aag) {
// Any groups, and no groups.
$allowedgroupsobj = groups_get_all_groups($this->cm->course, 0, $this->cm->groupingid);
// Any groups, and no groups.
if ($allgroups) {
$nogroups = new stdClass();
$nogroups->id = -1;
$nogroups->name = get_string('groupsnone');
$allowedgroupsobj[] = $nogroups;
$allowedgroupsobj = $coursegroups + [$nogroups];
} else {
// Only assigned groups.
$allowedgroupsobj = groups_get_all_groups($this->cm->course, $USER->id, $this->cm->groupingid);
$allowedgroupsobj = $usergroups;
}
foreach ($allowedgroupsobj as $group) {
@ -159,17 +197,16 @@ class filters implements renderable, templatable {
$this->groupsavailable = $groupsavailable;
$this->groupsselected = $groupsselected;
// If export links will require discussion filtering, find and set the discussion IDs.
$groupsselectedcount = count($groupsselected);
if ($groupsselectedcount > 0 && $groupsselectedcount < count($groupsavailable)) {
list($forumidin, $forumidparams) = $DB->get_in_or_equal($forumids, SQL_PARAMS_NAMED);
list($groupidin, $groupidparams) = $DB->get_in_or_equal($groupsselected, SQL_PARAMS_NAMED);
$dwhere = "course = :courseid AND forum = :forumid AND groupid {$groupidin}";
$dparams = [
'courseid' => $this->cm->course,
'forumid' => $this->cm->instance,
];
$dparams += $groupidparams;
$discussionids = $DB->get_fieldset_select('forum_discussions', 'DISTINCT id', $dwhere, $dparams);
$discussionswhere = "course = :courseid AND forum {$forumidin} AND groupid {$groupidin}";
$discussionsparams = ['courseid' => $this->courseid];
$discussionsparams += $forumidparams + $groupidparams;
$discussionids = $DB->get_fieldset_select('forum_discussions', 'DISTINCT id', $discussionswhere, $discussionsparams);
foreach ($discussionids as $discussionid) {
$this->discussionids[] = ['discid' => $discussionid];

@ -62,8 +62,17 @@ class summary_table extends table_sql {
/** @var array The values available for pagination size per page. */
protected $perpageoptions = [50, 100, 200];
/** @var \stdClass The course module object of the forum being reported on. */
protected $cm;
/** @var int The course ID containing the forum(s) being reported on. */
protected $courseid;
/** @var bool True if reporting on all forums in course user has access to, false if reporting on a single forum */
protected $iscoursereport = false;
/** @var bool True if user has access to all forums in the course (and is running course report), otherwise false. */
protected $accessallforums = false;
/** @var \stdClass The course module object(s) of the forum(s) being reported on. */
protected $cms = [];
/**
* @var int The user ID if only one user's summary will be generated.
@ -77,9 +86,14 @@ class summary_table extends table_sql {
protected $logreader = null;
/**
* @var \context|null
* @var array of \context objects for the forums included in the report.
*/
protected $context = null;
protected $forumcontexts = [];
/**
* @var context_course|context_module The context where the report is being run (either a specific forum or the course).
*/
protected $userfieldscontext = null;
/** @var bool Whether the user has the capability/capabilities to perform bulk operations. */
protected $allowbulkoperations = false;
@ -108,25 +122,24 @@ class summary_table extends table_sql {
* @param bool $canseeprivatereplies Whether the user can see all private replies or not.
* @param int $perpage The number of rows to display per page.
* @param bool $canexport Is the user allowed to export records?
* @param bool $iscoursereport Whether the user is running a course level report
* @param bool $accessallforums If user is running a course level report, do they have access to all forums in the course?
*/
public function __construct(int $courseid, array $filters, bool $allowbulkoperations,
bool $canseeprivatereplies, int $perpage, bool $canexport) {
global $USER, $OUTPUT;
bool $canseeprivatereplies, int $perpage, bool $canexport, bool $iscoursereport, bool $accessallforums) {
global $OUTPUT;
$forumid = $filters['forums'][0];
$uniqueid = $courseid . ($iscoursereport ? '' : '_' . $filters['forums'][0]);
parent::__construct("summaryreport_{$uniqueid}");
parent::__construct("summaryreport_{$courseid}_{$forumid}");
$this->cm = get_coursemodule_from_instance('forum', $forumid, $courseid);
$this->context = \context_module::instance($this->cm->id);
$this->courseid = $courseid;
$this->iscoursereport = $iscoursereport;
$this->accessallforums = $accessallforums;
$this->allowbulkoperations = $allowbulkoperations;
$this->canseeprivatereplies = $canseeprivatereplies;
$this->perpage = $perpage;
// Only show their own summary unless they have permission to view all.
if (!has_capability('forumreport/summary:viewall', $this->context)) {
$this->userid = $USER->id;
}
$this->set_forum_properties($filters['forums']);
$columnheaders = [];
@ -180,6 +193,37 @@ class summary_table extends table_sql {
$this->define_base_sql();
}
/**
* Sets properties that are determined by forum filter values.
*
* @param array $forumids The forum IDs passed in by the filter.
* @return void
*/
protected function set_forum_properties(array $forumids): void {
global $USER;
// Course context if reporting on all forums in the course the user has access to.
if ($this->iscoursereport) {
$this->userfieldscontext = \context_course::instance($this->courseid);
}
foreach ($forumids as $forumid) {
$cm = get_coursemodule_from_instance('forum', $forumid, $this->courseid);
$this->cms[] = $cm;
$this->forumcontexts[$cm->id] = \context_module::instance($cm->id);
// Set forum context if not reporting on course.
if (!isset($this->userfieldscontext)) {
$this->userfieldscontext = $this->forumcontexts[$cm->id];
}
// Only show own summary unless they have permission to view all in every forum being reported.
if (empty($this->userid) && !has_capability('forumreport/summary:viewall', $this->forumcontexts[$cm->id])) {
$this->userid = $USER->id;
}
}
}
/**
* Provides the string name of each filter type, to be used by errors.
* Note: This does not use language strings as the value is injected into error strings.
@ -230,7 +274,7 @@ class summary_table extends table_sql {
}
global $OUTPUT;
return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->cm->course, 'includefullname' => true));
return $OUTPUT->user_picture($data, array('courseid' => $this->courseid, 'includefullname' => true));
}
/**
@ -302,7 +346,7 @@ class summary_table extends table_sql {
}
$params = [
'id' => $this->cm->instance, // Forum id.
'id' => $this->cms[0]->instance, // Forum id.
'userids[]' => $data->userid, // User id.
];
@ -381,14 +425,15 @@ class summary_table extends table_sql {
switch($filtertype) {
case self::FILTER_FORUM:
// Requires exactly one forum ID.
if (count($values) != 1) {
// Requires at least one forum ID.
if (empty($values)) {
$paramcounterror = true;
} else {
// No select fields required - displayed in title.
// No extra joins required, forum is already joined.
$this->sql->filterwhere .= ' AND f.id = :forumid';
$this->sql->params['forumid'] = $values[0];
list($forumidin, $forumidparams) = $DB->get_in_or_equal($values, SQL_PARAMS_NAMED);
$this->sql->filterwhere .= " AND f.id {$forumidin}";
$this->sql->params += $forumidparams;
}
break;
@ -498,13 +543,12 @@ class summary_table extends table_sql {
protected function define_base_sql(): void {
global $USER;
$userfields = get_extra_user_fields($this->context);
$userfields = get_extra_user_fields($this->userfieldscontext);
$userfieldssql = \user_picture::fields('u', $userfields);
// Define base SQL query format.
$this->sql->basefields = ' ue.userid AS userid,
e.courseid AS courseid,
f.id as forumid,
SUM(CASE WHEN p.parent = 0 THEN 1 ELSE 0 END) AS postcount,
SUM(CASE WHEN p.parent != 0 THEN 1 ELSE 0 END) AS replycount,
' . $userfieldssql . ',
@ -543,10 +587,10 @@ class summary_table extends table_sql {
$this->sql->basewhere = 'e.courseid = :courseid';
$this->sql->basegroupby = 'ue.userid, e.courseid, f.id, u.id, ' . $userfieldssql;
$this->sql->basegroupby = 'ue.userid, e.courseid, u.id, ' . $userfieldssql;
if ($this->logreader) {
$this->fill_log_summary_temp_table($this->context->id);
$this->fill_log_summary_temp_table();
$this->sql->basefields .= ', CASE WHEN tmp.viewcount IS NOT NULL THEN tmp.viewcount ELSE 0 END AS viewcount';
$this->sql->basefromjoins .= ' LEFT JOIN {' . self::LOG_SUMMARY_TEMP_TABLE . '} tmp ON tmp.userid = u.id ';
@ -561,7 +605,7 @@ class summary_table extends table_sql {
$this->sql->params += [
'component' => 'mod_forum',
'courseid' => $this->cm->course,
'courseid' => $this->courseid,
] + $privaterepliesparams;
// Handle if a user is limited to viewing their own summary.
@ -642,8 +686,10 @@ class summary_table extends table_sql {
* @return void.
*/
protected function apply_filters(array $filters): void {
// Apply the forums filter.
$this->add_filter(self::FILTER_FORUM, $filters['forums']);
// Apply the forums filter if not reporting on every forum in a course.
if (!$this->accessallforums) {
$this->add_filter(self::FILTER_FORUM, $filters['forums']);
}
// Apply groups filter.
$this->add_filter(self::FILTER_GROUPS, $filters['groups']);
@ -719,10 +765,9 @@ class summary_table extends table_sql {
/**
* Fills the log summary temp table.
*
* @param int $contextid
* @return null
*/
protected function fill_log_summary_temp_table(int $contextid) {
protected function fill_log_summary_temp_table() {
global $DB;
$this->create_log_summary_temp_table();
@ -740,11 +785,19 @@ class summary_table extends table_sql {
$datewhere = $this->sql->filterbase['dateslog'] ?? '';
$dateparams = $this->sql->filterbase['dateslogparams'] ?? [];
$params = ['contextid' => $contextid] + $dateparams;
$contextids = [];
foreach ($this->forumcontexts as $forumcontext) {
$contextids[] = $forumcontext->id;
}
list($contextidin, $contextidparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
$params = $contextidparams + $dateparams;
$sql = "INSERT INTO {" . self::LOG_SUMMARY_TEMP_TABLE . "} (userid, viewcount)
SELECT userid, COUNT(*) AS viewcount
FROM {" . $logtable . "}
WHERE contextid = :contextid
WHERE contextid {$contextidin}
$datewhere
$nonanonymous
GROUP BY userid";
@ -797,42 +850,64 @@ class summary_table extends table_sql {
protected function get_filter_groups(array $groups): array {
global $USER;
$groupmode = groups_get_activity_groupmode($this->cm);
$aag = has_capability('moodle/site:accessallgroups', $this->context);
$usergroups = groups_get_all_groups($this->courseid, $USER->id);
$coursegroupsobj = groups_get_all_groups($this->courseid);
$allgroups = false;
$allowedgroupsobj = [];
$allowedgroups = [];
$filtergroups = [];
// Filtering only valid if a forum groups mode is enabled.
if (in_array($groupmode, [VISIBLEGROUPS, SEPARATEGROUPS])) {
$allgroupsobj = groups_get_all_groups($this->cm->course, 0, $this->cm->groupingid);
$allgroups = [];
foreach ($this->cms as $cm) {
// Only need to check for all groups access if not confirmed by a previous check.
if (!$allgroups) {
$groupmode = groups_get_activity_groupmode($cm);
foreach ($allgroupsobj as $group) {
$allgroups[] = $group->id;
// If no groups mode enabled on the forum, nothing to prepare.
if (!in_array($groupmode, [VISIBLEGROUPS, SEPARATEGROUPS])) {
continue;
}
$aag = has_capability('moodle/site:accessallgroups', $this->forumcontexts[$cm->id]);
if ($groupmode == VISIBLEGROUPS || $aag) {
$allgroups = true;
// All groups in course fetched, no need to continue checking for others.
break;
}
}
}
if ($groupmode == VISIBLEGROUPS || $aag) {
$nogroups = new \stdClass();
$nogroups->id = -1;
$nogroups->name = get_string('groupsnone');
if ($allgroups) {
$nogroups = new \stdClass();
$nogroups->id = -1;
$nogroups->name = get_string('groupsnone');
// Any groups and no groups.
$allowedgroupsobj = $allgroupsobj + [$nogroups];
} else {
// Only assigned groups.
$allowedgroupsobj = groups_get_all_groups($this->cm->course, $USER->id, $this->cm->groupingid);
}
// Any groups and no groups.
$allowedgroupsobj = $coursegroupsobj + [$nogroups];
} else {
$allowedgroupsobj = $usergroups;
}
foreach ($allowedgroupsobj as $group) {
$allowedgroups[] = $group->id;
}
foreach ($allowedgroupsobj as $group) {
$allowedgroups[] = $group->id;
}
// If not all groups in course are selected, filter by allowed groups submitted.
if (!empty($groups) && !empty(array_diff($allowedgroups, $groups))) {
// If not all groups in course are selected, filter by allowed groups submitted.
if (!empty($groups)) {
if (!empty(array_diff($allowedgroups, $groups))) {
$filtergroups = array_intersect($groups, $allowedgroups);
} else if (!empty(array_diff($allgroups, $allowedgroups))) {
} else {
$coursegroups = [];
foreach ($coursegroupsobj as $group) {
$coursegroups[] = $group->id;
}
// If user's 'all groups' is a subset of the course groups, filter by all groups available to them.
$filtergroups = $allowedgroups;
if (!empty(array_diff($coursegroups, $allowedgroups))) {
$filtergroups = $allowedgroups;
}
}
}
@ -863,13 +938,21 @@ class summary_table extends table_sql {
global $DB;
if (is_null($this->showwordcharcounts)) {
$forumids = [];
foreach ($this->cms as $cm) {
$forumids[] = $cm->instance;
}
list($forumidin, $forumidparams) = $DB->get_in_or_equal($forumids, SQL_PARAMS_NAMED);
// This should be really fast.
$sql = "SELECT 'x'
FROM {forum_posts} fp
JOIN {forum_discussions} fd ON fd.id = fp.discussion
WHERE fd.forum = :forumid AND (fp.wordcount IS NULL OR fp.charcount IS NULL)";
WHERE fd.forum {$forumidin} AND (fp.wordcount IS NULL OR fp.charcount IS NULL)";
if ($DB->record_exists_sql($sql, ['forumid' => $this->cm->instance])) {
if ($DB->record_exists_sql($sql, $forumidparams)) {
$this->showwordcharcounts = false;
} else {
$this->showwordcharcounts = true;

@ -29,68 +29,134 @@ if (isguestuser()) {
}
$courseid = required_param('courseid', PARAM_INT);
$forumid = required_param('forumid', PARAM_INT);
$forumid = optional_param('forumid', 0, PARAM_INT);
$perpage = optional_param('perpage', \forumreport_summary\summary_table::DEFAULT_PER_PAGE, PARAM_INT);
$download = optional_param('download', '', PARAM_ALPHA);
$filters = [];
$pageurlparams = [
'courseid' => $courseid,
'perpage' => $perpage,
];
// Establish filter values.
$filters['forums'] = [$forumid];
$filters['groups'] = optional_param_array('filtergroups', [], PARAM_INT);
$filters['datefrom'] = optional_param_array('datefrom', ['enabled' => 0], PARAM_INT);
$filters['dateto'] = optional_param_array('dateto', ['enabled' => 0], PARAM_INT);
$download = optional_param('download', '', PARAM_ALPHA);
$cm = null;
$modinfo = get_fast_modinfo($courseid);
$course = $modinfo->get_course();
$courseforums = $modinfo->instances['forum'];
$cms = [];
if (!isset($modinfo->instances['forum'][$forumid])) {
throw new \moodle_exception("A valid forum ID is required to generate a summary report.");
// Determine which forums the user has access to in the course.
$accessallforums = false;
$allforumidsincourse = array_keys($courseforums);
$forumsvisibletouser = [];
$forumselectoptions = [0 => get_string('forumselectcourseoption', 'forumreport_summary')];
foreach ($courseforums as $courseforumid => $courseforum) {
if ($courseforum->uservisible) {
$forumsvisibletouser[$courseforumid] = $courseforum;
$forumselectoptions[$courseforumid] = $courseforum->name;
}
}
$foruminfo = $modinfo->instances['forum'][$forumid];
$forumname = $foruminfo->name;
$cm = $foruminfo->get_course_module_record();
if ($forumid) {
if (!isset($forumsvisibletouser[$forumid])) {
throw new \moodle_exception('A valid forum ID is required to generate a summary report.');
}
require_login($courseid, false, $cm);
$context = \context_module::instance($cm->id);
$filters['forums'] = [$forumid];
$title = $forumsvisibletouser[$forumid]->name;
$forumcm = $forumsvisibletouser[$forumid];
$cms[] = $forumcm;
// This capability is required to view any version of the report.
if (!has_capability("forumreport/summary:view", $context)) {
$redirecturl = new moodle_url("/mod/forum/view.php");
$redirecturl->param('id', $forumid);
require_login($courseid, false, $forumcm);
$context = $forumcm->context;
$canexport = !$download && has_capability('mod/forum:exportforum', $context);
$redirecturl = new moodle_url('/mod/forum/view.php', ['id' => $forumid]);
$numforums = 1;
$pageurlparams['forumid'] = $forumid;
$iscoursereport = false;
} else {
// Course level report.
require_login($courseid, false);
$filters['forums'] = array_keys($forumsvisibletouser);
// Fetch the forum CMs for the course.
foreach ($forumsvisibletouser as $visibleforum) {
$cms[] = $visibleforum;
}
$context = \context_course::instance($courseid);
$title = $course->fullname;
// Export currently only supports single forum exports.
$canexport = false;
$redirecturl = new moodle_url('/course/view.php', ['id' => $courseid]);
$numforums = count($forumsvisibletouser);
$iscoursereport = true;
// Specify whether user has access to all forums in the course.
$accessallforums = empty(array_diff($allforumidsincourse, $filters['forums']));
}
$pageurl = new moodle_url('/mod/forum/report/summary/index.php', $pageurlparams);
$PAGE->set_url($pageurl);
$PAGE->set_pagelayout('report');
$PAGE->set_title($title);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add(get_string('nodetitle', 'forumreport_summary'));
$allowbulkoperations = !$download && !empty($CFG->messaging) && has_capability('moodle/course:bulkmessaging', $context);
$canseeprivatereplies = false;
$hasviewall = false;
$privatereplycapcount = 0;
$viewallcount = 0;
$canview = false;
foreach ($cms as $cm) {
$forumcontext = $cm->context;
// This capability is required in at least one of the given contexts to view any version of the report.
if (has_capability('forumreport/summary:view', $forumcontext)) {
$canview = true;
}
if (has_capability('mod/forum:readprivatereplies', $forumcontext)) {
$privatereplycapcount++;
}
if (has_capability('forumreport/summary:viewall', $forumcontext)) {
$viewallcount++;
}
}
if (!$canview) {
redirect($redirecturl);
}
$course = $modinfo->get_course();
// Only use private replies if user has that cap in all forums in the report.
if ($numforums === $privatereplycapcount) {
$canseeprivatereplies = true;
}
$urlparams = [
'courseid' => $courseid,
'forumid' => $forumid,
'perpage' => $perpage,
];
$url = new moodle_url("/mod/forum/report/summary/index.php", $urlparams);
$PAGE->set_url($url);
$PAGE->set_pagelayout('report');
$PAGE->set_title($forumname);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add(get_string('nodetitle', "forumreport_summary"));
// Will only show all users if user has the cap for all forums in the report.
if ($numforums === $viewallcount) {
$hasviewall = true;
}
// Prepare and display the report.
$allowbulkoperations = !$download && !empty($CFG->messaging) && has_capability('moodle/course:bulkmessaging', $context);
$canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $context);
$canexport = !$download && has_capability('mod/forum:exportforum', $context);
$table = new \forumreport_summary\summary_table($courseid, $filters, $allowbulkoperations,
$canseeprivatereplies, $perpage, $canexport);
$table->baseurl = $url;
$canseeprivatereplies, $perpage, $canexport, $iscoursereport, $accessallforums);
$table->baseurl = $pageurl;
$eventparams = [
'context' => $context,
'other' => [
'forumid' => $forumid,
'hasviewall' => has_capability('forumreport/summary:viewall', $context),
'hasviewall' => $hasviewall,
],
];
@ -101,16 +167,23 @@ if ($download) {
\forumreport_summary\event\report_viewed::create($eventparams)->trigger();
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('summarytitle', 'forumreport_summary', $forumname), 2, 'p-b-2');
echo $OUTPUT->heading(get_string('summarytitle', 'forumreport_summary', $title), 2, 'p-b-2');
if (!empty($filters['groups'])) {
\core\notification::info(get_string('viewsdisclaimer', 'forumreport_summary'));
}
// Allow switching to course report (or other forum user has access to).
$reporturl = new moodle_url('/mod/forum/report/summary/index.php', ['courseid' => $courseid]);
$forumselect = new single_select($reporturl, 'forumid', $forumselectoptions, $forumid, '');
$forumselect->set_label(get_string('forumselectlabel', 'forumreport_summary'));
echo $OUTPUT->render($forumselect);
// Render the report filters form.
$renderer = $PAGE->get_renderer('forumreport_summary');
echo $renderer->render_filters_form($cm, $url, $filters);
unset($filters['forums']);
echo $renderer->render_filters_form($course, $cms, $pageurl, $filters);
$table->show_download_buttons_at(array(TABLE_P_BOTTOM));
echo $renderer->render_summary_table($table);
echo $OUTPUT->footer();

@ -38,16 +38,18 @@ $string['filter:groupsbuttonlabel'] = 'Open the groups filter';
$string['filter:groupsname'] = 'Groups';
$string['filter:groupscountall'] = 'Groups (all)';
$string['filter:groupscountnumber'] = 'Groups ({$a})';
$string['forumselectlabel'] = 'Forum selected';
$string['forumselectcourseoption'] = 'All forums in course';
$string['latestpost'] = 'Most recent post';
$string['exportposts'] = 'Export posts';
$string['exportpostslabel'] = 'Export posts for {$a}';
$string['nodetitle'] = 'Summary report';
$string['nodetitle'] = 'Forum summary report';
$string['pluginname'] = 'Forum summary report';
$string['postcount'] = 'Number of discussions posted';
$string['privacy:metadata'] = 'The Forum summary report plugin does not store any personal data.';
$string['replycount'] = 'Number of replies posted';
$string['summary:viewall'] = 'Access summary report data for each user within a given forum or forums';
$string['summary:view'] = 'Access summary report within a given forum or forums';
$string['summarytitle'] = 'Summary report - {$a}';
$string['summarytitle'] = 'Forum summary report - {$a}';
$string['viewsdisclaimer'] = 'Number of views column is not filtered by group';
$string['wordcount'] = 'Word count';

@ -37,13 +37,14 @@ class forumreport_summary_renderer extends plugin_renderer_base {
/**
* Render the filters available for the forum summary report.
*
* @param stdClass $cm The course module object.
* @param stdClass $course The course object.
* @param array $cms Array of course module objects.
* @param moodle_url $actionurl The form action URL.
* @param array $filters Optional array of currently applied filter values.
* @return string The filter form HTML.
*/
public function render_filters_form(stdClass $cm, moodle_url $actionurl, array $filters = []): string {
$renderable = new \forumreport_summary\output\filters($cm, $actionurl, $filters);
public function render_filters_form(stdClass $course, array $cms, moodle_url $actionurl, array $filters = []): string {
$renderable = new \forumreport_summary\output\filters($course, $cms, $actionurl, $filters);
$templatecontext = $renderable->export_for_template($this);
return $this->render_from_template('forumreport_summary/filters', $templatecontext);

@ -61,7 +61,7 @@
}
}}
<div class="pb-4" data-report-id="{{uniqid}}">
<div class="pb-4 pt-4" data-report-id="{{uniqid}}">
<form id="filtersform" name="filtersform" method="post" action="{{actionurl}}">
<input type="hidden" name="submitted" value="true">

@ -43,7 +43,7 @@ Feature: Message users in the summary report
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
And I click on "Select 'Student 1'" "checkbox"
And I click on "Select 'Student 3'" "checkbox"
And I set the field "With selected users..." to "Send a message"
@ -65,7 +65,7 @@ Feature: Message users in the summary report
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
And I click on "Select all" "checkbox"
And I set the field "With selected users..." to "Send a message"
Then I should see "Send message to 3 people"
@ -79,6 +79,6 @@ Feature: Message users in the summary report
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then I should not see "With selected users..."
And I should not see "Select all"

@ -0,0 +1,120 @@
@mod @mod_forum @forumreport @forumreport_summary
Feature: Course level forum summary report
In order to gain an overview of students' forum activities across a course
As a teacher
I should be able to prepare a summary report of all forums in a course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
| student3 | Student | 3 | student3@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student2 | C2 | student |
| student3 | C2 | student |
And the following "activities" exist:
| activity | name | description | course | idnumber |
| forum | forum1 | C1 forum 1 | C1 | forum1 |
| forum | forum2 | C1 forum 2 | C1 | forum2 |
| forum | forum3 | C1 forum 3 | C1 | forum3 |
| forum | forum4 | C2 forum 1 | C2 | forum4 |
And the following forum discussions exist in course "Course 1":
| user | forum | name | message | created |
| teacher1 | forum1 | discussion1 | Discussion 1 | ##2018-01-14 09:00:00## |
| teacher1 | forum2 | discussion2 | Discussion 2 | ##2019-03-27 12:10:00## |
| teacher1 | forum3 | discussion3 | Discussion 3 | ##2019-12-25 15:20:00## |
| teacher1 | forum3 | discussion4 | Discussion 4 | ##2019-12-26 09:30:00## |
| student1 | forum2 | discussion5 | Discussion 5 | ##2019-06-06 18:40:00## |
| student1 | forum3 | discussion6 | Discussion 6 | ##2020-01-25 11:50:00## |
And the following forum replies exist in course "Course 1":
| user | forum | discussion | subject | message | created |
| teacher1 | forum1 | discussion1 | Re d1 | Reply 1 | ##2018-02-15 11:10:00## |
| teacher1 | forum2 | discussion5 | Re d5 | Reply 2 | ##2019-06-09 18:20:00## |
| teacher1 | forum2 | discussion5 | Re d5 | Reply 3 | ##2019-07-10 09:30:00## |
| student1 | forum1 | discussion1 | Re d1 | Reply 4 | ##2018-01-25 16:40:00## |
| student1 | forum2 | discussion2 | Re d6 | Reply 5 | ##2019-03-28 11:50:00## |
| student1 | forum3 | discussion4 | Re d4 | Reply 6 | ##2019-12-30 20:00:00## |
And the following forum discussions exist in course "Course 2":
| user | forum | name | message | created |
| teacher1 | forum4 | discussion7 | Discussion 7 | ##2020-01-29 15:00:00## |
| student2 | forum4 | discussion8 | Discussion 8 | ##2020-02-02 16:00:00## |
And the following forum replies exist in course "Course 2":
| user | forum | discussion | subject | message | created |
| teacher1 | forum4 | discussion8 | Re d8 | Reply 7 | ##2020-02-03 09:45:00## |
| student2 | forum4 | discussion7 | Re d7 | Reply 8 | ##2020-02-04 13:50:00## |
Scenario: Course forum summary report can be viewed by teacher and contains accurate data
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum2"
And I navigate to "Forum summary report" in current page administration
And I should see "Export posts"
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies | | |
| First name / Surname | -3- | -4- | Earliest post | Most recent post |
| Student 1 | 1 | 1 | Thursday, 28 March 2019, 11:50 AM | Thursday, 6 June 2019, 6:40 PM |
| Student 2 | 0 | 0 | - | - |
| Teacher 1 | 1 | 2 | Wednesday, 27 March 2019, 12:10 PM | Wednesday, 10 July 2019, 9:30 AM |
And the following should not exist in the "forumreport_summary_table" table:
| First name / Surname |
| Student 3 |
And the "Forum selected" select box should contain "All forums in course"
And the "Forum selected" select box should contain "forum1"
And the "Forum selected" select box should contain "forum2"
And the "Forum selected" select box should contain "forum3"
And the "Forum selected" select box should not contain "forum4"
Then I select "All forums in course" from the "Forum selected" singleselect
And I should not see "Export posts"
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies | | |
| First name / Surname | -3- | -4- | Earliest post | Most recent post |
| Student 1 | 2 | 3 | Thursday, 25 January 2018, 4:40 PM | Saturday, 25 January 2020, 11:50 AM |
| Student 2 | 0 | 0 | - | - |
| Teacher 1 | 4 | 3 | Sunday, 14 January 2018, 9:00 AM | Thursday, 26 December 2019, 9:30 AM |
And the following should not exist in the "forumreport_summary_table" table:
| First name / Surname |
| Student 3 |
Scenario: Students given the view capability can view their own course report data
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| forumreport/summary:view | Allow | student | Course | C1 |
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Forum summary report" in current page administration
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies | | |
| First name / Surname | -2- | -3- | Earliest post | Most recent post |
| Student 1 | 0 | 1 | Thursday, 25 January 2018, 4:40 PM | Thursday, 25 January 2018, 4:40 PM |
And the following should not exist in the "forumreport_summary_table" table:
| First name / Surname |
| Student 2 |
| Student 3 |
| Teacher 1 |
And the "Forum selected" select box should contain "All forums in course"
And the "Forum selected" select box should contain "forum1"
And the "Forum selected" select box should contain "forum2"
And the "Forum selected" select box should contain "forum3"
And the "Forum selected" select box should not contain "forum4"
Then I select "All forums in course" from the "Forum selected" singleselect
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies | | |
| First name / Surname | -2- | -3- | Earliest post | Most recent post |
| Student 1 | 2 | 3 | Thursday, 25 January 2018, 4:40 PM | Saturday, 25 January 2020, 11:50 AM |
And the following should not exist in the "forumreport_summary_table" table:
| First name / Surname |
| Student 2 |
| Student 3 |
| Teacher 1 |

@ -44,7 +44,7 @@ Feature: Include private replies in the summary report
When I log in as "teacher2"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Teacher 1" row "Number of replies posted" column of "forumreport_summary_table" table should contain "3"
Scenario: Private replies are not counted when Teacher has not capability
@ -54,5 +54,5 @@ Feature: Include private replies in the summary report
When I log in as "teacher2"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Teacher 1" row "Number of replies posted" column of "forumreport_summary_table" table should contain "2"

@ -36,7 +36,7 @@ Feature: Report relevant content availability
Given I log in as "teacher1"
When I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then the following should exist in the "forumreport_summary_table" table:
| -2- | -3- | -4- | -5- | -6- | -7- | -8- |
| Student 1 | 1 | 1 | 5 | 0 | 3 | 14 |
@ -60,7 +60,7 @@ Feature: Report relevant content availability
Given I log in as "student1"
When I am on "Course 1" course homepage
And I follow "forum1"
Then "Summary report" "link" should not exist in current page administration
Then "Forum summary report" "link" should not exist in current page administration
@javascript
Scenario: Students given the view capability can only view their own report data
@ -70,7 +70,7 @@ Feature: Report relevant content availability
When I log in as "student1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then the following should exist in the "forumreport_summary_table" table:
| -1- | -2- | -3- | -4- | -5- | -6- | -7- |
| Student 1 | 1 | 1 | 5 | 1 | 3 | 14 |

@ -44,7 +44,7 @@ Feature: Attachments count column data available
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Teacher 1" row "Number of attachments" column of "forumreport_summary_table" table should contain "6"
And "Student 1" row "Number of attachments" column of "forumreport_summary_table" table should contain "5"
And "Student 2" row "Number of attachments" column of "forumreport_summary_table" table should contain "0"

@ -46,7 +46,7 @@ Feature: Post date columns data available
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Teacher 1" row "Earliest post" column of "forumreport_summary_table" table should contain "Tuesday, 2 January 2018, 9:00 AM"
Then "Teacher 1" row "Most recent post" column of "forumreport_summary_table" table should contain "Sunday, 1 September 2019, 7:00 AM"
Then "Student 1" row "Earliest post" column of "forumreport_summary_table" table should contain "Wednesday, 27 March 2019, 4:00 AM"

@ -65,7 +65,7 @@ Feature: Groups report filter is available if groups exist
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Groups" "button" should exist
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies |
@ -115,7 +115,7 @@ Feature: Groups report filter is available if groups exist
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Groups" "button" should exist
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies |
@ -151,7 +151,7 @@ Feature: Groups report filter is available if groups exist
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum2"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Groups" "button" should exist
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies |
@ -177,7 +177,7 @@ Feature: Groups report filter is available if groups exist
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum2"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies |
| First name / Surname | -3- | -4- |
@ -202,7 +202,7 @@ Feature: Groups report filter is available if groups exist
When I log in as "admin"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Groups" "button" should exist
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies |
@ -221,3 +221,32 @@ Feature: Groups report filter is available if groups exist
And I should not see "Student 2"
And I should not see "With selected users..."
And I should not see "Download table data as"
@javascript
Scenario: Course forum summary report can be filtered by group
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum2"
And I navigate to "Forum summary report" in current page administration
And I select "All forums in course" from the "Forum selected" singleselect
And I click on "Groups" "button"
And I click on "Clear" "button" in the "filter-groups-popover" "region"
And I click on "Group A" "checkbox" in the "filter-groups-popover" "region"
And I click on "Group C" "checkbox" in the "filter-groups-popover" "region"
And I click on "Save" "button"
And "Groups (2)" "button" should exist
Then the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies |
| First name / Surname | -3- | -4- |
| Student 1 | 1 | 1 |
| Teacher 1 | 2 | 3 |
And I should not see "Student 2"
# Ensure re-ordering retains filter.
And I click on "Number of discussions posted" "link"
And "Groups (2)" "button" should exist
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions | Replies |
| First name / Surname | -3- | -4- |
| Student 1 | 1 | 1 |
| Teacher 1 | 2 | 3 |
And I should not see "Student 2"

@ -52,7 +52,7 @@ Feature: Groups report filter is not available if no groups exist
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I follow "forum1"
And I navigate to "Summary report" in current page administration
And I navigate to "Forum summary report" in current page administration
Then "Groups" "button" should not exist
And the following should exist in the "forumreport_summary_table" table:
# | | Discussions |