From 1b9b8237a3ba4f76deb84a22087465b80a068695 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Fri, 8 Oct 2021 07:16:51 +0700 Subject: [PATCH] Ad-hoc DB report: Improve sub-category navigation options #396428 --- amd/build/reportcategories.min.js | 2 +- amd/build/reportcategories.min.js.map | 2 +- amd/src/reportcategories.js | 2 +- category.php | 51 ++++++ categorydelete.php | 7 +- classes/local/category.php | 117 +++++++++++++ classes/local/query.php | 134 +++++++++++++++ classes/output/category.php | 154 ++++++++++++++++++ classes/output/category_query.php | 81 +++++++++ classes/output/index_page.php | 103 ++++++++++++ classes/output/renderer.php | 68 ++++++++ classes/utils.php | 39 +++++ delete.php | 23 ++- edit.php | 43 ++++- edit_form.php | 11 +- index.php | 75 +-------- lang/en/report_customsql.php | 3 + locallib.php | 40 +---- manage.php | 6 +- styles.css | 3 +- templates/category.mustache | 93 +++++++++++ templates/category_query.mustache | 67 ++++++++ templates/expand_collapse_link.mustache | 38 +++++ templates/index_page.mustache | 57 +++++++ templates/query_actions.mustache | 45 +++++ tests/behat/report_customsql.feature | 19 +++ ...port_customsql_local_category_testcase.php | 87 ++++++++++ .../report_customsql_local_query_testcase.php | 62 +++++++ view.php | 18 +- 29 files changed, 1320 insertions(+), 130 deletions(-) create mode 100644 category.php create mode 100644 classes/local/category.php create mode 100644 classes/local/query.php create mode 100644 classes/output/category.php create mode 100644 classes/output/category_query.php create mode 100644 classes/output/index_page.php create mode 100644 classes/output/renderer.php create mode 100644 templates/category.mustache create mode 100644 templates/category_query.mustache create mode 100644 templates/expand_collapse_link.mustache create mode 100644 templates/index_page.mustache create mode 100644 templates/query_actions.mustache create mode 100644 tests/report_customsql_local_category_testcase.php create mode 100644 tests/report_customsql_local_query_testcase.php diff --git a/amd/build/reportcategories.min.js b/amd/build/reportcategories.min.js index 865f862..0702bf8 100644 --- a/amd/build/reportcategories.min.js +++ b/amd/build/reportcategories.min.js @@ -1,2 +1,2 @@ -define ("report_customsql/reportcategories",["jquery"],function(a){var b={init:function init(){a("body").on("click",".csql_category h2",b.expandCollapse);a(".csql_expandcollapseall").on("click",b.expandCollapseAll);b.updateExpandCollapseAll()},expandCollapse:function expandCollapse(c){var d=a(c.target).closest(".csql_category");if(d.length){if(d.hasClass("csql_categoryhidden")){d.removeClass("csql_categoryhidden").addClass("csql_categoryshown")}else{d.removeClass("csql_categoryshown").addClass("csql_categoryhidden")}c.preventDefault();b.updateExpandCollapseAll()}},expandCollapseAll:function expandCollapseAll(c){if(0===a(".csql_categoryshown").length){a(".csql_category").removeClass("csql_categoryhidden");a(".csql_category").addClass("csql_categoryshown")}else{a(".csql_category").removeClass("csql_categoryshown");a(".csql_category").addClass("csql_categoryhidden")}c.preventDefault();b.updateExpandCollapseAll()},updateExpandCollapseAll:function updateExpandCollapseAll(){var b=a(".csql_expandcollapseall");if(0===a(".csql_categoryshown").length){b.text(b.data("expandalltext"))}else{b.text(b.data("collapsealltext"))}}};return b}); +define ("report_customsql/reportcategories",["jquery"],function(a){var b={init:function init(){a("body").on("click",".csql_category h2 .categoryname, .csql_category h2 .reportcounts",b.expandCollapse);a(".csql_expandcollapseall").on("click",b.expandCollapseAll);b.updateExpandCollapseAll()},expandCollapse:function expandCollapse(c){var d=a(c.target).closest(".csql_category");if(d.length){if(d.hasClass("csql_categoryhidden")){d.removeClass("csql_categoryhidden").addClass("csql_categoryshown")}else{d.removeClass("csql_categoryshown").addClass("csql_categoryhidden")}c.preventDefault();b.updateExpandCollapseAll()}},expandCollapseAll:function expandCollapseAll(c){if(0===a(".csql_categoryshown").length){a(".csql_category").removeClass("csql_categoryhidden");a(".csql_category").addClass("csql_categoryshown")}else{a(".csql_category").removeClass("csql_categoryshown");a(".csql_category").addClass("csql_categoryhidden")}c.preventDefault();b.updateExpandCollapseAll()},updateExpandCollapseAll:function updateExpandCollapseAll(){var b=a(".csql_expandcollapseall");if(0===a(".csql_categoryshown").length){b.text(b.data("expandalltext"))}else{b.text(b.data("collapsealltext"))}}};return b}); //# sourceMappingURL=reportcategories.min.js.map diff --git a/amd/build/reportcategories.min.js.map b/amd/build/reportcategories.min.js.map index aa092f3..d0c9c88 100644 --- a/amd/build/reportcategories.min.js.map +++ b/amd/build/reportcategories.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/reportcategories.js"],"names":["define","$","t","init","on","expandCollapse","expandCollapseAll","updateExpandCollapseAll","e","catwrapper","target","closest","length","hasClass","removeClass","addClass","preventDefault","link","text","data"],"mappings":"AA0BAA,OAAM,qCAAC,CAAC,QAAD,CAAD,CAAa,SAASC,CAAT,CAAY,CAI3B,GAAIC,CAAAA,CAAC,CAAG,CAKJC,IAAI,CAAE,eAAW,CACbF,CAAC,CAAC,MAAD,CAAD,CAAUG,EAAV,CAAa,OAAb,CAAsB,mBAAtB,CAA2CF,CAAC,CAACG,cAA7C,EACAJ,CAAC,CAAC,yBAAD,CAAD,CAA6BG,EAA7B,CAAgC,OAAhC,CAAyCF,CAAC,CAACI,iBAA3C,EACAJ,CAAC,CAACK,uBAAF,EACH,CATG,CAgBJF,cAAc,CAAE,wBAASG,CAAT,CAAY,CACxB,GAAIC,CAAAA,CAAU,CAAGR,CAAC,CAACO,CAAC,CAACE,MAAH,CAAD,CAAYC,OAAZ,CAAoB,gBAApB,CAAjB,CACA,GAAIF,CAAU,CAACG,MAAf,CAAuB,CACnB,GAAIH,CAAU,CAACI,QAAX,CAAoB,qBAApB,CAAJ,CAAgD,CAC5CJ,CAAU,CAACK,WAAX,CAAuB,qBAAvB,EAA8CC,QAA9C,CAAuD,oBAAvD,CACH,CAFD,IAEO,CACHN,CAAU,CAACK,WAAX,CAAuB,oBAAvB,EAA6CC,QAA7C,CAAsD,qBAAtD,CACH,CACDP,CAAC,CAACQ,cAAF,GACAd,CAAC,CAACK,uBAAF,EACH,CACJ,CA3BG,CAkCJD,iBAAiB,CAAE,2BAASE,CAAT,CAAY,CAC3B,GAAwC,CAApC,GAAAP,CAAC,CAAC,qBAAD,CAAD,CAAyBW,MAA7B,CAA2C,CAEvCX,CAAC,CAAC,gBAAD,CAAD,CAAoBa,WAApB,CAAgC,qBAAhC,EACAb,CAAC,CAAC,gBAAD,CAAD,CAAoBc,QAApB,CAA6B,oBAA7B,CACH,CAJD,IAIO,CAEHd,CAAC,CAAC,gBAAD,CAAD,CAAoBa,WAApB,CAAgC,oBAAhC,EACAb,CAAC,CAAC,gBAAD,CAAD,CAAoBc,QAApB,CAA6B,qBAA7B,CACH,CACDP,CAAC,CAACQ,cAAF,GACAd,CAAC,CAACK,uBAAF,EACH,CA9CG,CAoDJA,uBAAuB,CAAE,kCAAW,CAChC,GAAIU,CAAAA,CAAI,CAAGhB,CAAC,CAAC,yBAAD,CAAZ,CACA,GAAwC,CAApC,GAAAA,CAAC,CAAC,qBAAD,CAAD,CAAyBW,MAA7B,CAA2C,CAEvCK,CAAI,CAACC,IAAL,CAAUD,CAAI,CAACE,IAAL,CAAU,eAAV,CAAV,CACH,CAHD,IAGO,CAEHF,CAAI,CAACC,IAAL,CAAUD,CAAI,CAACE,IAAL,CAAU,iBAAV,CAAV,CACH,CACJ,CA7DG,CAAR,CAgEA,MAAOjB,CAAAA,CACV,CArEK,CAAN","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 * JavaScript to expand/collapse sections.\n *\n * @package report_customsql\n * @copyright 2014 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * @module report_customsql/reportcategories\n */\ndefine(['jquery'], function($) {\n /**\n * @alias module:report_customsql/reportcategories\n */\n var t = {\n\n /**\n * Initialise the tabs.\n */\n init: function() {\n $('body').on('click', '.csql_category h2', t.expandCollapse);\n $('.csql_expandcollapseall').on('click', t.expandCollapseAll);\n t.updateExpandCollapseAll();\n },\n\n /**\n * Event handler for expanding or collapsing one section.\n *\n * @param {Event} e DOM event.\n */\n expandCollapse: function(e) {\n var catwrapper = $(e.target).closest('.csql_category');\n if (catwrapper.length) {\n if (catwrapper.hasClass('csql_categoryhidden')) {\n catwrapper.removeClass('csql_categoryhidden').addClass('csql_categoryshown');\n } else {\n catwrapper.removeClass('csql_categoryshown').addClass('csql_categoryhidden');\n }\n e.preventDefault();\n t.updateExpandCollapseAll();\n }\n },\n\n /**\n * Event handler for expanding or collapsing one section.\n *\n * @param {Event} DOM event.\n */\n expandCollapseAll: function(e) {\n if ($('.csql_categoryshown').length === 0) {\n // All categories collapsed, do expand all.\n $('.csql_category').removeClass('csql_categoryhidden');\n $('.csql_category').addClass('csql_categoryshown');\n } else {\n // All Some categories open, do collapse all.\n $('.csql_category').removeClass('csql_categoryshown');\n $('.csql_category').addClass('csql_categoryhidden');\n }\n e.preventDefault();\n t.updateExpandCollapseAll();\n },\n\n /**\n * Update the text of the expand/collpase all link, based\n * on whether any sections are open.\n */\n updateExpandCollapseAll: function() {\n var link = $('.csql_expandcollapseall');\n if ($('.csql_categoryshown').length === 0) {\n // All categories collapsed, link should expand all.\n link.text(link.data('expandalltext'));\n } else {\n // All Some categories open, link should collapse all.\n link.text(link.data('collapsealltext'));\n }\n }\n };\n\n return t;\n});\n"],"file":"reportcategories.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/reportcategories.js"],"names":["define","$","t","init","on","expandCollapse","expandCollapseAll","updateExpandCollapseAll","e","catwrapper","target","closest","length","hasClass","removeClass","addClass","preventDefault","link","text","data"],"mappings":"AA0BAA,OAAM,qCAAC,CAAC,QAAD,CAAD,CAAa,SAASC,CAAT,CAAY,CAI3B,GAAIC,CAAAA,CAAC,CAAG,CAKJC,IAAI,CAAE,eAAW,CACbF,CAAC,CAAC,MAAD,CAAD,CAAUG,EAAV,CAAa,OAAb,CAAsB,kEAAtB,CAA0FF,CAAC,CAACG,cAA5F,EACAJ,CAAC,CAAC,yBAAD,CAAD,CAA6BG,EAA7B,CAAgC,OAAhC,CAAyCF,CAAC,CAACI,iBAA3C,EACAJ,CAAC,CAACK,uBAAF,EACH,CATG,CAgBJF,cAAc,CAAE,wBAASG,CAAT,CAAY,CACxB,GAAIC,CAAAA,CAAU,CAAGR,CAAC,CAACO,CAAC,CAACE,MAAH,CAAD,CAAYC,OAAZ,CAAoB,gBAApB,CAAjB,CACA,GAAIF,CAAU,CAACG,MAAf,CAAuB,CACnB,GAAIH,CAAU,CAACI,QAAX,CAAoB,qBAApB,CAAJ,CAAgD,CAC5CJ,CAAU,CAACK,WAAX,CAAuB,qBAAvB,EAA8CC,QAA9C,CAAuD,oBAAvD,CACH,CAFD,IAEO,CACHN,CAAU,CAACK,WAAX,CAAuB,oBAAvB,EAA6CC,QAA7C,CAAsD,qBAAtD,CACH,CACDP,CAAC,CAACQ,cAAF,GACAd,CAAC,CAACK,uBAAF,EACH,CACJ,CA3BG,CAkCJD,iBAAiB,CAAE,2BAASE,CAAT,CAAY,CAC3B,GAAwC,CAApC,GAAAP,CAAC,CAAC,qBAAD,CAAD,CAAyBW,MAA7B,CAA2C,CAEvCX,CAAC,CAAC,gBAAD,CAAD,CAAoBa,WAApB,CAAgC,qBAAhC,EACAb,CAAC,CAAC,gBAAD,CAAD,CAAoBc,QAApB,CAA6B,oBAA7B,CACH,CAJD,IAIO,CAEHd,CAAC,CAAC,gBAAD,CAAD,CAAoBa,WAApB,CAAgC,oBAAhC,EACAb,CAAC,CAAC,gBAAD,CAAD,CAAoBc,QAApB,CAA6B,qBAA7B,CACH,CACDP,CAAC,CAACQ,cAAF,GACAd,CAAC,CAACK,uBAAF,EACH,CA9CG,CAoDJA,uBAAuB,CAAE,kCAAW,CAChC,GAAIU,CAAAA,CAAI,CAAGhB,CAAC,CAAC,yBAAD,CAAZ,CACA,GAAwC,CAApC,GAAAA,CAAC,CAAC,qBAAD,CAAD,CAAyBW,MAA7B,CAA2C,CAEvCK,CAAI,CAACC,IAAL,CAAUD,CAAI,CAACE,IAAL,CAAU,eAAV,CAAV,CACH,CAHD,IAGO,CAEHF,CAAI,CAACC,IAAL,CAAUD,CAAI,CAACE,IAAL,CAAU,iBAAV,CAAV,CACH,CACJ,CA7DG,CAAR,CAgEA,MAAOjB,CAAAA,CACV,CArEK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\r\n//\r\n// Moodle is free software: you can redistribute it and/or modify\r\n// it under the terms of the GNU General Public License as published by\r\n// the Free Software Foundation, either version 3 of the License, or\r\n// (at your option) any later version.\r\n//\r\n// Moodle is distributed in the hope that it will be useful,\r\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n// GNU General Public License for more details.\r\n//\r\n// You should have received a copy of the GNU General Public License\r\n// along with Moodle. If not, see .\r\n\r\n/*\r\n * JavaScript to expand/collapse sections.\r\n *\r\n * @package report_customsql\r\n * @copyright 2014 The Open University\r\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r\n */\r\n\r\n/**\r\n * @module report_customsql/reportcategories\r\n */\r\ndefine(['jquery'], function($) {\r\n /**\r\n * @alias module:report_customsql/reportcategories\r\n */\r\n var t = {\r\n\r\n /**\r\n * Initialise the tabs.\r\n */\r\n init: function() {\r\n $('body').on('click', '.csql_category h2 .categoryname, .csql_category h2 .reportcounts', t.expandCollapse);\r\n $('.csql_expandcollapseall').on('click', t.expandCollapseAll);\r\n t.updateExpandCollapseAll();\r\n },\r\n\r\n /**\r\n * Event handler for expanding or collapsing one section.\r\n *\r\n * @param {Event} e DOM event.\r\n */\r\n expandCollapse: function(e) {\r\n var catwrapper = $(e.target).closest('.csql_category');\r\n if (catwrapper.length) {\r\n if (catwrapper.hasClass('csql_categoryhidden')) {\r\n catwrapper.removeClass('csql_categoryhidden').addClass('csql_categoryshown');\r\n } else {\r\n catwrapper.removeClass('csql_categoryshown').addClass('csql_categoryhidden');\r\n }\r\n e.preventDefault();\r\n t.updateExpandCollapseAll();\r\n }\r\n },\r\n\r\n /**\r\n * Event handler for expanding or collapsing one section.\r\n *\r\n * @param {Event} DOM event.\r\n */\r\n expandCollapseAll: function(e) {\r\n if ($('.csql_categoryshown').length === 0) {\r\n // All categories collapsed, do expand all.\r\n $('.csql_category').removeClass('csql_categoryhidden');\r\n $('.csql_category').addClass('csql_categoryshown');\r\n } else {\r\n // All Some categories open, do collapse all.\r\n $('.csql_category').removeClass('csql_categoryshown');\r\n $('.csql_category').addClass('csql_categoryhidden');\r\n }\r\n e.preventDefault();\r\n t.updateExpandCollapseAll();\r\n },\r\n\r\n /**\r\n * Update the text of the expand/collpase all link, based\r\n * on whether any sections are open.\r\n */\r\n updateExpandCollapseAll: function() {\r\n var link = $('.csql_expandcollapseall');\r\n if ($('.csql_categoryshown').length === 0) {\r\n // All categories collapsed, link should expand all.\r\n link.text(link.data('expandalltext'));\r\n } else {\r\n // All Some categories open, link should collapse all.\r\n link.text(link.data('collapsealltext'));\r\n }\r\n }\r\n };\r\n\r\n return t;\r\n});\r\n"],"file":"reportcategories.min.js"} \ No newline at end of file diff --git a/amd/src/reportcategories.js b/amd/src/reportcategories.js index 7e46d38..a419710 100644 --- a/amd/src/reportcategories.js +++ b/amd/src/reportcategories.js @@ -34,7 +34,7 @@ define(['jquery'], function($) { * Initialise the tabs. */ init: function() { - $('body').on('click', '.csql_category h2', t.expandCollapse); + $('body').on('click', '.csql_category h2 .categoryname, .csql_category h2 .reportcounts', t.expandCollapse); $('.csql_expandcollapseall').on('click', t.expandCollapseAll); t.updateExpandCollapseAll(); }, diff --git a/category.php b/category.php new file mode 100644 index 0000000..4a8798f --- /dev/null +++ b/category.php @@ -0,0 +1,51 @@ +. + +/** + * This page shows the list of queries in a category, with edit icons, an add new button + * if you have the report/customsql:definequeries capability + * + * @package report_customsql + * @copyright 2021 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once(dirname(__FILE__) . '/locallib.php'); +require_once($CFG->libdir . '/adminlib.php'); + +// Start the page. +admin_externalpage_setup('report_customsql'); +$context = context_system::instance(); +require_capability('report/customsql:view', $context); + +$categoryid = required_param('id', PARAM_INT); +$record = $DB->get_record('report_customsql_categories', ['id' => $categoryid], '*', MUST_EXIST); +$queries = $DB->get_records('report_customsql_queries', ['categoryid' => $categoryid]); + +$category = new \report_customsql\local\category($record); +$category->load_queries_data($queries); +$widget = new \report_customsql\output\category($category, $context); + +$PAGE->set_title(format_string($category->get_name())); +$PAGE->navbar->add(format_string($category->get_name())); +$output = $PAGE->get_renderer('report_customsql'); + +echo $OUTPUT->header(); + +echo $output->render($widget); + +echo $OUTPUT->footer(); diff --git a/categorydelete.php b/categorydelete.php index 3fa8ec4..155dca4 100644 --- a/categorydelete.php +++ b/categorydelete.php @@ -59,8 +59,7 @@ echo $OUTPUT->heading(get_string('deletecategoryareyousure', 'report_customsql')); echo html_writer::tag('p', get_string('categorynamex', 'report_customsql', $category->name )); echo $OUTPUT->confirm(get_string('deletecategoryyesno', 'report_customsql'), - new single_button(new moodle_url(report_customsql_url('categorydelete.php'), - array('id' => $id, 'confirm' => 1, 'sesskey' => sesskey())), get_string('yes')), - new single_button(new moodle_url(report_customsql_url('index.php')), - get_string('no'))); + new single_button(report_customsql_url('categorydelete.php', + ['id' => $id, 'confirm' => 1, 'sesskey' => sesskey()]), get_string('yes')), + new single_button(report_customsql_url('index.php'), get_string('no'))); echo $OUTPUT->footer(); diff --git a/classes/local/category.php b/classes/local/category.php new file mode 100644 index 0000000..0553205 --- /dev/null +++ b/classes/local/category.php @@ -0,0 +1,117 @@ +. + +namespace report_customsql\local; + +use report_customsql\utils; + +/** + * Category class. + * + * @package report_customsql + * @copyright 2021 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class category { + /** @var int Category ID. */ + private $id; + + /** @var string Category name. */ + private $name; + + /** @var array Pre-loaded queries data. */ + private $queriesdata; + + /** @var array Pre-loaded statistic data. */ + private $statistic; + + /** + * Create a new category object. + * + * @param \stdClass $record The record from database. + */ + public function __construct(\stdClass $record) { + $this->id = $record->id; + $this->name = $record->name; + } + + /** + * Load queries of category from records. + * + * @param array $queries Records to load. + */ + public function load_queries_data(array $queries): void { + $statistic = []; + $queriesdata = []; + foreach (report_customsql_runable_options() as $type => $description) { + $fitleredqueries = utils::get_number_of_report_by_type($queries, $type); + $statistic[$type] = count($fitleredqueries); + if ($fitleredqueries) { + $queriesdata[] = [ + 'type' => $type, + 'queries' => $fitleredqueries + ]; + } + } + $this->queriesdata = $queriesdata; + $this->statistic = $statistic; + } + + /** + * Get category ID. + * + * @return int Category ID. + */ + public function get_id(): int { + return $this->id; + } + + /** + * Get category name. + * + * @return string Category name. + */ + public function get_name(): string { + return $this->name; + } + + /** + * Get pre-loaded queries' data of this category. + * + * @return array Queries' data. + */ + public function get_queries_data(): array { + return $this->queriesdata; + } + + /** + * Get pre-loaded statistic of this category. + * + * @return array Statistic data. + */ + public function get_statistic(): array { + return $this->statistic; + } + + /** + * Get url to view the category. + * + * @return \moodle_url Category's url. + */ + public function get_url(): \moodle_url { + return report_customsql_url('category.php', ['id' => $this->id]); + } +} diff --git a/classes/local/query.php b/classes/local/query.php new file mode 100644 index 0000000..d604ca3 --- /dev/null +++ b/classes/local/query.php @@ -0,0 +1,134 @@ +. + +namespace report_customsql\local; + +use moodle_url; + +/** + * Query class. + * + * @package report_customsql + * @copyright 2021 The Open Univesity + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query { + /** @var \stdClass The query's record from database. */ + private $record; + + /** + * Create a new category object. + * + * @param \stdClass $record The record from database. + */ + public function __construct(\stdClass $record) { + $this->record = $record; + } + + /** + * Get query Id. + * + * @return int Query's Id. + */ + public function get_id(): int { + return $this->record->id; + } + + /** + * Get query's display name. + * + * @return string Display name. + */ + public function get_displayname(): string { + return $this->record->displayname; + } + + /** + * Get url to view query. + * + * @return moodle_url View url. + */ + public function get_url(): moodle_url { + return report_customsql_url('view.php', ['id' => $this->record->id]); + } + + /** Get url to edit query. + * + * @param moodle_url|null $returnurl Return url. + * @return moodle_url Edit url. + */ + public function get_edit_url(moodle_url $returnurl = null): moodle_url { + $param = ['id' => $this->record->id]; + if ($returnurl) { + $param['returnurl'] = $returnurl->out_as_local_url(false); + } + + return report_customsql_url('edit.php', $param); + } + + /** + * Get url to delete query. + * + * @param moodle_url|null $returnurl Return url. + * @return moodle_url Delete url. + */ + public function get_delete_url(moodle_url $returnurl = null): moodle_url { + $param = ['id' => $this->record->id]; + if ($returnurl) { + $param['returnurl'] = $returnurl->out_as_local_url(false); + } + + return report_customsql_url('delete.php', $param); + } + + /** + * Get the time note. + * + * @return string Time not. + */ + public function get_time_note() { + return report_customsql_time_note($this->record, 'span'); + } + + /** + * Get the text to display the capability. + * + * @return string Capability text. + */ + public function get_capability_string() { + $capabilities = report_customsql_capability_options(); + return $capabilities[$this->record->capability]; + } + + /** + * Check if user can edit the query. + * + * @param \context $context The context to check. + * @return bool true if the user has this capability. Otherwise false. + */ + public function can_edit(\context $context): bool { + return has_capability('report/customsql:definequeries', $context); + } + + /** + * Check the capability to view the query. + * + * @return bool Has capability to view or not? + */ + public function can_view(\context $context):bool { + return empty($report->capability) || has_capability($report->capability, $context); + } +} diff --git a/classes/output/category.php b/classes/output/category.php new file mode 100644 index 0000000..37a80f0 --- /dev/null +++ b/classes/output/category.php @@ -0,0 +1,154 @@ +. + +namespace report_customsql\output; + +use context; +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use report_customsql\local\query as report_query; +use report_customsql\local\category as report_category; + +/** + * Category renderable class. + * + * @package report_customsql + * @copyright 2021 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class category implements renderable, templatable { + /** @var report_category Category object. */ + private $category; + + /** @var context Context. */ + private $context; + + /** @var int Shown category id from optional param. */ + private $showcat; + + /** @var int Hidden category id from optional param. */ + private $hidecat; + + /** @var bool Do we show the 'Show only' link? */ + private $showonlythislink; + + /** @var bool Can the category expanse/collapse? */ + private $expandable; + + /** @var moodle_url Return url. */ + private $returnurl; + + /** @var bool Show 'Add new query' button or not. */ + private $addnewquerybtn; + + /** Create the category renderable object. + * + * @param report_category $category Category object. + * @param context $context Context to check the permission. + * @param bool $expandable Can the category expanse/collapse? + * @param int $showcat Shown category id from optional param + * @param int $hidecat Hidden category id from optional param + * @param bool $showonlythislink Do we show the 'Show only' link? + * @param bool $addnewquerybtn Show 'Add new query' button or not. + * @param moodle_url|null $returnurl Return url. + */ + public function __construct(report_category $category, context $context, bool $expandable = false, int $showcat = 0, + int $hidecat = 0, bool $showonlythislink = false, bool $addnewquerybtn = true, moodle_url $returnurl = null) { + $this->category = $category; + $this->context = $context; + $this->expandable = $expandable; + $this->showcat = $showcat; + $this->hidecat = $hidecat; + $this->showonlythislink = $showonlythislink; + $this->addnewquerybtn = $addnewquerybtn; + $this->returnurl = $returnurl ?? $this->category->get_url(); + } + + public function export_for_template(renderer_base $output) { + + $queriesdata = $this->category->get_queries_data(); + + $querygroups = []; + foreach ($queriesdata as $querygroup) { + $queries = []; + foreach ($querygroup['queries'] as $querydata) { + $query = new report_query($querydata); + if (!$query->can_view($this->context)) { + continue; + } + $querywidget = new category_query($query, $this->category, $this->context, $this->returnurl); + $queries[] = ['categoryqueryitem' => $output->render($querywidget)]; + } + + $querygroups[] = [ + 'type' => $querygroup['type'], + 'title' => get_string($querygroup['type'] . 'header', 'report_customsql'), + 'helpicon' => $output->help_icon($querygroup['type'] . 'header', 'report_customsql'), + 'queries' => $queries + ]; + } + + $addquerybutton = ''; + if ($this->addnewquerybtn && has_capability('report/customsql:definequeries', $this->context)) { + $addnewqueryurl = report_customsql_url('edit.php', ['categoryid' => $this->category->get_id(), + 'returnurl' => $this->returnurl->out_as_local_url(false)]); + $addquerybutton = $output->single_button($addnewqueryurl, get_string('addreport', 'report_customsql'), 'post', + ['class' => 'mb-1']); + } + + return [ + 'id' => $this->category->get_id(), + 'name' => $this->category->get_name(), + 'expandable' => $this->expandable, + 'show' => $this->get_showing_state(), + 'showonlythislink' => $this->showonlythislink, + 'url' => $this->category->get_url()->out(false), + 'linkref' => $this->get_link_reference(), + 'statistic' => $this->category->get_statistic(), + 'querygroups' => $querygroups, + 'addquerybutton' => $addquerybutton + ]; + } + + /** + * Get showing state of category. Default is hidden. + * + * @return string + */ + private function get_showing_state(): string { + $categoryid = $this->category->get_id(); + + return $categoryid == $this->showcat && $categoryid != $this->hidecat ? 'shown' : 'hidden'; + } + + /** + * Get the link with showcat/hidecat parameter. + * + * @return string The url. + */ + private function get_link_reference(): string { + $categoryid = $this->category->get_id(); + if ($categoryid == $this->showcat) { + $params = ['hidecat' => $categoryid]; + } else { + $params = ['showcat' => $categoryid]; + } + + return report_customsql_url('index.php', $params)->out(false); + } +} diff --git a/classes/output/category_query.php b/classes/output/category_query.php new file mode 100644 index 0000000..6365ee0 --- /dev/null +++ b/classes/output/category_query.php @@ -0,0 +1,81 @@ +. + +namespace report_customsql\output; + +use context; +use moodle_url; +use renderable; +use report_customsql\local\query; +use report_customsql\local\category; +use templatable; + + +/** + * Renderable class to show the query item in category page. + * + * @package report_customsql + * @copyright 2021 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class category_query implements renderable, templatable { + /** @var query Query object. */ + private $query; + + /** @var query Category object. */ + private $category; + + private $context; + + /** @var moodle_url Return url. */ + private $returnurl; + + /** + * Create the category renderable object. + * @param query $query Query object. + * @param category $category + * @param context $context Context to check the capability. + * @param moodle_url $returnurl Return url. + */ + public function __construct(query $query, category $category, context $context, moodle_url $returnurl) { + $this->query = $query; + $this->category = $category; + $this->context = $context; + $this->returnurl = $returnurl; + } + + public function export_for_template(\renderer_base $output) { + $imgedit = $output->pix_icon('t/edit', get_string('edit')); + $imgdelete = $output->pix_icon('t/delete', get_string('delete')); + + return [ + 'id' => $this->query->get_id(), + 'displayname' => $this->query->get_displayname(), + 'url' => $this->query->get_url()->out(false), + 'canedit' => $this->query->can_edit($this->context), + 'timenote' => $this->query->get_time_note(), + 'editbutton' => [ + 'url' => $this->query->get_edit_url($this->returnurl)->out(false), + 'img' => $imgedit + ], + 'deletebutton' => [ + 'url' => $this->query->get_delete_url($this->returnurl)->out(false), + 'img' => $imgdelete + ], + 'capability' => $this->query->get_capability_string() + ]; + } +} diff --git a/classes/output/index_page.php b/classes/output/index_page.php new file mode 100644 index 0000000..4d8ef73 --- /dev/null +++ b/classes/output/index_page.php @@ -0,0 +1,103 @@ +. + +namespace report_customsql\output; + +use context; +use moodle_url; +use renderable; +use templatable; +use renderer_base; +use report_customsql\utils; +use report_customsql\local\category as report_category; + +/** + * Index page renderable class. + * + * @package report_customsql + * @copyright 2021 The Open Univesity + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class index_page implements renderable, templatable { + /** @var array Categories' data. */ + private $categories; + + /** @var array Queries' data. */ + private $queries; + + /** @var context Context to check the capability. */ + private $context; + + /** @var moodle_url Return url for edit/delete link. */ + private $returnurl; + + /** @var int Shown category id from optional param. */ + private $showcat; + + /** @var int Hidden category id from optional param. */ + private $hidecat; + + /** Build the index page renderable object. + * + * @param array $categories Categories for renderer. + * @param array $queries Queries for renderer. + * @param context $context Context to check the capability. + * @param moodle_url $returnurl Return url for edit/delete link. + * @param int $showcat Showing Category Id. + * @param int $hidecat Hiding Category Id. + */ + public function __construct(array $categories, array $queries, context $context, moodle_url $returnurl, + int $showcat = 0, int $hidecat = 0) { + $this->categories = $categories; + $this->queries = $queries; + $this->context = $context; + $this->returnurl = $returnurl; + $this->showcat = $showcat; + $this->hidecat = $hidecat; + } + + public function export_for_template(renderer_base $output) { + $categoriesdata = []; + $grouppedqueries = utils::group_queries_by_category($this->queries); + foreach ($this->categories as $record) { + $category = new report_category($record); + $queries = $grouppedqueries[$record->id] ?? []; + $category->load_queries_data($queries); + $categorywidget = new category($category, $this->context, true, $this->showcat, $this->hidecat, true, + false, $this->returnurl); + $categoriesdata[] = ['category' => $output->render($categorywidget)]; + } + + $addquerybutton = $managecategorybutton = ''; + if (has_capability('report/customsql:definequeries', $this->context)) { + $addquerybutton = $output->single_button(report_customsql_url('edit.php', ['returnurl' => $this->returnurl]), + get_string('addreport', 'report_customsql'), 'post', ['class' => 'mb-1']); + } + if (has_capability('report/customsql:managecategories', $this->context)) { + $managecategorybutton = $output->single_button(report_customsql_url('manage.php'), + get_string('managecategories', 'report_customsql')); + } + + $data = [ + 'expandable' => true, + 'expandcollapselinkattheend' => (count($this->categories) >= 5), + 'categories' => $categoriesdata, + 'addquerybutton' => $addquerybutton, + 'managecategorybutton' => $managecategorybutton + ]; + return $data; + } +} diff --git a/classes/output/renderer.php b/classes/output/renderer.php new file mode 100644 index 0000000..068b941 --- /dev/null +++ b/classes/output/renderer.php @@ -0,0 +1,68 @@ +. + +namespace report_customsql\output; + +use context; +use html_writer; +use moodle_url; +use plugin_renderer_base; +use stdClass; + +/** + * Ad-hoc database queries renderer class. + * + * @package report_customsql + * @copyright 2021 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends plugin_renderer_base { + + /** + * Output the standard action icons (edit, delete and back to list) for a report. + * + * @param stdClass $report the report. + * @param context $context context to use for permission checks. + * @param stdClass $category Category object. + * @return string HTML for report actions. + */ + public function render_report_actions(stdClass $report, stdClass $category, context $context):string { + if (has_capability('report/customsql:definequeries', $context)) { + $reporturl = report_customsql_url('view.php', ['id' => $report->id]); + $editaction = $this->action_link( + report_customsql_url('edit.php', ['id' => $report->id, 'returnurl' => $reporturl->out_as_local_url(false)]), + $this->pix_icon('t/edit', '') . ' ' . + get_string('editreportx', 'report_customsql', format_string($report->displayname))); + $deleteaction = $this->action_link( + report_customsql_url('delete.php', ['id' => $report->id, 'returnurl' => $reporturl->out_as_local_url(false)]), + $this->pix_icon('t/delete', '') . ' ' . + get_string('deletereportx', 'report_customsql', format_string($report->displayname))); + } + + $backtocategoryaction = $this->action_link( + report_customsql_url('category.php', ['id' => $category->id]), + $this->pix_icon('t/left', '') . + get_string('backtocategory', 'report_customsql', $category->name)); + + $context = [ + 'editaction' => $editaction, + 'deleteaction' => $deleteaction, + 'backtocategoryaction' => $backtocategoryaction + ]; + + return $this->render_from_template('report_customsql/query_actions', $context); + } +} diff --git a/classes/utils.php b/classes/utils.php index a2a6223..86d1985 100644 --- a/classes/utils.php +++ b/classes/utils.php @@ -25,6 +25,8 @@ */ class utils { + + /** * Return the current timestamp, or a fixed timestamp specified by an automated test. * @@ -38,4 +40,41 @@ public static function time(): int { return time(); } } + + /** + * Group the queries by category Id. + * + * @param array $queries Queries need to be grouped. + * @return array Pre-loaded Categories. + */ + public static function group_queries_by_category($queries) { + $grouppedqueries = []; + foreach ($queries as $query) { + if (isset($grouppedqueries[$query->categoryid])) { + $grouppedqueries[$query->categoryid][] = $query; + } else { + $grouppedqueries[$query->categoryid] = [$query]; + } + } + + return $grouppedqueries; + } + + public function get_queries_data($queries) { + + } + + /** + * Get queries for each type. + * + * @param array $queries Array of queries. + * @param string $type Type to filter. + * @return array All queries of type. + */ + public static function get_number_of_report_by_type(array $queries, string $type) { + return array_filter($queries, function($query) use ($type) { + return $query->runable == $type; + }, ARRAY_FILTER_USE_BOTH); + } + } diff --git a/delete.php b/delete.php index 6d911f2..cc233be 100644 --- a/delete.php +++ b/delete.php @@ -27,6 +27,7 @@ require_once($CFG->libdir . '/adminlib.php'); $id = required_param('id', PARAM_INT); +$returnurl = optional_param('returnurl', '', PARAM_LOCALURL); admin_externalpage_setup('report_customsql', '', ['id' => $id], '/report/customsql/delete.php'); @@ -38,13 +39,25 @@ print_error('invalidreportid', 'report_customsql', report_customsql_url('index.php'), $id); } +if ($returnurl) { + $returnurl = new moodle_url($returnurl); +} else { + $returnurl = report_customsql_url('category.php', ['id' => $report->categoryid]); +} + if (optional_param('confirm', false, PARAM_BOOL)) { $ok = $DB->delete_records('report_customsql_queries', array('id' => $id)); if (!$ok) { print_error('errordeletingreport', 'report_customsql', report_customsql_url('index.php')); } report_customsql_log_delete($id); - redirect(report_customsql_url('index.php')); + + // We can not return to the view report page because the report is deleted. + if (strpos($returnurl, 'report/customsql/view.php?id=' . $id)) { + redirect(report_customsql_url('category.php', ['id' => $report->categoryid])); + } else { + redirect($returnurl); + } } $runnableoptions = report_customsql_runable_options(); @@ -61,9 +74,9 @@ $runnableoptions[$report->runable])). $OUTPUT->confirm(get_string('deleteareyousure', 'report_customsql'), - new single_button(new moodle_url(report_customsql_url('delete.php'), - array('id' => $id, 'confirm' => 1)), get_string('yes')), - new single_button(new moodle_url(report_customsql_url('index.php')), - get_string('no'))). + new single_button(report_customsql_url('delete.php', + ['id' => $id, 'confirm' => 1, 'returnurl' => $returnurl->out_as_local_url(false)]), + get_string('yes')), + new single_button($returnurl, get_string('no'))). $OUTPUT->footer(); diff --git a/edit.php b/edit.php index f91232a..523c118 100644 --- a/edit.php +++ b/edit.php @@ -28,10 +28,15 @@ require_once($CFG->libdir . '/adminlib.php'); $id = optional_param('id', 0, PARAM_INT); +$categoryid = optional_param('categoryid', 0, PARAM_INT); +$returnurl = optional_param('returnurl', '', PARAM_LOCALURL); $urlparams = []; if ($id) { $urlparams['id'] = $id; } +if ($categoryid) { + $urlparams['categoryid'] = $categoryid; +} admin_externalpage_setup('report_customsql', '', $urlparams, '/report/customsql/edit.php'); $context = context_system::instance(); @@ -40,6 +45,12 @@ $relativeurl = 'edit.php'; $report = null; $reportquerysql = ''; +$params = []; + +if (!empty($returnurl)) { + $returnurl = new moodle_url($returnurl); + $params['returnurl'] = $returnurl->out_as_local_url(false); +} // Are we editing an existing report, or creating a new one. if ($id) { @@ -52,16 +63,31 @@ foreach ($queryparams as $param => $value) { $report->{'queryparam'.$param} = $value; } - $relativeurl .= '?id=' . $id; + $params['id'] = $id; + $category = $DB->get_record('report_customsql_categories', ['id' => $report->categoryid], '*', MUST_EXIST); + $PAGE->navbar->add(format_string($category->name), report_customsql_url('category.php', ['id' => $category->id])); + $PAGE->navbar->add(format_string($report->displayname)); +} else { + // If we add new query in a category, add a breadcrumb for it. + if ($categoryid) { + $category = $DB->get_record('report_customsql_categories', ['id' => $categoryid], '*', MUST_EXIST); + $PAGE->navbar->add(format_string($category->name), report_customsql_url('category.php', ['id' => $category->id])); + } + $PAGE->navbar->add(get_string('addreport', 'report_customsql')); } $querysql = optional_param('querysql', $reportquerysql, PARAM_RAW); $queryparams = report_customsql_get_query_placeholders_and_field_names($querysql); +$customdata = ['queryparams' => $queryparams, 'forcecategoryid' => $categoryid]; -$mform = new report_customsql_edit_form(report_customsql_url($relativeurl), $queryparams); +$mform = new report_customsql_edit_form(report_customsql_url($relativeurl, $params), $customdata); if ($mform->is_cancelled()) { - redirect(report_customsql_url('index.php')); + if ($returnurl) { + redirect($returnurl); + } else { + redirect(report_customsql_url('index.php')); + } } if ($newreport = $mform->get_data()) { @@ -118,14 +144,21 @@ report_customsql_log_edit($id); if ($newreport->runable == 'manual') { redirect(report_customsql_url('view.php?id=' . $id)); + } else if ($returnurl) { + redirect($returnurl); } else { redirect(report_customsql_url('index.php')); } } admin_externalpage_setup('report_customsql'); -echo $OUTPUT->header(). - $OUTPUT->heading(get_string('editingareport', 'report_customsql')); +echo $OUTPUT->header(); + +if ($id) { + echo $OUTPUT->heading(get_string('editingareport', 'report_customsql')); +} else { + echo $OUTPUT->heading(get_string('addingareport', 'report_customsql')); +} if ($report) { $report->description = array('text' => $report->description, 'format' => $report->descriptionformat); diff --git a/edit_form.php b/edit_form.php index b351462..c380fef 100644 --- a/edit_form.php +++ b/edit_form.php @@ -38,11 +38,16 @@ public function definition() { global $CFG; $mform = $this->_form; + $customdata = $this->_customdata; $categoryoptions = report_customsql_category_options(); $mform->addElement('select', 'categoryid', get_string('category', 'report_customsql'), $categoryoptions); - $catdefault = isset($categoryoptions[1]) ? 1 : key($categoryoptions); + if ($customdata['forcecategoryid'] && array_key_exists($customdata['forcecategoryid'], $categoryoptions)) { + $catdefault = $customdata['forcecategoryid']; + } else { + $catdefault = isset($categoryoptions[1]) ? 1 : key($categoryoptions); + } $mform->setDefault('categoryid', $catdefault); $mform->addElement('text', 'displayname', @@ -65,9 +70,9 @@ public function definition() { $mform->registerNoSubmitButton('verify'); $hasparameters = 0; - if (count($this->_customdata)) { + if ($customdata['queryparams']) { $mform->addElement('static', 'params', '', get_string('queryparams', 'report_customsql')); - foreach ($this->_customdata as $queryparam => $formparam) { + foreach ($customdata['queryparams'] as $queryparam => $formparam) { $type = report_customsql_get_element_type($queryparam); $mform->addElement($type, $formparam, $queryparam); if ($type == 'text') { diff --git a/index.php b/index.php index aa1d73e..138e493 100644 --- a/index.php +++ b/index.php @@ -44,79 +44,16 @@ require_capability('report/customsql:view', $context); $categories = $DB->get_records('report_customsql_categories', null, 'name ASC'); +$queries = $DB->get_records('report_customsql_queries'); $showcat = optional_param('showcat', 0, PARAM_INT); $hidecat = optional_param('hidecat', 0, PARAM_INT); -if (!$showcat && count($categories) == 1) { - $showcat = reset($categories)->id; -} +$returnurl = report_customsql_url('index.php'); -echo $OUTPUT->header(); - -$expandcollapsealllink = html_writer::link('#', get_string('expandall'), [ - 'class' => 'csql_expandcollapseall', - 'data-expandalltext' => get_string('expandall'), - 'data-collapsealltext' => get_string('collapseall')]); -$expandcollapsealllink = html_writer::div($expandcollapsealllink, 'csql_expandcollapseallcontainer'); -echo $expandcollapsealllink; - -foreach ($categories as $category) { - // Are we showing this cat? Default is hidden. - $show = $category->id == $showcat && $category->id != $hidecat ? 'shown' : 'hidden'; - - echo html_writer::start_tag('div', array('class' => 'csql_category csql_category' . $show)); - if ($category->id == $showcat) { - $params = array('hidecat' => $category->id); - } else { - $params = array('showcat' => $category->id); - } - $linkhref = new moodle_url('/report/customsql/index.php', $params); - $link = html_writer::link($linkhref, $category->name, array('class' => 'categoryname')); - - $manualreports = report_customsql_get_reports_for($category->id, 'manual'); - $dailyreports = report_customsql_get_reports_for($category->id, 'daily'); - $weeklyreports = report_customsql_get_reports_for($category->id, 'weekly'); - $monthlyreports = report_customsql_get_reports_for($category->id, 'monthly'); +$widget = new \report_customsql\output\index_page($categories, $queries, $context, $returnurl, $showcat, $hidecat); +$output = $PAGE->get_renderer('report_customsql'); - // Category content. - $cc = new stdClass(); - $cc->manual = count($manualreports); - $cc->daily = count($dailyreports); - $cc->weekly = count($weeklyreports); - $cc->monthly = count($monthlyreports); - $reportcounts = get_string('categorycontent', 'report_customsql', $cc); - - $reportcounts = html_writer::tag('span', $reportcounts, array('class' => 'reportcounts')); - echo $OUTPUT->heading($link . ' ' . $reportcounts); - - echo html_writer::start_tag('div', array('class' => 'csql_category_reports')); - if (empty($manualreports) && empty($dailyreports) && empty($weeklyreports) && empty($monthlyreports)) { - echo $OUTPUT->heading(get_string('availablereports', 'report_customsql'), 3). - html_writer::tag('p', get_string('noreportsavailable', 'report_customsql')); - } else { - report_customsql_print_reports_for($manualreports, 'manual'); - report_customsql_print_reports_for($dailyreports, 'daily'); - report_customsql_print_reports_for($weeklyreports, 'weekly'); - report_customsql_print_reports_for($monthlyreports, 'monthly'); - } - echo html_writer::end_tag('div'); - echo html_writer::end_tag('div'); -} - -if (count($categories) >= 5) { - // If there are many categores, show the link again. - echo $expandcollapsealllink; -} - -if (has_capability('report/customsql:definequeries', $context)) { - echo $OUTPUT->single_button(report_customsql_url('edit.php'), - get_string('addreport', 'report_customsql'), 'post', ['class' => 'mb-1']); -} -if (has_capability('report/customsql:managecategories', $context)) { - echo $OUTPUT->single_button(report_customsql_url('manage.php'), - get_string('managecategories', 'report_customsql')); -} +echo $OUTPUT->header(); -// Initialise the expand/collapse JavaScript. -$PAGE->requires->js_call_amd('report_customsql/reportcategories', 'init'); +echo $output->render($widget); echo $OUTPUT->footer(); diff --git a/lang/en/report_customsql.php b/lang/en/report_customsql.php index 849ee01..e56d997 100644 --- a/lang/en/report_customsql.php +++ b/lang/en/report_customsql.php @@ -24,6 +24,7 @@ $string['addcategory'] = 'Add a new category'; $string['addcategorydesc'] = 'To change a report\'s category, you must edit that report. Here you can edit category texts, delete a category or add a new category.'; +$string['addingareport'] = 'Adding an ad-hoc database query'; $string['addreport'] = 'Add a new query'; $string['addreportcategory'] = 'Add a new category for reports'; $string['anyonewhocanveiwthisreport'] = 'Anyone who can view this report (report/customsql:view)'; @@ -35,6 +36,7 @@ $string['availablereports'] = 'On-demand queries'; $string['availableto'] = 'Available to {$a}.'; $string['backtoreportlist'] = 'Back to the list of queries'; +$string['backtocategory'] = 'Back to category \'{$a}\''; $string['category'] = 'Category'; $string['categorycontent'] = '({$a->manual} on-demand, {$a->daily} daily, {$a->weekly} weekly, {$a->monthly} monthly)'; $string['categoryexists'] = 'Category names must be unique, this name already exists'; @@ -172,6 +174,7 @@ $string['runquery'] = 'Run query'; $string['schedulednote'] = 'These queries are automatically run on the first day of each week or month, to report on the previous week or month. These links let you view the results that has already been accumulated.'; $string['scheduledqueries'] = 'Scheduled queries'; +$string['showonlythiscategory'] = 'Show only {$a}'; $string['startofweek'] = 'Day to run weekly reports'; $string['startofweek_default'] = 'Use site calendar start of week ({$a})'; $string['startofweek_desc'] = 'This is the day which should be considered the first day of the week, for weekly scheduled reports.'; diff --git a/locallib.php b/locallib.php index b90951f..9470a66 100644 --- a/locallib.php +++ b/locallib.php @@ -245,9 +245,15 @@ function report_customsql_substitute_user_token($sql, $userid) { return str_replace('%%USERID%%', $userid, $sql); } -function report_customsql_url($relativeurl) { - global $CFG; - return $CFG->wwwroot.'/report/customsql/'.$relativeurl; +/** + * Create url to $relativeurl. + * + * @param string $relativeurl Relative url. + * @param array $params Parameter for url. + * @return moodle_url the relative url. + */ +function report_customsql_url($relativeurl, $params = []) { + return new moodle_url('/report/customsql/' . $relativeurl, $params); } function report_customsql_capability_options() { @@ -450,34 +456,6 @@ function report_customsql_time_note($report, $tag) { return html_writer::tag($tag, $note, array('class' => 'admin_note')); } -/** - * Output the standard action icons (edit, delete and back to list) for a report. - * - * @param stdClass $report the report. - * @param context $context context to use for permission checks. - */ -function display_report_actions(stdClass $report, context $context): void { - global $OUTPUT; - - if (has_capability('report/customsql:definequeries', $context)) { - echo html_writer::tag('p', - $OUTPUT->action_link( - new moodle_url(report_customsql_url('edit.php'), ['id' => $report->id]), - $OUTPUT->pix_icon('t/edit', '') . ' ' . - get_string('editreportx', 'report_customsql', format_string($report->displayname)))); - echo html_writer::tag('p', - $OUTPUT->action_link( - new moodle_url(report_customsql_url('delete.php'), ['id' => $report->id]), - $OUTPUT->pix_icon('t/delete', '') . ' ' . - get_string('deletereportx', 'report_customsql', format_string($report->displayname)))); - } - - echo html_writer::tag('p', - $OUTPUT->action_link( - new moodle_url(report_customsql_url('index.php')), - $OUTPUT->pix_icon('t/left', '') . - get_string('backtoreportlist', 'report_customsql'))); -} function report_customsql_pretify_column_names($row, $querysql) { $colnames = []; diff --git a/manage.php b/manage.php index e4290e0..803d433 100644 --- a/manage.php +++ b/manage.php @@ -34,6 +34,7 @@ admin_externalpage_setup('report_customsql', '', null, '/report/customsql/manage.php'); $context = context_system::instance(); require_capability('report/customsql:managecategories', $context); +$PAGE->navbar->add(get_string('managecategories', 'report_customsql'), report_customsql_url('manage.php')); echo $OUTPUT->header() . $OUTPUT->heading(get_string('managecategories', 'report_customsql')); @@ -45,8 +46,9 @@ foreach ($categories as $category) { echo html_writer::start_tag('div'); - echo ' ' . html_writer::tag('span', format_string($category->name) . ' ', array('class' => 'report_customsql')) . - html_writer::tag('a', $OUTPUT->pix_icon('t/edit', get_string('edit')), + echo ' ' . html_writer::link(report_customsql_url('category.php', ['id' => $category->id]), + format_string($category->name) . ' ', array('class' => 'report_customsql')) . + html_writer::tag('a', $OUTPUT->pix_icon('t/edit', get_string('edit')), array('title' => get_string('editcategoryx', 'report_customsql', format_string($category->name)), 'href' => report_customsql_url('addcategory.php?id=' . $category->id))); diff --git a/styles.css b/styles.css index d2dd2bf..7deeb80 100644 --- a/styles.css +++ b/styles.css @@ -2,7 +2,8 @@ font-size: 0.8em; color: #c10031; } -.reportcounts { +.reportcounts, +body.path-admin-report-customsql a.view-category { font-weight: normal; font-size: 0.65em; } diff --git a/templates/category.mustache b/templates/category.mustache new file mode 100644 index 0000000..65792f2 --- /dev/null +++ b/templates/category.mustache @@ -0,0 +1,93 @@ +{{! + 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 report_customsql/category + + Template to render a category. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * name Category name. + + Example context (json): + { + "id": 1, + "name": "category 1", + "expandable": false, + "show": "shown", + "showonlythislink": "true", + "url": "https://example.com/report_customsql/category.php?id=1", + "linkref": "https://example.com/report_customsql/index.php?hidecat=1", + "statistic": { + "manual": 1, + "daily": 2, + "weekly": 3, + "monthly": 4 + }, + "querygroups": [] + } +}} + +
+

