MDL-66373 forumreport_summary: Introduced dates filter
authorMichael Hawkins <michaelh@moodle.com>
Fri, 6 Sep 2019 05:06:27 +0000 (13:06 +0800)
committerMichael Hawkins <michaelh@moodle.com>
Mon, 4 Nov 2019 02:04:57 +0000 (10:04 +0800)
Added the dates data filter, and refactored summaryreport_table to
handle passing filter content into the base SQL. Previously it was only
possible to add filter content into a separate part of the query.

19 files changed:
mod/forum/report/summary/amd/build/filters.min.js
mod/forum/report/summary/amd/build/filters.min.js.map
mod/forum/report/summary/amd/build/selectors.min.js
mod/forum/report/summary/amd/build/selectors.min.js.map
mod/forum/report/summary/amd/src/filters.js
mod/forum/report/summary/amd/src/selectors.js
mod/forum/report/summary/classes/form/dates_filter_form.php [new file with mode: 0644]
mod/forum/report/summary/classes/output/filters.php
mod/forum/report/summary/classes/summary_table.php
mod/forum/report/summary/index.php
mod/forum/report/summary/lang/en/forumreport_summary.php
mod/forum/report/summary/templates/filter_dates.mustache [new file with mode: 0644]
mod/forum/report/summary/templates/filter_dates_popover.mustache [new file with mode: 0644]
mod/forum/report/summary/templates/filter_groups.mustache [new file with mode: 0644]
mod/forum/report/summary/templates/filters.mustache
mod/forum/report/summary/version.php
theme/boost/scss/moodle/modules.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css

index 6618d3f..25eb61a 100644 (file)
Binary files a/mod/forum/report/summary/amd/build/filters.min.js and b/mod/forum/report/summary/amd/build/filters.min.js differ
index 69e1fba..54c31d6 100644 (file)
Binary files a/mod/forum/report/summary/amd/build/filters.min.js.map and b/mod/forum/report/summary/amd/build/filters.min.js.map differ
index 320b8f5..ca815cd 100644 (file)
Binary files a/mod/forum/report/summary/amd/build/selectors.min.js and b/mod/forum/report/summary/amd/build/selectors.min.js differ
index 2fc0418..88ee010 100644 (file)
Binary files a/mod/forum/report/summary/amd/build/selectors.min.js.map and b/mod/forum/report/summary/amd/build/selectors.min.js.map differ
index 9ee20d8..55fbf4a 100644 (file)
@@ -26,6 +26,8 @@ import $ from 'jquery';
 import Popper from 'core/popper';
 import CustomEvents from 'core/custom_interaction_events';
 import Selectors from 'forumreport_summary/selectors';
