MDL-66268 forumreport_summary: Add filters handling incl groups filter

Part of MDL-66076.
This commit is contained in:
Michael Hawkins 2019-08-09 13:29:30 +08:00 committed by Jun Pataleta
parent 90f2b0a905
commit b29de56dd7
11 changed files with 608 additions and 11 deletions

View File

@ -0,0 +1,2 @@
define ("forumreport_summary/filters",["exports","jquery","core/popper"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var e=function(a){a=(0,b.default)(a);(0,b.default)(document).ready(function(){(0,b.default)(".loading-icon").hide();(0,b.default)("#summaryreport").removeClass("hidden")});(0,b.default)(a).on("click",".filter-clear",function(a){var b=a.target.parentNode.parentNode.parentElement.querySelectorAll("input[type=\"checkbox\"]:checked");b.forEach(function(a){a.checked=!1})});var d=function(a){var c=(0,b.default)("#filtersform").attr("action");if(a){a.preventDefault();var d=a.target.search.substr(1);c+="&"+d}(0,b.default)("#filtersform").attr("action",c);(0,b.default)("#filtersform").submit()};(0,b.default)(".resettable").on("click","a",function(a){d(a)});(0,b.default)("thead").on("click","a",function(a){d(a)});(0,b.default)(".pagination").on("click","a",function(a){d(a)});var e=function(a){var b=document.getElementById(a),c=b.querySelectorAll("input[type=\"checkbox\"]:not(:checked)");c.forEach(function(a){a.checked=!0})};(0,b.default)("#filter-groups-popover .select-all").on("click",function(){e("filter-groups-popover")});(0,b.default)("#filter-groups-button").on("click",function(){var a=document.querySelector("#filter-groups-button"),d=document.querySelector("#filter-groups-popover");new c.default(a,d,{placement:"bottom"});(0,b.default)("#filter-groups-popover").removeClass("hidden")});(0,b.default)(a).on("click","#filter-groups-popover .filter-save",function(){(0,b.default)("#filter-groups-popover").addClass("hidden");d(!1)})};a.init=e});
//# sourceMappingURL=filters.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,117 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module responsible for handling forum summary report filters.
*
* @module forumreport_summary/filters
* @package forumreport_summary
* @copyright 2019 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import Popper from 'core/popper';
export const init = (root) => {
root = $(root);
// Hide loading spinner and show report once page is ready.
// This ensures filters can be applied when sorting by columns.
$(document).ready(function() {
$('.loading-icon').hide();
$('#summaryreport').removeClass('hidden');
});
// Generic filter handlers.
// Event handler to clear filters.
$(root).on("click", ".filter-clear", function(event) {
// Clear checkboxes.
let selected = event.target.parentNode.parentNode.parentElement.querySelectorAll('input[type="checkbox"]:checked');
selected.forEach(function(checkbox) {
checkbox.checked = false;
});
});
// Called to override click event to trigger a proper generate request with filtering.
var generateWithFilters = (event) => {
var newLink = $('#filtersform').attr('action');
if (event) {
event.preventDefault();
let filterParams = event.target.search.substr(1);
newLink += '&' + filterParams;
}
$('#filtersform').attr('action', newLink);
$('#filtersform').submit();
};
// Override 'reset table preferences' so it generates with filters.
$('.resettable').on("click", "a", function(event) {
generateWithFilters(event);
});
// Override table heading sort links so they generate with filters.
$('thead').on("click", "a", function(event) {
generateWithFilters(event);
});
// Override pagination page links so they generate with filters.
$('.pagination').on("click", "a", function(event) {
generateWithFilters(event);
});
// Select all checkboxes within a filter section.
var selectAll = (checkboxdiv) => {
let targetdiv = document.getElementById(checkboxdiv);
let deselected = targetdiv.querySelectorAll('input[type="checkbox"]:not(:checked)');
deselected.forEach(function(checkbox) {
checkbox.checked = true;
});
};
// Groups filter specific handlers.
// Event to handle select all groups.
$('#filter-groups-popover .select-all').on('click', function() {
selectAll('filter-groups-popover');
});
// Event handler for showing groups filter popover.
$('#filter-groups-button').on('click', function() {
// Create popover.
var referenceElement = document.querySelector('#filter-groups-button'),
popperContent = document.querySelector('#filter-groups-popover');
new Popper(referenceElement, popperContent, {placement: 'bottom'});
// Show popover.
$('#filter-groups-popover').removeClass('hidden');
});
// Event handler to save groups filter.
$(root).on("click", "#filter-groups-popover .filter-save", function() {
// Close the popover.
$('#filter-groups-popover').addClass('hidden');
// Submit the filter values and re-generate report.
generateWithFilters(false);
});
};

