diff --git a/calendar/externallib.php b/calendar/externallib.php index eccd04eb896..d7f83479954 100644 --- a/calendar/externallib.php +++ b/calendar/externallib.php @@ -1371,4 +1371,82 @@ class core_calendar_external extends external_api { ] ); } + + /** + * Convert the specified dates into unix timestamps. + * + * @param array $datetimes Array of arrays containing date time details, each in the format: + * ['year' => a, 'month' => b, 'day' => c, + * 'hour' => d (optional), 'minute' => e (optional), 'key' => 'x' (optional)] + * @return array Provided array of dates converted to unix timestamps + * @throws moodle_exception If one or more of the dates provided does not convert to a valid timestamp. + */ + public static function get_timestamps($datetimes) { + $params = self::validate_parameters(self::get_timestamps_parameters(), ['data' => $datetimes]); + + $type = \core_calendar\type_factory::get_calendar_instance(); + $timestamps = ['timestamps' => []]; + + foreach ($params['data'] as $key => $datetime) { + $hour = $datetime['hour'] ?? 0; + $minute = $datetime['minute'] ?? 0; + + try { + $timestamp = $type->convert_to_timestamp( + $datetime['year'], $datetime['month'], $datetime['day'], $hour, $minute); + + $timestamps['timestamps'][] = [ + 'key' => $datetime['key'] ?? $key, + 'timestamp' => $timestamp, + ]; + + } catch (Exception $e) { + throw new moodle_exception('One or more of the dates provided were invalid'); + } + } + + return $timestamps; + } + + /** + * Describes the parameters for get_timestamps. + * + * @return external_function_parameters + */ + public static function get_timestamps_parameters() { + return new external_function_parameters ([ + 'data' => new external_multiple_structure( + new external_single_structure( + [ + 'key' => new external_value(PARAM_ALPHANUMEXT, 'key', VALUE_OPTIONAL), + 'year' => new external_value(PARAM_INT, 'year'), + 'month' => new external_value(PARAM_INT, 'month'), + 'day' => new external_value(PARAM_INT, 'day'), + 'hour' => new external_value(PARAM_INT, 'hour', VALUE_OPTIONAL), + 'minute' => new external_value(PARAM_INT, 'minute', VALUE_OPTIONAL), + ] + ) + ) + ]); + } + + /** + * Describes the timestamps return format. + * + * @return external_single_structure + */ + public static function get_timestamps_returns() { + return new external_single_structure( + [ + 'timestamps' => new external_multiple_structure( + new external_single_structure( + [ + 'key' => new external_value(PARAM_ALPHANUMEXT, 'Timestamp key'), + 'timestamp' => new external_value(PARAM_INT, 'Unix timestamp'), + ] + ) + ) + ] + ); + } } diff --git a/calendar/tests/externallib_test.php b/calendar/tests/externallib_test.php index d3a7cf9e779..810c25fdd20 100644 --- a/calendar/tests/externallib_test.php +++ b/calendar/tests/externallib_test.php @@ -2696,4 +2696,87 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { ); $this->assertEquals(['user', 'course', 'group'], $data['allowedeventtypes']); } + + /** + * Test get_timestamps with string keys, with and without optional hour/minute values. + */ + public function test_get_timestamps_string_keys() { + $this->resetAfterTest(true); + $this->setAdminUser(); + + $time1 = new DateTime('2018-12-30 00:00:00'); + $time2 = new DateTime('2019-03-27 23:59:00'); + + $dates = [ + [ + 'key' => 'from', + 'year' => $time1->format('Y'), + 'month' => $time1->format('m'), + 'day' => $time1->format('d'), + ], + [ + 'key' => 'to', + 'year' => $time2->format('Y'), + 'month' => (int) $time2->format('m'), + 'day' => $time2->format('d'), + 'hour' => $time2->format('H'), + 'minute' => $time2->format('i'), + ], + ]; + + $expectedtimestamps = [ + 'from' => $time1->getTimestamp(), + 'to' => $time2->getTimestamp(), + ]; + + $result = core_calendar_external::get_timestamps($dates); + + $this->assertEquals(['timestamps'], array_keys($result)); + $this->assertEquals(2, count($result['timestamps'])); + + foreach ($result['timestamps'] as $data) { + $this->assertTrue(in_array($data['key'], ['from', 'to'])); + $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']); + } + } + + /** + * Test get_timestamps with no keys specified, with and without optional hour/minute values. + */ + public function test_get_timestamps_no_keys() { + $this->resetAfterTest(true); + $this->setAdminUser(); + + $time1 = new DateTime('2018-12-30 00:00:00'); + $time2 = new DateTime('2019-03-27 23:59:00'); + + $dates = [ + [ + 'year' => $time1->format('Y'), + 'month' => $time1->format('m'), + 'day' => $time1->format('d'), + ], + [ + 'year' => $time2->format('Y'), + 'month' => (int) $time2->format('m'), + 'day' => $time2->format('d'), + 'hour' => $time2->format('H'), + 'minute' => $time2->format('i'), + ], + ]; + + $expectedtimestamps = [ + 0 => $time1->getTimestamp(), + 1 => $time2->getTimestamp(), + ]; + + $result = core_calendar_external::get_timestamps($dates); + + $this->assertEquals(['timestamps'], array_keys($result)); + $this->assertEquals(2, count($result['timestamps'])); + + foreach ($result['timestamps'] as $data) { + $this->assertEquals($expectedtimestamps[$data['key']], $data['timestamp']); + } + } } diff --git a/calendar/upgrade.txt b/calendar/upgrade.txt index 7bfbeb1a4e5..2f4273a7a98 100644 --- a/calendar/upgrade.txt +++ b/calendar/upgrade.txt @@ -9,6 +9,8 @@ information provided here is intended especially for developers. * calendar_cron() * calendar_get_mini() * calendar_get_upcoming() +* Added core_calendar_external::get_timestamps(), which allows an array containing an arbitrary number of arrays of + date/time data to be converted and returned as timestamps, along with an optional key. === 3.6 === * calendar_get_default_courses() function now has optional $userid parameter. diff --git a/lib/db/services.php b/lib/db/services.php index 0076942d698..15adc29f627 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -261,6 +261,14 @@ $functions = array( 'type' => 'read', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_calendar_get_timestamps' => [ + 'classname' => 'core_calendar_external', + 'methodname' => 'get_timestamps', + 'description' => 'Fetch unix timestamps for given date times.', + 'classpath' => 'calendar/externallib.php', + 'type' => 'read', + 'ajax' => true, + ], 'core_cohort_add_cohort_members' => array( 'classname' => 'core_cohort_external', 'methodname' => 'add_cohort_members', diff --git a/mod/forum/report/summary/amd/build/filters.min.js b/mod/forum/report/summary/amd/build/filters.min.js index 6618d3f162a..25eb61aa07a 100644 --- a/mod/forum/report/summary/amd/build/filters.min.js +++ b/mod/forum/report/summary/amd/build/filters.min.js @@ -1,2 +1,2 @@ -define ("forumreport_summary/filters",["exports","jquery","core/popper","core/custom_interaction_events","forumreport_summary/selectors"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=f(b);c=f(c);d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}var g=function(a){var f=(0,b.default)(a);(0,b.default)(document).ready(function(){(0,b.default)(".loading-icon").hide();(0,b.default)("#summaryreport").removeClass("hidden")});var g=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){g(a)});(0,b.default)("thead").on("click","a",function(a){g(a)});(0,b.default)(".pagination").on("click","a",function(a){g(a)});var h=function(a){(0,b.default)(a).addClass("hidden");g(!1)};f.on(d.default.events.activate,e.default.filters.group.selectall,function(){var b=a.querySelectorAll(e.default.filters.group.checkbox+":not(:checked)");b.forEach(function(a){a.checked=!0})});f.on(d.default.events.activate,e.default.filters.group.clear,function(){var b=a.querySelectorAll(e.default.filters.group.checkbox+":checked");b.forEach(function(a){a.checked=!1})});f.on(d.default.events.activate,e.default.filters.group.trigger,function(){var b=a.querySelector(e.default.filters.group.trigger),d=a.querySelector(e.default.filters.group.popover);new c.default(b,d,{placement:"bottom"});d.classList.remove("hidden");b.classList.add("btn-outline-primary");b.classList.remove("btn-primary");b.setAttribute("aria-expanded",!0)});f.on(d.default.events.activate,e.default.filters.group.save,function(){h("#filter-groups-popover")})};a.init=g}); +define ("forumreport_summary/filters",["exports","jquery","core/popper","core/custom_interaction_events","forumreport_summary/selectors","core/yui","core/ajax"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=h(b);c=h(c);d=h(d);e=h(e);f=h(f);g=h(g);function h(a){return a&&a.__esModule?a:{default:a}}var i=function(a){var h=(0,b.default)(a);(0,b.default)(document).ready(function(){(0,b.default)(".loading-icon").hide();(0,b.default)("#summaryreport").removeClass("hidden")});var i=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){i(a)});(0,b.default)("thead").on("click","a",function(a){i(a)});(0,b.default)(".pagination").on("click","a",function(a){i(a)});var j=function(a){(0,b.default)(a).addClass("hidden");i(!1)},k=function(a){var b=document.querySelector(a),d=document.querySelector(e.default.filters.date.calendar);d.style.removeProperty("z-index");new c.default(b,d,{placement:"bottom"})},l=function(a){if(document.querySelector("[data-openfilter=\"true\"]")){return!1}a.target.setAttribute("data-openfilter","true");return!0};h.on(d.default.events.activate,e.default.filters.group.selectall,function(){var b=a.querySelectorAll(e.default.filters.group.checkbox+":not(:checked)");b.forEach(function(a){a.checked=!0})});h.on(d.default.events.activate,e.default.filters.group.clear,function(){var b=a.querySelectorAll(e.default.filters.group.checkbox+":checked");b.forEach(function(a){a.checked=!1})});h.on(d.default.events.activate,e.default.filters.group.trigger,function(b){if(!l(b)){return!1}var d=a.querySelector(e.default.filters.group.trigger),f=a.querySelector(e.default.filters.group.popover);new c.default(d,f,{placement:"bottom"});f.classList.remove("hidden");d.classList.add("btn-outline-primary");d.classList.remove("btn-primary");d.setAttribute("aria-expanded",!0);return!0});h.on(d.default.events.activate,e.default.filters.group.save,function(){j("#filter-groups-popover")});h.on(d.default.events.activate,e.default.filters.date.trigger,function(b){if(!l(b)){return!1}var d=a.querySelector(e.default.filters.date.trigger),f=a.querySelector(e.default.filters.date.popover);new c.default(d,f,{placement:"bottom"});f.classList.remove("hidden");f.querySelector("[name=\"filterdatefrompopover[enabled]\"]").focus();d.classList.add("btn-outline-primary");d.classList.remove("btn-primary");d.setAttribute("aria-expanded",!0);return!0});h.on(d.default.events.activate,e.default.filters.date.save,function(){var b=document.forms.filtersform,c=a.querySelector(e.default.filters.date.popover),d=c.querySelector("[name=\"filterdatefrompopover[enabled]\"]").checked?1:0,h=c.querySelector("[name=\"filterdatetopopover[enabled]\"]").checked?1:0;f.default.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()});if(!d&&!h){b.elements["datefrom[timestamp]"].value=0;b.elements["datefrom[enabled]"].value=d;b.elements["dateto[timestamp]"].value=0;b.elements["dateto[enabled]"].value=h;j("#filter-dates-popover")}else{var i={data:[]};if(d){i.data.push({key:"from",year:c.querySelector("[name=\"filterdatefrompopover[year]\"]").value,month:c.querySelector("[name=\"filterdatefrompopover[month]\"]").value,day:c.querySelector("[name=\"filterdatefrompopover[day]\"]").value,hour:0,minute:0})}if(h){i.data.push({key:"to",year:c.querySelector("[name=\"filterdatetopopover[year]\"]").value,month:c.querySelector("[name=\"filterdatetopopover[month]\"]").value,day:c.querySelector("[name=\"filterdatetopopover[day]\"]").value,hour:23,minute:59})}g.default.call([{methodname:"core_calendar_get_timestamps",args:i}])[0].done(function(a){var c=0,e=0;a.timestamps.forEach(function(a){if("from"===a.key){c=a.timestamp}else if("to"===a.key){e=a.timestamp}});if(0e){var f=document.getElementById("dates-filter-warning");f.classList.remove("hidden");f.classList.add("d-block")}else{b.elements["datefrom[timestamp]"].value=c;b.elements["datefrom[enabled]"].value=d;b.elements["dateto[timestamp]"].value=e;b.elements["dateto[enabled]"].value=h;j("#filter-dates-popover")}})}});h.on(d.default.events.activate,e.default.filters.date.calendariconfrom,function(){k(e.default.filters.date.calendariconfrom)});h.on(d.default.events.activate,e.default.filters.date.calendariconto,function(){k(e.default.filters.date.calendariconto)})};a.init=i}); //# sourceMappingURL=filters.min.js.map diff --git a/mod/forum/report/summary/amd/build/filters.min.js.map b/mod/forum/report/summary/amd/build/filters.min.js.map index 69e1fba21f8..54c31d6e244 100644 --- a/mod/forum/report/summary/amd/build/filters.min.js.map +++ b/mod/forum/report/summary/amd/build/filters.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/filters.js"],"names":["init","root","jqRoot","document","ready","hide","removeClass","generateWithFilters","event","newLink","attr","preventDefault","filterParams","target","search","substr","submit","on","submitWithFilter","containerelement","addClass","CustomEvents","events","activate","Selectors","filters","group","selectall","deselected","querySelectorAll","checkbox","forEach","checked","clear","selected","trigger","referenceElement","querySelector","popperContent","popover","Popper","placement","classList","remove","add","setAttribute","save"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module responsible for handling forum summary report filters.\n *\n * @module forumreport_summary/filters\n * @package forumreport_summary\n * @copyright 2019 Michael Hawkins \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Popper from 'core/popper';\nimport CustomEvents from 'core/custom_interaction_events';\nimport Selectors from 'forumreport_summary/selectors';\n\nexport const init = (root) => {\n let jqRoot = $(root);\n\n // Hide loading spinner and show report once page is ready.\n // This ensures filters can be applied when sorting by columns.\n $(document).ready(function() {\n $('.loading-icon').hide();\n $('#summaryreport').removeClass('hidden');\n });\n\n // Generic filter handlers.\n\n // Called to override click event to trigger a proper generate request with filtering.\n var generateWithFilters = (event) => {\n var newLink = $('#filtersform').attr('action');\n\n if (event) {\n event.preventDefault();\n\n let filterParams = event.target.search.substr(1);\n newLink += '&' + filterParams;\n }\n\n $('#filtersform').attr('action', newLink);\n $('#filtersform').submit();\n };\n\n // Override 'reset table preferences' so it generates with filters.\n $('.resettable').on(\"click\", \"a\", function(event) {\n generateWithFilters(event);\n });\n\n // Override table heading sort links so they generate with filters.\n $('thead').on(\"click\", \"a\", function(event) {\n generateWithFilters(event);\n });\n\n // Override pagination page links so they generate with filters.\n $('.pagination').on(\"click\", \"a\", function(event) {\n generateWithFilters(event);\n });\n\n // Submit report via filter\n var submitWithFilter = (containerelement) => {\n // Close the container (eg popover).\n $(containerelement).addClass('hidden');\n\n // Submit the filter values and re-generate report.\n generateWithFilters(false);\n };\n\n // Groups filter specific handlers.\n\n // Event handler for clicking select all groups.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.selectall, function() {\n let deselected = root.querySelectorAll(Selectors.filters.group.checkbox + ':not(:checked)');\n deselected.forEach(function(checkbox) {\n checkbox.checked = true;\n });\n });\n\n // Event handler for clearing filter by clicking option.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.clear, function() {\n // Clear checkboxes.\n let selected = root.querySelectorAll(Selectors.filters.group.checkbox + ':checked');\n selected.forEach(function(checkbox) {\n checkbox.checked = false;\n });\n });\n\n // Event handler for showing groups filter popover.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.trigger, function() {\n // Create popover.\n var referenceElement = root.querySelector(Selectors.filters.group.trigger),\n popperContent = root.querySelector(Selectors.filters.group.popover);\n\n new Popper(referenceElement, popperContent, {placement: 'bottom'});\n\n // Show popover.\n popperContent.classList.remove('hidden');\n\n // Change to outlined button.\n referenceElement.classList.add('btn-outline-primary');\n referenceElement.classList.remove('btn-primary');\n\n // Let screen readers know that it's now expanded.\n referenceElement.setAttribute('aria-expanded', true);\n });\n\n // Event handler to click save groups filter.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.save, function() {\n submitWithFilter('#filter-groups-popover');\n });\n};\n"],"file":"filters.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/filters.js"],"names":["init","root","jqRoot","document","ready","hide","removeClass","generateWithFilters","event","newLink","attr","preventDefault","filterParams","target","search","substr","submit","on","submitWithFilter","containerelement","addClass","updateCalendarPosition","referenceid","referenceElement","querySelector","popperContent","Selectors","filters","date","calendar","style","removeProperty","Popper","placement","canOpenFilter","setAttribute","CustomEvents","events","activate","group","selectall","deselected","querySelectorAll","checkbox","forEach","checked","clear","selected","trigger","popover","classList","remove","add","save","focus","filtersForm","forms","filtersform","datesPopover","fromEnabled","toEnabled","Y","use","M","core_formchangechecker","reset_form_dirty_state","elements","value","args","data","push","Ajax","call","methodname","done","result","fromTimestamp","toTimestamp","key","timestamp","warningdiv","getElementById","calendariconfrom","calendariconto"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module responsible for handling forum summary report filters.\n *\n * @module forumreport_summary/filters\n * @package forumreport_summary\n * @copyright 2019 Michael Hawkins \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Popper from 'core/popper';\nimport CustomEvents from 'core/custom_interaction_events';\nimport Selectors from 'forumreport_summary/selectors';\nimport Y from 'core/yui';\nimport Ajax from 'core/ajax';\n\nexport const init = (root) => {\n let jqRoot = $(root);\n\n // Hide loading spinner and show report once page is ready.\n // This ensures filters can be applied when sorting by columns.\n $(document).ready(function() {\n $('.loading-icon').hide();\n $('#summaryreport').removeClass('hidden');\n });\n\n // Generic filter handlers.\n\n // Called to override click event to trigger a proper generate request with filtering.\n const generateWithFilters = (event) => {\n let newLink = $('#filtersform').attr('action');\n\n if (event) {\n event.preventDefault();\n\n let filterParams = event.target.search.substr(1);\n newLink += '&' + filterParams;\n }\n\n $('#filtersform').attr('action', newLink);\n $('#filtersform').submit();\n };\n\n // Override 'reset table preferences' so it generates with filters.\n $('.resettable').on(\"click\", \"a\", function(event) {\n generateWithFilters(event);\n });\n\n // Override table heading sort links so they generate with filters.\n $('thead').on(\"click\", \"a\", function(event) {\n generateWithFilters(event);\n });\n\n // Override pagination page links so they generate with filters.\n $('.pagination').on(\"click\", \"a\", function(event) {\n generateWithFilters(event);\n });\n\n // Submit report via filter\n const submitWithFilter = (containerelement) => {\n // Close the container (eg popover).\n $(containerelement).addClass('hidden');\n\n // Submit the filter values and re-generate report.\n generateWithFilters(false);\n };\n\n // Use popper to override date mform calendar position.\n const updateCalendarPosition = (referenceid) => {\n let referenceElement = document.querySelector(referenceid),\n popperContent = document.querySelector(Selectors.filters.date.calendar);\n\n popperContent.style.removeProperty(\"z-index\");\n new Popper(referenceElement, popperContent, {placement: 'bottom'});\n };\n\n // Call when opening filter to ensure only one can be activated.\n const canOpenFilter = (event) => {\n if (document.querySelector('[data-openfilter=\"true\"]')) {\n return false;\n }\n\n event.target.setAttribute('data-openfilter', \"true\");\n return true;\n };\n\n // Groups filter specific handlers.\n\n // Event handler for clicking select all groups.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.selectall, function() {\n let deselected = root.querySelectorAll(Selectors.filters.group.checkbox + ':not(:checked)');\n deselected.forEach(function(checkbox) {\n checkbox.checked = true;\n });\n });\n\n // Event handler for clearing filter by clicking option.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.clear, function() {\n // Clear checkboxes.\n let selected = root.querySelectorAll(Selectors.filters.group.checkbox + ':checked');\n selected.forEach(function(checkbox) {\n checkbox.checked = false;\n });\n });\n\n // Event handler for showing groups filter popover.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.trigger, function(event) {\n if (!canOpenFilter(event)) {\n return false;\n }\n\n // Create popover.\n let referenceElement = root.querySelector(Selectors.filters.group.trigger),\n popperContent = root.querySelector(Selectors.filters.group.popover);\n\n new Popper(referenceElement, popperContent, {placement: 'bottom'});\n\n // Show popover.\n popperContent.classList.remove('hidden');\n\n // Change to outlined button.\n referenceElement.classList.add('btn-outline-primary');\n referenceElement.classList.remove('btn-primary');\n\n // Let screen readers know that it's now expanded.\n referenceElement.setAttribute('aria-expanded', true);\n return true;\n });\n\n // Event handler to click save groups filter.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.save, function() {\n submitWithFilter('#filter-groups-popover');\n });\n\n // Dates filter specific handlers.\n\n // Event handler for showing dates filter popover.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.trigger, function(event) {\n if (!canOpenFilter(event)) {\n return false;\n }\n\n // Create popover.\n let referenceElement = root.querySelector(Selectors.filters.date.trigger),\n popperContent = root.querySelector(Selectors.filters.date.popover);\n\n new Popper(referenceElement, popperContent, {placement: 'bottom'});\n\n // Show popover and move focus.\n popperContent.classList.remove('hidden');\n popperContent.querySelector('[name=\"filterdatefrompopover[enabled]\"]').focus();\n\n // Change to outlined button.\n referenceElement.classList.add('btn-outline-primary');\n referenceElement.classList.remove('btn-primary');\n\n // Let screen readers know that it's now expanded.\n referenceElement.setAttribute('aria-expanded', true);\n return true;\n });\n\n // Event handler to save dates filter.\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.save, function() {\n // Populate the hidden form inputs to submit the data.\n let filtersForm = document.forms.filtersform;\n const datesPopover = root.querySelector(Selectors.filters.date.popover);\n const fromEnabled = datesPopover.querySelector('[name=\"filterdatefrompopover[enabled]\"]').checked ? 1 : 0;\n const toEnabled = datesPopover.querySelector('[name=\"filterdatetopopover[enabled]\"]').checked ? 1 : 0;\n\n // Disable the mform checker to prevent unsubmitted form warning to the user when closing the popover.\n Y.use('moodle-core-formchangechecker', function() {\n M.core_formchangechecker.reset_form_dirty_state();\n });\n\n if (!fromEnabled && !toEnabled) {\n // Update the elements in the filter form.\n filtersForm.elements['datefrom[timestamp]'].value = 0;\n filtersForm.elements['datefrom[enabled]'].value = fromEnabled;\n filtersForm.elements['dateto[timestamp]'].value = 0;\n filtersForm.elements['dateto[enabled]'].value = toEnabled;\n\n // Submit the filter values and re-generate report.\n submitWithFilter('#filter-dates-popover');\n } else {\n let args = {data: []};\n\n if (fromEnabled) {\n args.data.push({\n 'key': 'from',\n 'year': datesPopover.querySelector('[name=\"filterdatefrompopover[year]\"]').value,\n 'month': datesPopover.querySelector('[name=\"filterdatefrompopover[month]\"]').value,\n 'day': datesPopover.querySelector('[name=\"filterdatefrompopover[day]\"]').value,\n 'hour': 0,\n 'minute': 0\n });\n }\n\n if (toEnabled) {\n args.data.push({\n 'key': 'to',\n 'year': datesPopover.querySelector('[name=\"filterdatetopopover[year]\"]').value,\n 'month': datesPopover.querySelector('[name=\"filterdatetopopover[month]\"]').value,\n 'day': datesPopover.querySelector('[name=\"filterdatetopopover[day]\"]').value,\n 'hour': 23,\n 'minute': 59\n });\n }\n\n const request = {\n methodname: 'core_calendar_get_timestamps',\n args: args\n };\n\n Ajax.call([request])[0].done(function(result) {\n let fromTimestamp = 0,\n toTimestamp = 0;\n\n result['timestamps'].forEach(function(data){\n if (data.key === 'from') {\n fromTimestamp = data.timestamp;\n } else if (data.key === 'to') {\n toTimestamp = data.timestamp;\n }\n });\n\n // Display an error if the from date is later than the do date.\n if (toTimestamp > 0 && fromTimestamp > toTimestamp) {\n const warningdiv = document.getElementById('dates-filter-warning');\n warningdiv.classList.remove('hidden');\n warningdiv.classList.add('d-block');\n } else {\n filtersForm.elements['datefrom[timestamp]'].value = fromTimestamp;\n filtersForm.elements['datefrom[enabled]'].value = fromEnabled;\n filtersForm.elements['dateto[timestamp]'].value = toTimestamp;\n filtersForm.elements['dateto[enabled]'].value = toEnabled;\n\n // Submit the filter values and re-generate report.\n submitWithFilter('#filter-dates-popover');\n }\n });\n }\n });\n\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconfrom, function() {\n updateCalendarPosition(Selectors.filters.date.calendariconfrom);\n });\n\n jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconto, function() {\n updateCalendarPosition(Selectors.filters.date.calendariconto);\n });\n};\n"],"file":"filters.min.js"} \ No newline at end of file diff --git a/mod/forum/report/summary/amd/build/selectors.min.js b/mod/forum/report/summary/amd/build/selectors.min.js index 320b8f5b972..ca815cd4b27 100644 --- a/mod/forum/report/summary/amd/build/selectors.min.js +++ b/mod/forum/report/summary/amd/build/selectors.min.js @@ -1,2 +1,2 @@ -define ("forumreport_summary/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={filters:{group:{checkbox:"[data-region=\"filter-groups\"] input[type=\"checkbox\"]",clear:"[data-region=\"filter-groups\"] .filter-clear",popover:"#filter-groups-popover",save:"[data-region=\"filter-groups\"] .filter-save",selectall:"[data-region=\"filter-groups\"] .select-all",trigger:"#filter-groups-button"}}};return a.default}); +define ("forumreport_summary/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={filters:{group:{checkbox:"[data-region=\"filter-groups\"] input[type=\"checkbox\"]",clear:"[data-region=\"filter-groups\"] .filter-clear",popover:"#filter-groups-popover",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"}}};return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/mod/forum/report/summary/amd/build/selectors.min.js.map b/mod/forum/report/summary/amd/build/selectors.min.js.map index 2fc04188905..88ee0100a86 100644 --- a/mod/forum/report/summary/amd/build/selectors.min.js.map +++ b/mod/forum/report/summary/amd/build/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/selectors.js"],"names":["filters","group","checkbox","clear","popover","save","selectall","trigger"],"mappings":"yJAwBe,CACXA,OAAO,CAAE,CACLC,KAAK,CAAE,CACHC,QAAQ,CAAE,0DADP,CAEHC,KAAK,CAAE,+CAFJ,CAGHC,OAAO,CAAE,wBAHN,CAIHC,IAAI,CAAE,8CAJH,CAKHC,SAAS,CAAE,6CALR,CAMHC,OAAO,CAAE,uBANN,CADF,CADE,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module containing the selectors for the forum summary report.\n *\n * @module forumreport_summary/selectors\n * @package forumreport_summary\n * @copyright 2019 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n filters: {\n group: {\n checkbox: '[data-region=\"filter-groups\"] input[type=\"checkbox\"]',\n clear: '[data-region=\"filter-groups\"] .filter-clear',\n popover: '#filter-groups-popover',\n save: '[data-region=\"filter-groups\"] .filter-save',\n selectall: '[data-region=\"filter-groups\"] .select-all',\n trigger: '#filter-groups-button',\n }\n }\n};\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/selectors.js"],"names":["filters","group","checkbox","clear","popover","save","selectall","trigger","date","calendar","calendariconfrom","calendariconto"],"mappings":"yJAwBe,CACXA,OAAO,CAAE,CACLC,KAAK,CAAE,CACHC,QAAQ,CAAE,0DADP,CAEHC,KAAK,CAAE,+CAFJ,CAGHC,OAAO,CAAE,wBAHN,CAIHC,IAAI,CAAE,8CAJH,CAKHC,SAAS,CAAE,6CALR,CAMHC,OAAO,CAAE,uBANN,CADF,CASLC,IAAI,CAAE,CACFC,QAAQ,CAAE,8BADR,CAEFC,gBAAgB,CAAE,oCAFhB,CAGFC,cAAc,CAAE,kCAHd,CAIFP,OAAO,CAAE,uBAJP,CAKFC,IAAI,CAAE,6CALJ,CAMFE,OAAO,CAAE,sBANP,CATD,CADE,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module containing the selectors for the forum summary report.\n *\n * @module forumreport_summary/selectors\n * @package forumreport_summary\n * @copyright 2019 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n filters: {\n group: {\n checkbox: '[data-region=\"filter-groups\"] input[type=\"checkbox\"]',\n clear: '[data-region=\"filter-groups\"] .filter-clear',\n popover: '#filter-groups-popover',\n save: '[data-region=\"filter-groups\"] .filter-save',\n selectall: '[data-region=\"filter-groups\"] .select-all',\n trigger: '#filter-groups-button',\n },\n date: {\n calendar: '#dateselector-calendar-panel',\n calendariconfrom: '#id_filterdatefrompopover_calendar',\n calendariconto: '#id_filterdatetopopover_calendar',\n popover: '#filter-dates-popover',\n save: '[data-region=\"filter-dates\"] .filter-save',\n trigger: '#filter-dates-button',\n }\n }\n};\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/mod/forum/report/summary/amd/src/filters.js b/mod/forum/report/summary/amd/src/filters.js index 9ee20d857ee..55fbf4ac2e5 100644 --- a/mod/forum/report/summary/amd/src/filters.js +++ b/mod/forum/report/summary/amd/src/filters.js @@ -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); + }); }; diff --git a/mod/forum/report/summary/amd/src/selectors.js b/mod/forum/report/summary/amd/src/selectors.js index df0c6db8eeb..553cf28e995 100644 --- a/mod/forum/report/summary/amd/src/selectors.js +++ b/mod/forum/report/summary/amd/src/selectors.js @@ -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 index 00000000000..5d9f32e3db5 --- /dev/null +++ b/mod/forum/report/summary/classes/form/dates_filter_form.php @@ -0,0 +1,53 @@ +. + +/** + * The mform used by the forum summary report dates filter. + * + * @package forumreport_summary + * @copyright 2019 Michael Hawkins + * @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 + * @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); + } +} diff --git a/mod/forum/report/summary/classes/output/filters.php b/mod/forum/report/summary/classes/output/filters.php index 1c6be4724f5..60a71238beb 100644 --- a/mod/forum/report/summary/classes/output/filters.php +++ b/mod/forum/report/summary/classes/output/filters.php @@ -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; } } diff --git a/mod/forum/report/summary/classes/summary_table.php b/mod/forum/report/summary/classes/summary_table.php index a6d565cf7fa..3032c4967cf 100644 --- a/mod/forum/report/summary/classes/summary_table.php +++ b/mod/forum/report/summary/classes/summary_table.php @@ -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(); + // Apply relevant filters. + $this->define_base_filter_sql(); + $this->apply_filters($filters); + // Define the basic SQL data and object format. $this->define_base_sql(); - - // Apply relevant filters. - $this->apply_filters($filters); } /** - * 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); diff --git a/mod/forum/report/summary/index.php b/mod/forum/report/summary/index.php index 9b8e8285fdb..9872fc98a7c 100644 --- a/mod/forum/report/summary/index.php +++ b/mod/forum/report/summary/index.php @@ -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); diff --git a/mod/forum/report/summary/lang/en/forumreport_summary.php b/mod/forum/report/summary/lang/en/forumreport_summary.php index 50d10687570..71bb4423dfd 100644 --- a/mod/forum/report/summary/lang/en/forumreport_summary.php +++ b/mod/forum/report/summary/lang/en/forumreport_summary.php @@ -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 index 00000000000..d39c89774bc --- /dev/null +++ b/mod/forum/report/summary/templates/filter_dates.mustache @@ -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 . +}} +{{! + @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" + } + ] + } +}} + + + + +{{! Hidden dates fields to populate from visible mform in popover. }} +{{#filterdatesdata}} + + +{{/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 index 00000000000..a95b450b174 --- /dev/null +++ b/mod/forum/report/summary/templates/filter_dates_popover.mustache @@ -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 . +}} +{{! + @template forumreport_summary/filter_dates + + Summary report dates filter. + + Example context (json): + { + "filterdatesform": "
HTML for date filters
" + } +}} + + diff --git a/mod/forum/report/summary/templates/filter_groups.mustache b/mod/forum/report/summary/templates/filter_groups.mustache new file mode 100644 index 00000000000..939ed55e826 --- /dev/null +++ b/mod/forum/report/summary/templates/filter_groups.mustache @@ -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 . +}} +{{! + @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}} + + +{{! Groups filter popover }} + +{{/hasgroups}} diff --git a/mod/forum/report/summary/templates/filters.mustache b/mod/forum/report/summary/templates/filters.mustache index 211546c5fd4..f4dd7fbeef7 100644 --- a/mod/forum/report/summary/templates/filters.mustache +++ b/mod/forum/report/summary/templates/filters.mustache @@ -35,47 +35,36 @@ "groupname": "Group C", "checked": false } + ], + "filterdatesname": "Dates", + "filterdatesform": "
HTML for date filters
", + "filterdatesdata": [ + { + "type": "from", + "timestamp": "510969600", + "enabled": "1" + }, + { + "type": "to", + "timestamp": "725673600", + "enabled": "1" + } ] } }} -
+
-
- {{#hasgroups}} - - {{! Start groups filter popover}} - - {{! End groups filter popover}} - {{/hasgroups}} +
+ {{> forumreport_summary/filter_dates}} + {{> forumreport_summary/filter_groups}}
+ + {{! Dates filter popover - mform must exist outside of the filtersform }} + {{> forumreport_summary/filter_dates_popover}}
{{#js}} diff --git a/mod/forum/report/summary/version.php b/mod/forum/report/summary/version.php index d695f93e325..5a0c84e8782 100644 --- a/mod/forum/report/summary/version.php +++ b/mod/forum/report/summary/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019090200; +$plugin->version = 2019101800; $plugin->requires = 2019071900; $plugin->component = 'forumreport_summary'; diff --git a/theme/boost/scss/moodle/modules.scss b/theme/boost/scss/moodle/modules.scss index 6bd0c9e03a1..e1e6a0d8753 100644 --- a/theme/boost/scss/moodle/modules.scss +++ b/theme/boost/scss/moodle/modules.scss @@ -416,6 +416,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, diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 06b514ee779..f2d44583799 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -16561,6 +16561,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; } diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 4c95e055f78..0cc729a5706 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -16833,6 +16833,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; }