+import Y from 'core/yui';
+import Ajax from 'core/ajax';
 
 export const init = (root) => {
     let jqRoot = $(root);
@@ -40,8 +42,8 @@ export const init = (root) => {
     // Generic filter handlers.
 
     // Called to override click event to trigger a proper generate request with filtering.
-    var generateWithFilters = (event) => {
-        var newLink = $('#filtersform').attr('action');
+    const generateWithFilters = (event) => {
+        let newLink = $('#filtersform').attr('action');
 
         if (event) {
             event.preventDefault();
@@ -70,7 +72,7 @@ export const init = (root) => {
     });
 
     // Submit report via filter
-    var submitWithFilter = (containerelement) => {
+    const submitWithFilter = (containerelement) => {
         // Close the container (eg popover).
         $(containerelement).addClass('hidden');
 
@@ -78,6 +80,25 @@ export const init = (root) => {
         generateWithFilters(false);
     };
 
+    // Use popper to override date mform calendar position.
+    const updateCalendarPosition = (referenceid) => {
+        let referenceElement = document.querySelector(referenceid),
+            popperContent = document.querySelector(Selectors.filters.date.calendar);
+
+        popperContent.style.removeProperty("z-index");
+        new Popper(referenceElement, popperContent, {placement: 'bottom'});
+    };
+
+    // Call when opening filter to ensure only one can be activated.
+    const canOpenFilter = (event) => {
+        if (document.querySelector('[data-openfilter="true"]')) {
+            return false;
+        }
+
+        event.target.setAttribute('data-openfilter', "true");
+        return true;
+    };
+
     // Groups filter specific handlers.
 
     // Event handler for clicking select all groups.
@@ -98,9 +119,13 @@ export const init = (root) => {
     });
 
     // Event handler for showing groups filter popover.
-    jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.trigger, function() {
+    jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.trigger, function(event) {
+        if (!canOpenFilter(event)) {
+            return false;
+        }
+
         // Create popover.
-        var referenceElement = root.querySelector(Selectors.filters.group.trigger),
+        let referenceElement = root.querySelector(Selectors.filters.group.trigger),
             popperContent = root.querySelector(Selectors.filters.group.popover);
 
         new Popper(referenceElement, popperContent, {placement: 'bottom'});
@@ -114,10 +139,128 @@ export const init = (root) => {
 
         // Let screen readers know that it's now expanded.
         referenceElement.setAttribute('aria-expanded', true);
+        return true;
     });
 
     // Event handler to click save groups filter.
     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.save, function() {
         submitWithFilter('#filter-groups-popover');
     });
+
+    // Dates filter specific handlers.
+
+   // Event handler for showing dates filter popover.
+    jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.trigger, function(event) {
+        if (!canOpenFilter(event)) {
+            return false;
+        }
+
+        // Create popover.
+        let referenceElement = root.querySelector(Selectors.filters.date.trigger),
+            popperContent = root.querySelector(Selectors.filters.date.popover);
+
+        new Popper(referenceElement, popperContent, {placement: 'bottom'});
+
+        // Show popover and move focus.
+        popperContent.classList.remove('hidden');
+        popperContent.querySelector('[name="filterdatefrompopover[enabled]"]').focus();
+
+        // Change to outlined button.
+        referenceElement.classList.add('btn-outline-primary');
+        referenceElement.classList.remove('btn-primary');
+
+        // Let screen readers know that it's now expanded.
+        referenceElement.setAttribute('aria-expanded', true);
+        return true;
+    });
+
+    // Event handler to save dates filter.
+    jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.save, function() {
+        // Populate the hidden form inputs to submit the data.
+        let filtersForm = document.forms.filtersform;
+        const datesPopover = root.querySelector(Selectors.filters.date.popover);
+        const fromEnabled = datesPopover.querySelector('[name="filterdatefrompopover[enabled]"]').checked ? 1 : 0;
+        const toEnabled = datesPopover.querySelector('[name="filterdatetopopover[enabled]"]').checked ? 1 : 0;
+
+        // Disable the mform checker to prevent unsubmitted form warning to the user when closing the popover.
+        Y.use('moodle-core-formchangechecker', function() {
+            M.core_formchangechecker.reset_form_dirty_state();
+        });
+
+        if (!fromEnabled && !toEnabled) {
+            // Update the elements in the filter form.
+            filtersForm.elements['datefrom[timestamp]'].value = 0;
+            filtersForm.elements['datefrom[enabled]'].value = fromEnabled;
+            filtersForm.elements['dateto[timestamp]'].value = 0;
+            filtersForm.elements['dateto[enabled]'].value = toEnabled;
+
+            // Submit the filter values and re-generate report.
+            submitWithFilter('#filter-dates-popover');
+        } else {
+            let args = {data: []};
+
+            if (fromEnabled) {
+                args.data.push({
+                    'key': 'from',
+                    'year': datesPopover.querySelector('[name="filterdatefrompopover[year]"]').value,
+                    'month': datesPopover.querySelector('[name="filterdatefrompopover[month]"]').value,
+                    'day': datesPopover.querySelector('[name="filterdatefrompopover[day]"]').value,
+                    'hour': 0,
+                    'minute': 0
+                });
+            }
+
+            if (toEnabled) {
+                args.data.push({
+                    'key': 'to',
+                    'year': datesPopover.querySelector('[name="filterdatetopopover[year]"]').value,
+                    'month': datesPopover.querySelector('[name="filterdatetopopover[month]"]').value,
+                    'day': datesPopover.querySelector('[name="filterdatetopopover[day]"]').value,
+                    'hour': 23,
+                    'minute': 59
+                });
+            }
+
+            const request = {
+                methodname: 'core_calendar_get_timestamps',
+                args: args
+            };
+
+            Ajax.call([request])[0].done(function(result) {
+                let fromTimestamp = 0,
+                    toTimestamp = 0;
+
+                result['timestamps'].forEach(function(data){
+                    if (data.key === 'from') {
+                        fromTimestamp = data.timestamp;
+                    } else if (data.key === 'to') {
+                        toTimestamp = data.timestamp;
+                    }
+                });
+
+                // Display an error if the from date is later than the do date.
+                if (toTimestamp > 0 && fromTimestamp > toTimestamp) {
+                    const warningdiv = document.getElementById('dates-filter-warning');
+                    warningdiv.classList.remove('hidden');
+                    warningdiv.classList.add('d-block');
+                } else {
+                    filtersForm.elements['datefrom[timestamp]'].value = fromTimestamp;
+                    filtersForm.elements['datefrom[enabled]'].value = fromEnabled;
+                    filtersForm.elements['dateto[timestamp]'].value = toTimestamp;
+                    filtersForm.elements['dateto[enabled]'].value = toEnabled;
+
+                    // Submit the filter values and re-generate report.
+                    submitWithFilter('#filter-dates-popover');
+                }
+            });
+        }
+    });
+
+    jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconfrom, function() {
+        updateCalendarPosition(Selectors.filters.date.calendariconfrom);
+    });
+
+    jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconto, function() {
+        updateCalendarPosition(Selectors.filters.date.calendariconto);
+    });
 };