View File

@ -0,0 +1,169 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Forum summary report filters renderable.
*
* @package forumreport_summary
* @copyright 2019 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace forumreport_summary\output;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
defined('MOODLE_INTERNAL') || die();
/**
* Forum summary report filters renderable.
*
* @copyright 2019 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filters implements renderable, templatable {
/**
* Course module the report is being run within.
*
* @var stdClass $cm
*/
protected $cm;
/**
* Moodle URL used as the form action on the generate button.
*
* @var moodle_url $actionurl
*/
protected $actionurl;
/**
* Details of groups available for filtering.
* Stored in the format groupid => groupname.
*
* @var array $groupsavailable
*/
protected $groupsavailable = [];
/**
* IDs of groups selected for filtering.
*
* @var array $groupsselected
*/
protected $groupsselected = [];
/**
* Builds renderable filter data.
*
* @param stdClass $cm The course module object.
* @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]
*/
public function __construct(stdClass $cm, moodle_url $actionurl, array $filterdata = []) {
$this->cm = $cm;
$this->actionurl = $actionurl;
// Prepare groups filter data.
$groupsdata = $filterdata['groups'] ?? [];
$this->prepare_groups_data($groupsdata);
}
/**
* Prepares groups data and sets relevant property values.
*
* @param array $groupsdata Groups selected for filtering.
* @return void.
*/
protected function prepare_groups_data(array $groupsdata): void {
$groupsavailable = [];
$groupsselected = [];
// Only fetch groups user has access to.
$groups = groups_get_activity_allowed_groups($this->cm);
// Include a 'no groups' option if groups exist.
if (!empty($groups)) {
$nogroups = new stdClass();
$nogroups->id = -1;
$nogroups->name = get_string('groupsnone');
array_push($groups, $nogroups);
}
foreach ($groups as $group) {
$groupsavailable[$group->id] = $group->name;
// Select provided groups if they are available.
if (in_array($group->id, $groupsdata)) {
$groupsselected[] = $group->id;
}
}
// Overwrite groups properties.
$this->groupsavailable = $groupsavailable;
$this->groupsselected = $groupsselected;
}
/**
* Export data for use as the context of a mustache template.
*
* @param renderer_base $renderer The renderer to be used to display report filters.
* @return array Data in a format compatible with a mustache template.
*/
public function export_for_template(renderer_base $renderer): stdClass {
$output = new stdClass();
// Set formaction URL.
$output->actionurl = $this->actionurl->out(false);
// Set groups filter data.
if (!empty($this->groupsavailable)) {
$output->hasgroups = true;
$groupscount = count($this->groupsselected);
if (count($this->groupsavailable) <= $groupscount) {
$output->filtergroupsname = get_string('filter:groupscountall', 'forumreport_summary');
} else if (!empty($this->groupsselected)) {
$output->filtergroupsname = get_string('filter:groupscountnumber', 'forumreport_summary', $groupscount);
} else {
$output->filtergroupsname = get_string('filter:groupsname', 'forumreport_summary');
}
// Set groups filter.
$groupsdata = [];
foreach ($this->groupsavailable as $groupid => $groupname) {
$groupsdata[] = [
'groupid' => $groupid,
'groupname' => $groupname,
'checked' => in_array($groupid, $this->groupsselected),
];
}
$output->filtergroups = $groupsdata;
} else {
$output->hasgroups = false;
}
return $output;
}
}