+ {{#expandable}} + {{name}} + {{/expandable}} + {{^expandable}} + {{{name}}} + {{/expandable}} + {{#statistic}} + + {{#str}}categorycontent, report_customsql, { + "manual": {{#quote}}{{manual}}{{/quote}}, + "daily": {{#quote}}{{daily}}{{/quote}}, + "weekly": {{#quote}}{{weekly}}{{/quote}}, + "monthly": {{#quote}}{{monthly}}{{/quote}} + }{{/str}} + + {{/statistic}} + {{#showonlythislink}} + {{#str}}showonlythiscategory, report_customsql, {{name}}{{/str}} + {{/showonlythislink}} +

+
+ {{#querygroups}} + {{#type}} +

+ {{title}} + {{{helpicon}}} +

+ {{/type}} + {{#queries}} + {{{categoryqueryitem}}} + {{/queries}} + {{/querygroups}} + {{^querygroups}} +

{{#str}}availablereports, report_customsql{{/str}}

+

{{#str}}noreportsavailable, report_customsql{{/str}}

+ {{/querygroups}} +
+
+ +{{#addquerybutton}} + {{{addquerybutton}}} +{{/addquerybutton}} diff --git a/templates/category_query.mustache b/templates/category_query.mustache new file mode 100644 index 0000000..ea745b9 --- /dev/null +++ b/templates/category_query.mustache @@ -0,0 +1,67 @@ +{{! + 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 report_customsql/category_query + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * displayname string Display name. + * url string Url to the query. + * timenote string to say if there are identity fields to show. + * capability string Capability text. + + Example context (json): + { + "displayname": "Query 1", + "url": "http://example.com/report/customsql/view.php?id=1", + "canedit": true, + "timenote": "This query was last run on Monday, 30 August 2021, 5:30 PM.", + "capability": "Only administrators (moodle/site:config)", + "editbutton": { + "url": "http://example.com/report/customsql/edit.php?id=1", + "img": "Edit button img" + }, + "deletebutton": { + "url": "http://example.com/green/report/customsql/delete.php?id=1", + "img": "Delete button img" + } + } +}} + +

+ {{{displayname}}} {{{timenote}}} + {{#canedit}} + {{#str}}availableto, report_customsql, {{capability}} {{/str}} + {{#editbutton}} + + {{{img}}} + + {{/editbutton}} + {{#deletebutton}} + + {{{img}}} + + {{/deletebutton}} + {{/ canedit }} +

diff --git a/templates/expand_collapse_link.mustache b/templates/expand_collapse_link.mustache new file mode 100644 index 0000000..190339e --- /dev/null +++ b/templates/expand_collapse_link.mustache @@ -0,0 +1,38 @@ +{{! + 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 report_customsql/expand_collapse_link + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + + } +}} + + diff --git a/templates/index_page.mustache b/templates/index_page.mustache new file mode 100644 index 0000000..1d83d3b --- /dev/null +++ b/templates/index_page.mustache @@ -0,0 +1,57 @@ +{{! + 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 report_customsql/index_page + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * none + + Example context (json): + { + "expandable": true, + "expandcollapselinkattheend": true, + "categories": [] + } +}} +{{#expandable}} + {{>report_customsql/expand_collapse_link}} +{{/expandable}} + +{{#categories}} + {{{category}}} +{{/categories}} + +{{#expandcollapselinkattheend}} + {{>report_customsql/expand_collapse_link}} +{{/expandcollapselinkattheend}} + +{{{addquerybutton}}} +{{{managecategorybutton}}} + +{{#js}} + require(['report_customsql/reportcategories'], function(reportcategories) { + reportcategories.init(); + }); +{{/js}} diff --git a/templates/query_actions.mustache b/templates/query_actions.mustache new file mode 100644 index 0000000..8cbc58b --- /dev/null +++ b/templates/query_actions.mustache @@ -0,0 +1,45 @@ +{{! + 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 report_customsql/query_actions + + Moodle template for the list of valid options in an autocomplate form element. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + "editaction": "Edit query 'Test'", + "deleteaction": "Delete query 'Test'", + "backtocategoryaction": "Back to category 'Miscellaneous'" + } +}} +{{#editaction}} +

{{{editaction}}}

+{{/editaction}} + +{{#deleteaction}} +

{{{deleteaction}}}

+{{/deleteaction}} + +{{#backtocategoryaction}} +

{{{backtocategoryaction}}}

+{{/backtocategoryaction}} diff --git a/tests/behat/report_customsql.feature b/tests/behat/report_customsql.feature index 9af6138..b5d9e9a 100644 --- a/tests/behat/report_customsql.feature +++ b/tests/behat/report_customsql.feature @@ -110,6 +110,25 @@ Feature: Ad-hoc database queries report And I should not see "Expand all" And I should see "Collapse all" + Scenario: View a category and add an ad-hoc database query inside a category + Given the custom sql report category "Category 1" exists: + And the custom sql report category "Category 2" exists: + When I log in as "admin" + And I navigate to "Reports > Ad-hoc database queries" in site administration + And I follow "Show only Category 2" + Then I should see "Category 2" + And I should see "No queries available" + And I press "Add a new query" + And the field "Category" matches value "Category 2" + And I set the following fields to these values: + | Query name | Test query | + | Query SQL | SELECT * FROM {config} WHERE name = 'version' | + And I press "Save changes" + And I should see "Test query" + And I should see "Category 2" in the "div#page-navbar" "css_element" + And I follow "Back to category 'Category 2'" + And I should see "Test query" + Scenario: Delete an empty Ad-hoc database queries category Given the custom sql report category "Special reports" exists: When I log in as "admin" diff --git a/tests/report_customsql_local_category_testcase.php b/tests/report_customsql_local_category_testcase.php new file mode 100644 index 0000000..f32aa0c --- /dev/null +++ b/tests/report_customsql_local_category_testcase.php @@ -0,0 +1,87 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +use report_customsql\local\category; + +global $CFG; +require_once($CFG->dirroot . '/report/customsql/locallib.php'); + +/** + * Tests for the report_customsql\local\category. + * + * @package report_customsql + * @copyright 2021 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class report_customsql_local_category_testcase extends advanced_testcase { + /** + * Test create category. + */ + public function test_create_category() { + $this->resetAfterTest(); + $fakerecord = (object) [ + 'id' => 1, + 'name' => 'Category 1' + ]; + + $category = new category($fakerecord); + + $this->assertEquals(1, $category->get_id()); + $this->assertEquals('Category 1', $category->get_name()); + $this->assertStringContainsString('category.php?id=1', $category->get_url()); + } + + /** + * Test create category. + */ + public function test_load_queries_data() { + $this->resetAfterTest(); + $fakerecord = (object) [ + 'id' => 1, + 'name' => 'Category 1' + ]; + + $fakequeries = [ + (object) [ + 'id' => 1, + 'displayname' => 'Q1', + 'runable' => 'manual' + ], + (object) [ + 'id' => 2, + 'displayname' => 'Q2', + 'runable' => 'manual' + ], + (object) [ + 'id' => 3, + 'displayname' => 'Q3', + 'runable' => 'daily' + ] + ]; + + $category = new category($fakerecord); + $category->load_queries_data($fakequeries); + + $this->assertEquals(2, $category->get_statistic()['manual']); + $this->assertEquals(1, $category->get_statistic()['daily']); + $this->assertEquals(0, $category->get_statistic()['weekly']); + $this->assertEquals(0, $category->get_statistic()['monthly']); + // The result contains 2 elements: manual and daily. + $this->assertEquals(2, count($category->get_queries_data()[1])); + } +} diff --git a/tests/report_customsql_local_query_testcase.php b/tests/report_customsql_local_query_testcase.php new file mode 100644 index 0000000..d7363cf --- /dev/null +++ b/tests/report_customsql_local_query_testcase.php @@ -0,0 +1,62 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +use report_customsql\local\query; + +global $CFG; +require_once($CFG->dirroot . '/report/customsql/locallib.php'); + + +/** + * Tests for the report_customsql\local\query. + * + * @package report_customsql + * @copyright 2021 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class report_customsql_local_query_testcase extends advanced_testcase { + /** + * Test create query. + */ + public function test_create_query() { + $this->resetAfterTest(); + $this->setAdminUser(); + + $fakerecord = (object) [ + 'id' => 1, + 'displayname' => 'Query 1', + 'runable' => 'daily', + 'capability' => 'moodle/site:config', + 'lastrun' => 0 + ]; + + $query = new query($fakerecord); + + $this->assertEquals(1, $query->get_id()); + $this->assertEquals('Query 1', $query->get_displayname()); + $this->assertStringContainsString('view.php?id=1', $query->get_url()); + $this->assertStringContainsString('edit.php?id=1', $query->get_edit_url()); + $this->assertStringContainsString('delete.php?id=1', $query->get_delete_url()); + $this->assertEquals('This query has not yet been run.', + $query->get_time_note()); + $this->assertEquals('Only administrators (moodle/site:config)', $query->get_capability_string()); + // Admin user should have capability to edit and view queries. + $this->assertEquals(true, $query->can_edit(context_system::instance())); + $this->assertEquals(true, $query->can_view(context_system::instance())); + } +} diff --git a/view.php b/view.php index ebfe8b8..de615e1 100644 --- a/view.php +++ b/view.php @@ -34,6 +34,8 @@ print_error('invalidreportid', 'report_customsql', report_customsql_url('index.php'), $id); } +$category = $DB->get_record('report_customsql_categories', ['id' => $report->categoryid], '*', MUST_EXIST); + $embed = optional_param('embed', 0, PARAM_BOOL); $urlparams['embed'] = $embed; @@ -41,12 +43,15 @@ admin_externalpage_setup('report_customsql', '', $urlparams, '/report/customsql/view.php', ['pagelayout' => 'report']); $PAGE->set_title(format_string($report->displayname)); +$PAGE->navbar->add(format_string($category->name), report_customsql_url('category.php', ['id' => $report->categoryid])); $PAGE->navbar->add(format_string($report->displayname)); if ($embed) { $PAGE->set_pagelayout('embedded'); } +$output = $PAGE->get_renderer('report_customsql'); + $context = context_system::instance(); if (!empty($report->capability)) { require_capability($report->capability, $context); @@ -101,7 +106,6 @@ admin_externalpage_setup('report_customsql', '', $urlparams, '/report/customsql/view.php'); $PAGE->set_title(format_string($report->displayname)); - $PAGE->navbar->add(format_string($report->displayname)); echo $OUTPUT->header(); echo $OUTPUT->heading(format_string($report->displayname)); if (!html_is_blank($report->description)) { @@ -109,7 +113,7 @@ } $mform->display(); - display_report_actions($report, $context); + echo $output->render_report_actions($report, $category, $context); echo $OUTPUT->footer(); die; @@ -208,19 +212,19 @@ echo report_customsql_time_note($report, 'p'); echo $OUTPUT->download_dataformat_selector(get_string('downloadthisreportas', 'report_customsql'), - new moodle_url(report_customsql_url('download.php')), 'dataformat', ['id' => $id, 'timestamp' => $csvtimestamp]); + report_customsql_url('download.php'), 'dataformat', ['id' => $id, 'timestamp' => $csvtimestamp]); } } if (!empty($queryparams)) { echo html_writer::tag('p', $OUTPUT->action_link( - new moodle_url(report_customsql_url('view.php'), ['id' => $id]), + report_customsql_url('view.php', ['id' => $id]), $OUTPUT->pix_icon('t/editstring', '') . ' ' . get_string('changetheparameters', 'report_customsql'))); } -display_report_actions($report, $context); +echo $output->render_report_actions($report, $category, $context); $archivetimes = report_customsql_get_archive_times($report); if (count($archivetimes) > 1) { @@ -233,8 +237,8 @@ echo html_writer::tag('b', $formattedtime); } else { echo html_writer::tag('a', $formattedtime, - array('href' => new moodle_url(report_customsql_url('view.php'), - array('id' => $id, 'timestamp' => $time)))); + array('href' => report_customsql_url('view.php', + ['id' => $id, 'timestamp' => $time]))); } echo ''; }