index df0c6db..553cf28 100644 (file)
@@ -31,6 +31,14 @@ export default {
             save: '[data-region="filter-groups"] .filter-save',
             selectall: '[data-region="filter-groups"] .select-all',
             trigger: '#filter-groups-button',
+        },
+        date: {
+            calendar: '#dateselector-calendar-panel',
+            calendariconfrom: '#id_filterdatefrompopover_calendar',
+            calendariconto: '#id_filterdatetopopover_calendar',
+            popover: '#filter-dates-popover',
+            save: '[data-region="filter-dates"] .filter-save',
+            trigger: '#filter-dates-button',
         }
     }
 };
diff --git a/mod/forum/report/summary/classes/form/dates_filter_form.php b/mod/forum/report/summary/classes/form/dates_filter_form.php
new file mode 100644 (file)
index 0000000..5d9f32e
--- /dev/null
@@ -0,0 +1,53 @@
+<?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/>.
+
+/**
+ * The mform used by the forum summary report dates filter.
+ *
+ * @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\form;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot.'/lib/formslib.php');
+
+/**
+ * The mform class for creating the forum summary report dates filter.
+ *
+ * @copyright 2019 Michael Hawkins <michaelh@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class dates_filter_form extends \moodleform {
+    /**
+     * The form definition.
+     *
+     */
+    public function definition() {
+        $attributes = [
+            'class' => 'align-items-center',
+        ];
+
+        // From date field.
+        $this->_form->addElement('date_selector', 'filterdatefrompopover', get_string('from'), ['optional' => true], $attributes);
+
+        // To date field.
+        $this->_form->addElement('date_selector', 'filterdatetopopover', get_string('to'), ['optional' => true], $attributes);
+    }
+}
index 1c6be47..60a7123 100644 (file)
@@ -29,6 +29,7 @@ use renderable;
 use renderer_base;
 use stdClass;
 use templatable;
+use forumreport_summary;
 
 defined('MOODLE_INTERNAL') || die();
 