View File

@ -41,6 +41,9 @@ class summary_table extends table_sql {
/** Forum filter type */
const FILTER_FORUM = 1;
/** Groups filter type */
const FILTER_GROUPS = 2;
/** @var \stdClass The various SQL segments that will be combined to form queries to fetch various information. */
public $sql;
@ -53,25 +56,36 @@ class summary_table extends table_sql {
/** @var int The forum ID being reported on. */
protected $forumid;
/** @var \stdClass The course module object of the forum being reported on. */
protected $cm;
/**
* @var int The user ID if only one user's summary will be generated.
* This will apply to users without permission to view others' summaries.
*/
protected $userid;
/**
* @var bool Whether the table should be overridden to show the 'nothing to display' message.
* False unless checks confirm there will be nothing to display.
*/
protected $nothingtodisplay = false;
/**
* Forum report table constructor.
*
* @param int $courseid The ID of the course the forum(s) exist within.
* @param int $forumid The ID of the forum being summarised.
* @param array $filters Report filters in the format 'type' => [values].
*/
public function __construct(int $courseid, int $forumid) {
public function __construct(int $courseid, array $filters) {
global $USER;
$forumid = $filters['forums'][0];
parent::__construct("summaryreport_{$courseid}_{$forumid}");
$cm = get_coursemodule_from_instance('forum', $forumid, $courseid);
$context = \context_module::instance($cm->id);
$this->cm = get_coursemodule_from_instance('forum', $forumid, $courseid);
$context = \context_module::instance($this->cm->id);
// Only show their own summary unless they have permission to view all.
if (!has_capability('forumreport/summary:viewall', $context)) {
@ -98,8 +112,8 @@ class summary_table extends table_sql {
// Define the basic SQL data and object format.
$this->define_base_sql();
// Set the forum ID.
$this->add_filter(self::FILTER_FORUM, [$forumid]);
// Apply relevant filters.
$this->apply_filters($filters);
}
/**
@ -111,6 +125,7 @@ class summary_table extends table_sql {
public function get_filter_name(int $filtertype): string {
$filternames = [
self::FILTER_FORUM => 'Forum',
self::FILTER_GROUPS => 'Groups',
];
return $filternames[$filtertype];
@ -232,6 +247,8 @@ class summary_table extends table_sql {
* @throws coding_exception
*/
public function add_filter(int $filtertype, array $values = []): void {
global $DB;
$paramcounterror = false;
switch($filtertype) {
@ -248,6 +265,68 @@ class summary_table extends table_sql {
break;
case self::FILTER_GROUPS:
// Find total number of options available (groups plus 'no groups').
$availablegroups = groups_get_activity_allowed_groups($this->cm);
$alloptionscount = 1 + count($availablegroups);
// Skip adding filter if not applied, or all options are selected.
if (!empty($values) && count($values) < $alloptionscount) {
// Include users without groups if that option (-1) is selected.
$nonekey = array_search(-1, $values, true);
// Users within selected groups or not in any groups are included.
if ($nonekey !== false && count($values) > 1) {
unset($values[$nonekey]);
list($groupidin, $groupidparams) = $DB->get_in_or_equal($values, SQL_PARAMS_NAMED, 'groupid');
// No select fields required.
// No joins required (handled by where to prevent data duplication).
$this->sql->filterwhere .= "
AND (u.id =
(SELECT gm.userid
FROM {groups_members} gm
WHERE gm.userid = u.id
AND gm.groupid {$groupidin}
GROUP BY gm.userid
LIMIT 1)
OR
(SELECT nogm.userid
FROM mdl_groups_members nogm
WHERE nogm.userid = u.id
GROUP BY nogm.userid
LIMIT 1)
IS NULL)";
$this->sql->params += $groupidparams;
} else if ($nonekey !== false) {
// Only users within no groups are included.
unset($values[$nonekey]);
// No select fields required.
$this->sql->filterfromjoins .= " LEFT JOIN {groups_members} nogm ON nogm.userid = u.id";
$this->sql->filterwhere .= " AND nogm.id IS NULL";
} else if (!empty($values)) {
// Only users within selected groups are included.
list($groupidin, $groupidparams) = $DB->get_in_or_equal($values, SQL_PARAMS_NAMED, 'groupid');
// No select fields required.
// No joins required (handled by where to prevent data duplication).
$this->sql->filterwhere .= "
AND u.id = (
SELECT gm.userid
FROM {groups_members} gm
WHERE gm.userid = u.id
AND gm.groupid {$groupidin}
GROUP BY gm.userid
LIMIT 1)";
$this->sql->params += $groupidparams;
}
}
break;
default:
throw new coding_exception("Report filter type '{$filtertype}' not found.");
break;
@ -361,6 +440,12 @@ class summary_table extends table_sql {
public function out($pagesize, $useinitialsbar, $downloadhelpbutton = ''): void {
global $DB;
// If there is nothing to display, print the relevant string and return, no further action is required.
if ($this->nothingtodisplay) {
$this->print_nothing_to_display();
return;
}
if (!$this->columns) {
$sql = $this->get_full_sql();
@ -378,6 +463,20 @@ class summary_table extends table_sql {
$this->finish_output();
}
/**
* Apply the relevant filters to the report.
*
* @param array $filters Report filters in the format 'type' => [values].
* @return void.
*/
protected function apply_filters(array $filters): void {
// Apply the forums filter.
$this->add_filter(self::FILTER_FORUM, $filters['forums']);
// Apply groups filter.
$this->add_filter(self::FILTER_GROUPS, $filters['groups']);
}
/**
* Prepares a complete SQL statement from the base query and any filters defined.
*

View File

@ -31,6 +31,12 @@ if (isguestuser()) {
$courseid = required_param('courseid', PARAM_INT);
$forumid = required_param('forumid', PARAM_INT);
$perpage = optional_param('perpage', 25, PARAM_INT);
$filters = [];
// Establish filter values.
$filters['forums'] = [$forumid];
$filters['groups'] = optional_param_array('filtergroups', [], PARAM_INT);
$cm = null;
$modinfo = get_fast_modinfo($courseid);
@ -47,9 +53,9 @@ if ($forumid > 0) {
}
require_login($courseid, false, $cm);
$context = \context_module::instance($cm->id);
// This capability is required to view any version of the report.
$context = \context_module::instance($cm->id);
if (!has_capability("forumreport/summary:view", $context)) {
$redirecturl = new moodle_url("/mod/forum/view.php");
$redirecturl->param('id', $forumid);
@ -73,7 +79,11 @@ $PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('summarytitle', 'forumreport_summary', $forumname), 2, 'p-b-2');
$table = new \forumreport_summary\summary_table($courseid, $forumid);
$table->baseurl = $url;
$table->out($perpage, false);
// Render the report filters form.
$renderer = $PAGE->get_renderer('forumreport_summary');
echo $renderer->render_filters_form($cm, $url, $filters);
// Prepare and display the report.
echo $renderer->render_report($courseid, $url, $filters, $perpage);
echo $OUTPUT->footer();

View File

@ -24,6 +24,9 @@
$string['attachmentcount'] = 'Number of attachments';
$string['earliestpost'] = 'Earliest post';
$string['filter:groupsname'] = 'Groups';
$string['filter:groupscountall'] = 'Groups (all)';
$string['filter:groupscountnumber'] = 'Groups ({$a})';
$string['latestpost'] = 'Most recent post';
$string['nodetitle'] = 'Summary report';
$string['pluginname'] = 'Forum summary report';

View File

@ -0,0 +1,76 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides rendering functionality for the forum summary report subplugin.
*
* @package forumreport_summary
* @copyright 2019 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Renderer for the forum summary report.
*
* @copyright 2019 Michael Hawkins <michaelh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
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 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);
$templatecontext = $renderable->export_for_template($this);
return $this->render_from_template('forumreport_summary/filters', $templatecontext);
}
/**
* Render the summary report table.
*
* @param int $courseid ID of the course where the forum is located.
* @param string $url Base URL for the report page.
* @param array $filters Values of filters to be applied.
* @param int $perpage Number of results to render per page.
* @return string The report table HTML.
*/
public function render_report($courseid, $url, $filters, $perpage) {
// Initialise table.
$table = new \forumreport_summary\summary_table($courseid, $filters);
$table->baseurl = $url;
// Buffer so calling script can output the report as required.
ob_start();
// Render table.
$table->out($perpage, false);
$tablehtml = ob_get_contents();
ob_end_clean();
return $this->render_from_template('forumreport_summary/report', ['tablehtml' => $tablehtml, 'placeholdertext' => false]);
}
}

View File

@ -0,0 +1,81 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template forumreport_summary/filters
Summary report filters.
Example context (json):
{
"actionurl": "https://mymoodlesite.com/mod/forum/report/summary/index.php?courseid=2&forumid=2&perpage=50",
"hasgroups": true,
"filtergroupsname" : "Groups (all)",
"filtergroups": [
{
"gropuid": "1",
"groupname": "Group A",
"checked": true
},
{
"gropuid": "3",
"groupname": "Group C",
"checked": false
}
]
}
}}
<div class="p-b-3" data-report-id="{{uniqid}}">
<form id="filtersform" name="filtersform" method="post" action="{{actionurl}}">
<input type="hidden" name="submitted" value="true">
<div id="filtersbuttons">
{{#hasgroups}}
<button type="button" id="filter-groups-button" class="btn btn-primary filter-button">
{{filtergroupsname}}
</button>
{{! Start groups filter popover}}
<div id="filter-groups-popover" class="popover m-t-1 hidden">
<h3 class="filter-header">{{# str}} filter:groupsname, forumreport_summary {{/ str}}</h3>
<div class="filter-body">
<div class="form-check filter-scrollable">
{{#filtergroups}}
<input id="filtergroups{{groupid}}" class="form-check-input" type="checkbox" name="filtergroups[]"
value="{{groupid}}" {{#checked}} checked="checked" {{/checked}}>
<label class="form-check-label pt-1" for="filtergroups{{groupid}}">{{groupname}}</label>
<br>
{{/filtergroups}}
<br>
</div>
<a href="#"><span class="select-all">{{# str}} selectall {{/ str}}</span></a>
<div class="float-right">
<a href="#"><span class="filter-clear p-1">{{# str}} clear {{/ str}}</span></a>
<a href="#"><span class="filter-save"><strong>{{# str}} save {{/ str}}</strong></span></a>
</div>
</div>
</div>
{{! End groups filter popover}}
{{/hasgroups}}
</div>
</form>
</div>
{{#js}}
require(['forumreport_summary/filters'], function(Filters) {
Filters.init(document.querySelector("[data-report-id='{{uniqid}}']"));
});
{{/js}}

View File

@ -0,0 +1,39 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template forumreport_summary/report
Summary report filters.
Example context (json):
{
"placeholdertext": "To generate the summary report, set any filter values required, then select \"Generate report\".",
"tablehtml": false
}
}}
{{! The placeholder text, used before the report has been generated }}
{{^tablehtml}}<div class='d-block p-b-10'><h3>{{placeholdertext}}</h3></div>{{/tablehtml}}
{{#tablehtml}}
<div class="text-center">
{{> core/loading }}
</div>
<div id="summaryreport" class="hidden">
{{{tablehtml}}}
</div>
{{/tablehtml}}

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2019082000;
$plugin->version = 2019090200;
$plugin->requires = 2019071900;
$plugin->component = 'forumreport_summary';