diff --git a/mod/forum/report/summary/classes/event/report_downloaded.php b/mod/forum/report/summary/classes/event/report_downloaded.php index cc9533686c8..9374eb5309d 100644 --- a/mod/forum/report/summary/classes/event/report_downloaded.php +++ b/mod/forum/report/summary/classes/event/report_downloaded.php @@ -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); } /** diff --git a/mod/forum/report/summary/classes/event/report_viewed.php b/mod/forum/report/summary/classes/event/report_viewed.php index 5f313d18a5b..991dd326437 100644 --- a/mod/forum/report/summary/classes/event/report_viewed.php +++ b/mod/forum/report/summary/classes/event/report_viewed.php @@ -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); } /** diff --git a/mod/forum/report/summary/classes/output/filters.php b/mod/forum/report/summary/classes/output/filters.php index 4fe3398c92e..54a6eea837c 100644 --- a/mod/forum/report/summary/classes/output/filters.php +++ b/mod/forum/report/summary/classes/output/filters.php @@ -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]; diff --git a/mod/forum/report/summary/classes/summary_table.php b/mod/forum/report/summary/classes/summary_table.php index 7cc4ba5522a..2e5e7a68a88 100644 --- a/mod/forum/report/summary/classes/summary_table.php +++ b/mod/forum/report/summary/classes/summary_table.php @@ -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; diff --git a/mod/forum/report/summary/index.php b/mod/forum/report/summary/index.php index 274adcbe83d..54ec58b3ee9 100644 --- a/mod/forum/report/summary/index.php +++ b/mod/forum/report/summary/index.php @@ -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(); diff --git a/mod/forum/report/summary/lang/en/forumreport_summary.php b/mod/forum/report/summary/lang/en/forumreport_summary.php index b741274894c..d9051b45e69 100644 --- a/mod/forum/report/summary/lang/en/forumreport_summary.php +++ b/mod/forum/report/summary/lang/en/forumreport_summary.php @@ -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'; diff --git a/mod/forum/report/summary/renderer.php b/mod/forum/report/summary/renderer.php index 1c61daed696..06d04480047 100644 --- a/mod/forum/report/summary/renderer.php +++ b/mod/forum/report/summary/renderer.php @@ -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); diff --git a/mod/forum/report/summary/templates/filters.mustache b/mod/forum/report/summary/templates/filters.mustache index 09dde0e596c..f0b9f2eaf7f 100644 --- a/mod/forum/report/summary/templates/filters.mustache +++ b/mod/forum/report/summary/templates/filters.mustache @@ -61,7 +61,7 @@ } }} -
+
diff --git a/mod/forum/report/summary/tests/behat/bulk_message.feature b/mod/forum/report/summary/tests/behat/bulk_message.feature index beac0d5b3ef..86fb4bbbb55 100644 --- a/mod/forum/report/summary/tests/behat/bulk_message.feature +++ b/mod/forum/report/summary/tests/behat/bulk_message.feature @@ -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" diff --git a/mod/forum/report/summary/tests/behat/course_summary.feature b/mod/forum/report/summary/tests/behat/course_summary.feature new file mode 100644 index 00000000000..473bdade76e --- /dev/null +++ b/mod/forum/report/summary/tests/behat/course_summary.feature @@ -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 | diff --git a/mod/forum/report/summary/tests/behat/private_replies.feature b/mod/forum/report/summary/tests/behat/private_replies.feature index 92d040d008a..ab80ad431c7 100644 --- a/mod/forum/report/summary/tests/behat/private_replies.feature +++ b/mod/forum/report/summary/tests/behat/private_replies.feature @@ -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" diff --git a/mod/forum/report/summary/tests/behat/summary_data_access.feature b/mod/forum/report/summary/tests/behat/summary_data_access.feature index f7f4776f20a..0fed1f2b472 100644 --- a/mod/forum/report/summary/tests/behat/summary_data_access.feature +++ b/mod/forum/report/summary/tests/behat/summary_data_access.feature @@ -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 | diff --git a/mod/forum/report/summary/tests/behat/summary_data_attachments.feature b/mod/forum/report/summary/tests/behat/summary_data_attachments.feature index a715a70b41b..b448a850d4a 100644 --- a/mod/forum/report/summary/tests/behat/summary_data_attachments.feature +++ b/mod/forum/report/summary/tests/behat/summary_data_attachments.feature @@ -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" diff --git a/mod/forum/report/summary/tests/behat/summary_data_post_dates.feature b/mod/forum/report/summary/tests/behat/summary_data_post_dates.feature index c1834066f93..07fdff1c73b 100644 --- a/mod/forum/report/summary/tests/behat/summary_data_post_dates.feature +++ b/mod/forum/report/summary/tests/behat/summary_data_post_dates.feature @@ -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" diff --git a/mod/forum/report/summary/tests/behat/summary_filter_groups.feature b/mod/forum/report/summary/tests/behat/summary_filter_groups.feature index 3fa3448e140..90393fa2670 100644 --- a/mod/forum/report/summary/tests/behat/summary_filter_groups.feature +++ b/mod/forum/report/summary/tests/behat/summary_filter_groups.feature @@ -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" diff --git a/mod/forum/report/summary/tests/behat/summary_filter_no_groups.feature b/mod/forum/report/summary/tests/behat/summary_filter_no_groups.feature index bd010fc1b29..7f837dce86a 100644 --- a/mod/forum/report/summary/tests/behat/summary_filter_no_groups.feature +++ b/mod/forum/report/summary/tests/behat/summary_filter_no_groups.feature @@ -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 |