@@ -69,6 +70,20 @@ class filters implements renderable, templatable {
      */
     protected $groupsselected = [];
 
+    /**
+     * HTML for dates filter.
+     *
+     * @var array $datesdata
+     */
+    protected $datesdata = [];
+
+    /**
+     * Text to display on the dates filter button.
+     *
+     * @var string $datesbuttontext
+     */
+    protected $datesbuttontext;
+
     /**
      * Builds renderable filter data.
      *
@@ -84,6 +99,11 @@ class filters implements renderable, templatable {
         // Prepare groups filter data.
         $groupsdata = $filterdata['groups'] ?? [];
         $this->prepare_groups_data($groupsdata);
+
+        // Prepare dates filter data.
+        $datefromdata = $filterdata['datefrom'] ?? [];
+        $datetodata = $filterdata['dateto'] ?? [];
+        $this->prepare_dates_data($datefromdata, $datetodata);
     }
 
     /**
@@ -129,6 +149,78 @@ class filters implements renderable, templatable {
         $this->groupsselected = $groupsselected;
     }
 
+    /**
+     * Prepares from date, to date and button text.
+     * Empty data will default to a disabled filter with today's date.
+     *
+     * @param array $datefromdata From date selected for filtering, and whether the filter is enabled.
+     * @param array $datetodata To date selected for filtering, and whether the filter is enabled.
+     * @return void.
+     */
+    private function prepare_dates_data(array $datefromdata, array $datetodata): void {
+        $timezone = \core_date::get_user_timezone_object();
+        $calendartype = \core_calendar\type_factory::get_calendar_instance();
+        $timestamptoday = time();
+        $datetoday  = $calendartype->timestamp_to_date_array($timestamptoday, $timezone);
+
+        // Prepare date/enabled data.
+        if (empty($datefromdata['enabled'])) {
+            $fromdate = $datetoday;
+            $fromtimestamp = $timestamptoday;
+            $fromenabled = false;
+        } else {
+            $fromdate = $calendartype->timestamp_to_date_array($datefromdata['timestamp'], $timezone);
+            $fromtimestamp = $datefromdata['timestamp'];
+            $fromenabled = true;
+        }
+
+        if (empty($datetodata['enabled'])) {
+            $todate = $datetoday;
+            $totimestamp = $timestamptoday;
+            $toenabled = false;
+        } else {
+            $todate = $calendartype->timestamp_to_date_array($datetodata['timestamp'], $timezone);
+            $totimestamp = $datetodata['timestamp'];
+            $toenabled = true;
+        }
+
+        $this->datesdata = [
+            'from' => [
+                'day'       => $fromdate['mday'],
+                'month'     => $fromdate['mon'],
+                'year'      => $fromdate['year'],
+                'timestamp' => $fromtimestamp,
+                'enabled'   => $fromenabled,
+            ],
+            'to' => [
+                'day'       => $todate['mday'],
+                'month'     => $todate['mon'],
+                'year'      => $todate['year'],
+                'timestamp' => $totimestamp,
+                'enabled'   => $toenabled,
+            ],
+        ];
+
+        // Prepare button string data.
+        $displayformat = get_string('strftimedatemonthabbr', 'langconfig');
+        $fromdatestring = $calendartype->timestamp_to_date_string($fromtimestamp, $displayformat, $timezone, true, true);
+        $todatestring = $calendartype->timestamp_to_date_string($totimestamp, $displayformat, $timezone, true, true);
+
+        if ($fromenabled && $toenabled) {
+            $datestrings = [
+                'datefrom' => $fromdatestring,
+                'dateto'   => $todatestring,
+            ];
+            $this->datesbuttontext = get_string('filter:datesfromto', 'forumreport_summary', $datestrings);
+        } else if ($fromenabled) {
+            $this->datesbuttontext = get_string('filter:datesfrom', 'forumreport_summary', $fromdatestring);
+        } else if ($toenabled) {
+            $this->datesbuttontext = get_string('filter:datesto', 'forumreport_summary', $todatestring);
+        } else {
+            $this->datesbuttontext = get_string('filter:datesname', 'forumreport_summary');
+        }
+    }
+
     /**
      * Export data for use as the context of a mustache template.
      *
@@ -171,6 +263,33 @@ class filters implements renderable, templatable {
             $output->hasgroups = false;
         }
 
+        // Set date button and generate dates popover mform.
+        $datesformdata = [];
+
+        if ($this->datesdata['from']['enabled']) {
+            $datesformdata['filterdatefrompopover'] = $this->datesdata['from'];
+        }
+
+        if ($this->datesdata['to']['enabled']) {
+            $datesformdata['filterdatetopopover'] = $this->datesdata['to'];
+        }
+
+        $output->filterdatesname = $this->datesbuttontext;
+        $datesform = new forumreport_summary\form\dates_filter_form();
+        $datesform->set_data($datesformdata);
+        $output->filterdatesform = $datesform->render();
+
+         // Set dates filter data within filters form.
+        $disableddate = [
+            'day' => '',
+            'month' => '',
+            'year' => '',
+            'enabled' => '0',
+        ];
+        $datefromdata = ['type' => 'from'] + ($this->datesdata['from']['enabled'] ? $this->datesdata['from'] : $disableddate);
+        $datetodata = ['type' => 'to'] + ($this->datesdata['to']['enabled'] ? $this->datesdata['to'] : $disableddate);
+        $output->filterdatesdata = [$datefromdata, $datetodata];
+
         return $output;
     }
 }
index a6d565c..3032c49 100644 (file)
@@ -44,6 +44,9 @@ class summary_table extends table_sql {
     /** Groups filter type */
     const FILTER_GROUPS = 2;
 
+    /** Dates filter type */
+    const FILTER_DATES = 3;
+
     /** Table to store summary data extracted from the log table */
     const LOG_SUMMARY_TEMP_TABLE = 'forum_report_summary_counts';
 
@@ -146,23 +149,26 @@ class summary_table extends table_sql {
         // Define configs.
         $this->define_table_configs();
 
-        // Define the basic SQL data and object format.
-        $this->define_base_sql();
-
         // Apply relevant filters.
+        $this->define_base_filter_sql();
         $this->apply_filters($filters);
+
+        // Define the basic SQL data and object format.
+        $this->define_base_sql();
     }
 
     /**
-     * Provides the string name of each filter type.
+     * 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.
      *
      * @param int $filtertype Type of filter
      * @return string Name of the filter
      */
-    public function get_filter_name(int $filtertype): string {
+    protected function get_filter_name(int $filtertype): string {
         $filternames = [
             self::FILTER_FORUM => 'Forum',
             self::FILTER_GROUPS => 'Groups',
+            self::FILTER_DATES => 'Dates',
         ];
 
         return $filternames[$filtertype];
@@ -362,6 +368,38 @@ class summary_table extends table_sql {
 
                 break;
 
+            case self::FILTER_DATES:
+                if (!isset($values['from']['enabled']) || !isset($values['to']['enabled']) ||
+                        ($values['from']['enabled'] && !isset($values['from']['timestamp'])) ||
+                        ($values['to']['enabled'] && !isset($values['to']['timestamp']))) {
+                    $paramcounterror = true;
+                } else {
+                    $this->sql->filterbase['dates'] = '';
+                    $this->sql->filterbase['dateslog'] = '';
+                    $this->sql->filterbase['dateslogparams'] = [];
+
+                    // From date.
+                    if ($values['from']['enabled']) {
+                        // If the filter was enabled, include the date restriction.
+                        // Needs to form part of the base join to posts, so will be injected by define_base_sql().
+                        $this->sql->filterbase['dates'] .= " AND p.created >= :fromdate";
+                        $this->sql->params['fromdate'] = $values['from']['timestamp'];
+                        $this->sql->filterbase['dateslog'] .= ' AND timecreated >= :fromdate';
+                        $this->sql->filterbase['dateslogparams']['fromdate'] = $values['from']['timestamp'];
+                    }
+
+                    // To date.
+                    if ($values['to']['enabled']) {
+                        // If the filter was enabled, include the date restriction.
+                        // Needs to form part of the base join to posts, so will be injected by define_base_sql().
+                        $this->sql->filterbase['dates'] .= " AND p.created <= :todate";
+                        $this->sql->params['todate'] = $values['to']['timestamp'];
+                        $this->sql->filterbase['dateslog'] .= ' AND timecreated <= :todate';
+                        $this->sql->filterbase['dateslogparams']['todate'] = $values['to']['timestamp'];
+                    }
+                }
+
+                break;
             default:
                 throw new coding_exception("Report filter type '{$filtertype}' not found.");
                 break;
@@ -385,6 +423,8 @@ class summary_table extends table_sql {
         $this->is_downloadable(true);
         $this->no_sorting('select');
         $this->set_attribute('id', 'forumreport_summary_table');
+        $this->sql = new \stdClass();
+        $this->sql->params = [];
     }
 
     /**
@@ -395,8 +435,6 @@ class summary_table extends table_sql {
     protected function define_base_sql(): void {
         global $USER;
 
-        $this->sql = new \stdClass();
-
         $userfields = get_extra_user_fields($this->context);
         $userfieldssql = \user_picture::fields('u', $userfields);
 
@@ -429,7 +467,8 @@ class summary_table extends table_sql {
                                     JOIN {forum_discussions} d ON d.forum = f.id
                                LEFT JOIN {forum_posts} p ON p.discussion =  d.id
                                      AND p.userid = ue.userid
-                                     ' . $privaterepliessql . '
+                                     ' . $privaterepliessql
+                                       . $this->sql->filterbase['dates'] . '
                                LEFT JOIN (
                                             SELECT COUNT(fi.id) AS attcount, fi.itemid AS postid, fi.userid
                                               FROM {files} fi
@@ -457,7 +496,7 @@ class summary_table extends table_sql {
             $this->sql->basefields .= ', SUM(CASE WHEN p.charcount IS NOT NULL THEN p.charcount ELSE 0 END) AS charcount';
         }
 
-        $this->sql->params = [
+        $this->sql->params += [
             'component' => 'mod_forum',
             'courseid' => $this->cm->course,
         ] + $privaterepliesparams;
@@ -467,7 +506,14 @@ class summary_table extends table_sql {
             $this->sql->basewhere .= ' AND ue.userid = :userid';
             $this->sql->params['userid'] = $this->userid;
         }
+    }
 
+    /**
+     * Instantiate the properties to store filter values.
+     *
+     * @return void.
+     */
+    protected function define_base_filter_sql(): void {
         // Filter values will be populated separately where required.
         $this->sql->filterfields = '';
         $this->sql->filterfromjoins = '';
@@ -533,6 +579,13 @@ class summary_table extends table_sql {
 
         // Apply groups filter.
         $this->add_filter(self::FILTER_GROUPS, $filters['groups']);
+
+        // Apply dates filter.
+        $datevalues = [
+            'from' => $filters['datefrom'],
+            'to' => $filters['dateto'],
+        ];
+        $this->add_filter(self::FILTER_DATES, $datevalues);
     }
 
     /**
@@ -614,11 +667,17 @@ class summary_table extends table_sql {
             $logtable = $this->logreader->get_internal_log_table_name();
             $nonanonymous = 'AND anonymous = 0';
         }
-        $params = ['contextid' => $contextid];
+
+        // Apply dates filter if applied.
+        $datewhere = $this->sql->filterbase['dateslog'] ?? '';
+        $dateparams = $this->sql->filterbase['dateslogparams'] ?? [];
+
+        $params = ['contextid' => $contextid] + $dateparams;
         $sql = "INSERT INTO {" . self::LOG_SUMMARY_TEMP_TABLE . "} (userid, viewcount)
                      SELECT userid, COUNT(*) AS viewcount
                        FROM {" . $logtable . "}
                       WHERE contextid = :contextid
+                            $datewhere
                             $nonanonymous
                    GROUP BY userid";
         $DB->execute($sql, $params);
index 9b8e828..9872fc9 100644 (file)
@@ -36,6 +36,8 @@ $filters = [];
 // 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);
 
index 50d1068..71bb442 100644 (file)
@@ -26,6 +26,12 @@ $string['attachmentcount'] = 'Number of attachments';
 $string['charcount'] = 'Character count';
 $string['viewcount'] = 'Number of views';
 $string['earliestpost'] = 'Earliest post';
+$string['filter:datesbuttonlabel'] = 'Open the dates filter';
+$string['filter:datesname'] = 'Dates';
+$string['filter:datesfrom'] = 'From {$a}';
+$string['filter:datesfromto'] = '{$a->datefrom} - {$a->dateto}';
+$string['filter:datesorderwarning'] = 'Please ensure the "From" date selected is earlier than the "To" date selected.';
+$string['filter:datesto'] = 'To {$a}';
 $string['filter:groupsbuttonlabel'] = 'Open the groups filter';
 $string['filter:groupsname'] = 'Groups';
 $string['filter:groupscountall'] = 'Groups (all)';
diff --git a/mod/forum/report/summary/templates/filter_dates.mustache b/mod/forum/report/summary/templates/filter_dates.mustache
new file mode 100644 (file)
index 0000000..d39c897
--- /dev/null
@@ -0,0 +1,50 @@
+{{!
+    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/filter_dates
+
+    Summary report dates filter.
+
+    Example context (json):
+    {
+        "filterdatesname": "Dates",
+        "filterdatesdata": [
+            {
+                "type": "from",
+                "timestamp": "1571377510",
+                "enabled": "1"
+            },
+            {
+                "type": "to",
+                "timestamp": "1571377510",
+                "enabled": "1"
+            }
+        ]
+    }
+}}
+
+
+<button type="button" id="filter-dates-button" class="btn btn-primary rounded p-1" aria-expanded="false"
+        aria-haspopup="true" aria-label="{{# str}} filter:datesbuttonlabel, forumreport_summary {{/ str}}">
+    {{filterdatesname}}
+</button>
+
+{{! Hidden dates fields to populate from visible mform in popover. }}
+{{#filterdatesdata}}
+<input type="hidden" name="date{{type}}[timestamp]" value="{{timestamp}}">
+<input type="hidden" name="date{{type}}[enabled]" value="{{enabled}}">
+{{/filterdatesdata}}
diff --git a/mod/forum/report/summary/templates/filter_dates_popover.mustache b/mod/forum/report/summary/templates/filter_dates_popover.mustache
new file mode 100644 (file)
index 0000000..a95b450
--- /dev/null
@@ -0,0 +1,40 @@
+{{!
+    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/filter_dates
+
+    Summary report dates filter.
+
+    Example context (json):
+    {
+        "filterdatesform": "<div>HTML for date filters</div>"
+    }
+}}
+
+<div id="filter-dates-popover" class="popover filter-dates-popover mt-3 hidden">
+    <h3 class="popover-header">{{# str}} filter:datesname, forumreport_summary {{/ str}}</h3>
+    <div class="popover-body" data-region="filter-dates">
+        {{{filterdatesform}}}
+        <div id="dates-filter-warning" class="form-control-feedback text-danger hidden float-right">
+            {{# str}} filter:datesorderwarning, forumreport_summary {{/ str}}
+        </div>
+        <br>&nbsp;
+        <button type="button" class="filter-save float-right btn btn-link p-0" aria-label="{{# str}} save {{/ str}}">
+            <strong>{{# str}} save {{/ str}}</strong>
+        </button>
+    </div>
+</div>
diff --git a/mod/forum/report/summary/templates/filter_groups.mustache b/mod/forum/report/summary/templates/filter_groups.mustache
new file mode 100644 (file)
index 0000000..939ed55
--- /dev/null
@@ -0,0 +1,70 @@
+{{!
+    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/filter_groups
+
+    Summary report groups filter.
+
+    Example context (json):
+    {
+        "hasgroups": true,
+        "filtergroupsname" : "Groups (all)",
+        "filtergroups": [
+            {
+                "groupid": "1",
+                "groupname": "Group A",
+                "checked": true
+            },
+            {
+                "groupid": "3",
+                "groupname": "Group C",
+                "checked": false
+            }
+        ]
+    }
+}}
+
+{{#hasgroups}}
+<button type="button" id="filter-groups-button" class="btn btn-primary rounded p-1 ml-2" aria-expanded="false"
+        aria-haspopup="true" aria-label="{{# str}} filter:groupsbuttonlabel, forumreport_summary {{/ str}}">
+    {{filtergroupsname}}
+</button>
+
+{{! Groups filter popover }}
+<div id="filter-groups-popover" class="popover m-t-1 hidden">
+    <h3 class="popover-header">{{# str}} filter:groupsname, forumreport_summary {{/ str}}</h3>
+    <div class="popover-body" data-region="filter-groups">
+        <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}}
+        </div>
+        <div class="filter-actions">
+            <button type="button" class="select-all btn btn-link p-0 pr-1" aria-label="{{# str}} selectall {{/ str}}">{{# str}} selectall {{/ str}}</button>
+            <div class="float-right">
+                <button type="button" class="filter-clear btn btn-link p-0 px-1" aria-label="{{# str}} clear {{/ str}}">{{# str}} clear {{/ str}}</button>
+                <button type="button" class="filter-save btn btn-link p-0" aria-label="{{# str}} save {{/ str}}">
+                    <strong>{{# str}} save {{/ str}}</strong>
+                </button>
+            </div>
+        </div>
+    </div>
+</div>
+{{/hasgroups}}
index 211546c..f4dd7fb 100644 (file)
                 "groupname": "Group C",
                 "checked": false
             }
+        ],
+        "filterdatesname": "Dates",
+        "filterdatesform": "<div>HTML for date filters</div>",
+        "filterdatesdata": [
+            {
+                "type": "from",
+                "timestamp": "510969600",
+                "enabled": "1"
+            },
+            {
+                "type": "to",
+                "timestamp": "725673600",
+                "enabled": "1"
+            }
         ]
     }
 }}
 
-<div class="p-b-3" data-report-id="{{uniqid}}">
+<div class="pb-4" 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 rounded p-1" aria-expanded="false"
-                    aria-haspopup="true" aria-label="{{# str}} filter:groupsbuttonlabel, forumreport_summary {{/ str}}">
-                {{filtergroupsname}}
-            </button>
 
-            {{! Start groups filter popover}}
-            <div id="filter-groups-popover" class="popover m-t-1 hidden">
-                <h3 class="popover-header">{{# str}} filter:groupsname, forumreport_summary {{/ str}}</h3>
-                <div class="popover-body" data-region="filter-groups">
-                    <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}}
-                    </div>
-                    <div class="filter-actions">
-                        <button type="button" class="select-all btn btn-link p-0 pr-1" aria-label="{{# str}} selectall {{/ str}}">{{# str}} selectall {{/ str}}</button>
-                        <div class="float-right">
-                            <button type="button" class="filter-clear btn btn-link p-0 px-1" aria-label="{{# str}} clear {{/ str}}">{{# str}} clear {{/ str}}</button>
-                            <button type="button" class="filter-save btn btn-link p-0" aria-label="{{# str}} save {{/ str}}">
-                                <strong>{{# str}} save {{/ str}}</strong>
-                            </button>
-                        </div>
-                    </div>
-                </div>
-            </div>
-            {{! End groups filter popover}}
-            {{/hasgroups}}
+        <div id="filtersbuttons">
+            {{> forumreport_summary/filter_dates}}
+            {{> forumreport_summary/filter_groups}}
         </div>
     </form>
+
+    {{! Dates filter popover - mform must exist outside of the filtersform }}
+    {{> forumreport_summary/filter_dates_popover}}
 </div>
 
 {{#js}}
index d695f93..5a0c84e 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version  = 2019090200;
+$plugin->version  = 2019101800;
 $plugin->requires = 2019071900;
 $plugin->component = 'forumreport_summary';
index 852b709..0a9f29e 100644 (file)
@@ -390,6 +390,16 @@ $author-image-margin-sm: 8px;
     margin-bottom: 1em;
 }
 
+// Required to fit a date mform into a filter popover.
+.filter-dates-popover {
+    width: 100%;
+    max-width: 41.5em;
+
+    .mform {
+        margin-left: -3em;
+    }
+}
+
 // End styling for mod_forum.
 
 .maincalendar .calendarmonth td,
index 741ae8f..ba6cc6b 100644 (file)
@@ -16387,6 +16387,12 @@ select {
   max-height: 25em;
   margin-bottom: 1em; }
 
+.filter-dates-popover {
+  width: 100%;
+  max-width: 41.5em; }
+  .filter-dates-popover .mform {
+    margin-left: -3em; }
+
 .maincalendar .calendarmonth td,
 .maincalendar .calendarmonth th {
   border: 1px dotted #dee2e6; }
index 6620b42..35e8ed2 100644 (file)
@@ -16659,6 +16659,12 @@ select {
   max-height: 25em;
   margin-bottom: 1em; }
 
+.filter-dates-popover {
+  width: 100%;
+  max-width: 41.5em; }
+  .filter-dates-popover .mform {
+    margin-left: -3em; }
+
 .maincalendar .calendarmonth td,
 .maincalendar .calendarmonth th {
   border: 1px dotted #dee2e6; }