From 40d6efb81f8e73925d332d178c6575c86767c6b7 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Thu, 29 Feb 2024 10:14:15 +0000 Subject: [PATCH 01/11] Accessibility: Required form fields should use default message not 'Required' #768430 --- categoryadd_form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/categoryadd_form.php b/categoryadd_form.php index 33bf63f..a84b5de 100644 --- a/categoryadd_form.php +++ b/categoryadd_form.php @@ -54,7 +54,7 @@ public function definition() { } $mform->addElement('text', 'name', get_string('categoryname'), ['size' => '30']); - $mform->addRule('name', get_string('required'), 'required', null); + $mform->addRule('name', null, 'required', null); $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->setDefault('name', ''); $mform->setType('name', PARAM_TEXT); From 718ec5d4f99009dbecf8c46a63c1c404a5ebf8f7 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Tue, 9 Jul 2024 17:06:12 +0100 Subject: [PATCH 02/11] Behat fixes to work with removed Moodle plugins #786405 --- tests/behat/report_customsql.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/behat/report_customsql.feature b/tests/behat/report_customsql.feature index 6c3e893..052702e 100644 --- a/tests/behat/report_customsql.feature +++ b/tests/behat/report_customsql.feature @@ -237,7 +237,7 @@ Feature: Ad-hoc database queries report And I set the following fields to these values: | Query name | Test query | | Description | Query that tries to return 2 rows. | - | Query SQL | SELECT * FROM {config_plugins} WHERE name = 'version' AND plugin IN ('mod_quiz', 'mod_wiki') | + | Query SQL | SELECT * FROM {config_plugins} WHERE name = 'version' AND plugin IN ('mod_quiz', 'mod_page') | And I press "Save changes" Then I should see "Test query" And I should see "This query reached the limit of 1 rows. Some rows may have been omitted from the end." From d30887f436473d9d96160789b05f9c6759452b48 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Tue, 6 Aug 2024 17:03:59 +0100 Subject: [PATCH 03/11] Theme: Remove unnecessary references to theme_ou, mod_subpage, and other removed plugins #709099 --- tests/report_test.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/report_test.php b/tests/report_test.php index d52efcf..cd24ec8 100644 --- a/tests/report_test.php +++ b/tests/report_test.php @@ -336,28 +336,28 @@ public function test_report_customsql_pretify_column_names_issue(): void { $row = new \stdClass(); $row->website = 'B747-19B'; $row->website_link_url = '%%WWWROOT%%/course/view.php%%Q%%id=123'; - $row->subpage = 'Self-referential nightmare'; - $row->subpage_link_url = '%%WWWROOT%%/mod/subpage/view.php%%Q%%id=4567'; + $row->frog = 'Self-referential nightmare'; + $row->frog_link_url = '%%WWWROOT%%/mod/frog/view.php%%Q%%id=4567'; $query = " SELECT c.shortname AS Website, '%%WWWROOT%%/course/view.php%%Q%%id=' || c.id AS Website_link_url, - s.name AS Subpage, - '%%WWWROOT%%/mod/subpage/view.php%%Q%%id=' || cm.id AS Subpage_link_url + s.name AS Frog, + '%%WWWROOT%%/mod/frog/view.php%%Q%%id=' || cm.id AS Frog_link_url - FROM {subpage_sections} ss - JOIN {subpage} s ON s.id = ss.subpageid + FROM {frog_sections} ss + JOIN {frog} s ON s.id = ss.subpageid JOIN {course_sections} cs ON cs.id = ss.sectionid JOIN {course_modules} cm ON cm.instance = s.id JOIN {modules} mod ON mod.id = cm.module JOIN {course} c ON c.id = cm.course - WHERE mod.name = 'subpage' + WHERE mod.name = 'frog' AND ',' || cs.sequence || ',' LIKE '%,' || cm.id || ',%' - ORDER BY website, subpage"; + ORDER BY website, frog"; - $this->assertEquals(['Website', 'Website link url', 'Subpage', 'Subpage link url'], + $this->assertEquals(['Website', 'Website link url', 'Frog', 'Frog link url'], report_customsql_pretify_column_names($row, $query)); } From 3de615af6f463c1f79fb2061d05339c00b1c40ca Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 29 May 2025 16:02:36 +0100 Subject: [PATCH 04/11] Ad-hoc queries: behat navigation URLs #865695 --- tests/behat/behat_report_customsql.php | 19 ++++++++++++++++ tests/behat/report_customsql.feature | 31 +++++++++----------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/tests/behat/behat_report_customsql.php b/tests/behat/behat_report_customsql.php index c630398..ad3a4ba 100644 --- a/tests/behat/behat_report_customsql.php +++ b/tests/behat/behat_report_customsql.php @@ -39,6 +39,25 @@ */ class behat_report_customsql extends behat_base { + /** + * Convert page names to URLs for steps like 'When I am on the "[page name]" page'. + * + * Recognised page names are: + * | report index | the list of all reports. | + * + * @param string $page name of the page, with the component name removed e.g. 'Admin notification'. + * @return moodle_url the corresponding URL. + * @throws Exception with a meaningful error message if the specified page cannot be found. + */ + protected function resolve_page_url(string $page): moodle_url { + switch (strtolower($page)) { + case 'report index': + return new moodle_url('/report/customsql/index.php'); + default: + throw new Exception('Unrecognised quiz page type "' . $page . '."'); + } + } + /** * Create a new report in the database. * diff --git a/tests/behat/report_customsql.feature b/tests/behat/report_customsql.feature index 052702e..a4c1911 100644 --- a/tests/behat/report_customsql.feature +++ b/tests/behat/report_customsql.feature @@ -5,9 +5,8 @@ Feature: Ad-hoc database queries report I need to be able to run arbitrary queries against the database Scenario: Create an Ad-hoc database query - When I log in as "admin" - And the Ad-hoc database queries thinks the time is "2021-05-10 18:00:00" - And I navigate to "Reports > Ad-hoc database queries" in site administration + Given the Ad-hoc database queries thinks the time is "2021-05-10 18:00:00" + When I am on the "report_customsql > report index" page logged in as admin And I press "Add a new query" And I set the following fields to these values: | Query name | Test query | @@ -36,9 +35,8 @@ Feature: Ad-hoc database queries report | timecreated | ## 2021-05-10 18:00:00 ## | | timemodified | ## 2021-05-10 18:00:00 ## | | usermodified | mamager1 | - When I log in as "admin" And the Ad-hoc database queries thinks the time is "2021-05-10 19:00:00" - And I navigate to "Reports > Ad-hoc database queries" in site administration + When I am on the "report_customsql > report index" page logged in as admin And I follow "Edit query 'Test query'" And the following fields match these values: | Query name | Test query | @@ -62,8 +60,7 @@ Feature: Ad-hoc database queries report | name | Test query | | description | Display the Moodle internal version number. | | querysql | SELECT * FROM {config} WHERE name = 'version' | - When I log in as "admin" - And I navigate to "Reports > Ad-hoc database queries" in site administration + When I am on the "report_customsql > report index" page logged in as admin And I follow "Delete query 'Test query'" And I press "Yes" Then I should not see "Test query" @@ -84,8 +81,7 @@ Feature: Ad-hoc database queries report Then downloading custom sql report "Test query" returns a file with headers "id,name,value" Scenario: Create an Ad-hoc database queries category - When I log in as "admin" - And I navigate to "Reports > Ad-hoc database queries" in site administration + When I am on the "report_customsql > report index" page logged in as admin And I press "Manage report categories" And I press "Add a new category" And I set the field "Category name" to "Category 1" @@ -95,8 +91,7 @@ Feature: Ad-hoc database queries report @javascript Scenario: Create an Ad-hoc database query in a custom category Given the custom sql report category "Special reports" exists: - When I log in as "admin" - And I navigate to "Reports > Ad-hoc database queries" in site administration + When I am on the "report_customsql > report index" page logged in as admin And I follow "Special reports" And I should see "No queries available" And I press "Add a new query" @@ -105,7 +100,7 @@ Feature: Ad-hoc database queries report | Query name | Test query | | Query SQL | SELECT * FROM {config} WHERE name = 'version' | And I press "Save changes" - And I navigate to "Reports > Ad-hoc database queries" in site administration + And I am on the "report_customsql > report index" page And I follow "Special reports" # Also test expand/collapse while we are here. Then I should see "Test query" @@ -120,8 +115,7 @@ Feature: Ad-hoc database queries report 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 + When I am on the "report_customsql > report index" page logged in as admin And I follow "Show only Category 2" Then I should see "Category 2" And I should see "No queries available" @@ -138,8 +132,7 @@ Feature: Ad-hoc database queries report Scenario: Delete an empty Ad-hoc database queries category Given the custom sql report category "Special reports" exists: - When I log in as "admin" - And I navigate to "Reports > Ad-hoc database queries" in site administration + When I am on the "report_customsql > report index" page logged in as admin And I press "Manage report categories" And I follow "Delete category 'Special reports'" And I press "Yes" @@ -178,8 +171,7 @@ Feature: Ad-hoc database queries report And I should see "This report has 1 rows." Scenario: Create and run an Ad-hoc database query that has parameters - When I log in as "admin" - And I navigate to "Reports > Ad-hoc database queries" in site administration + When I am on the "report_customsql > report index" page logged in as admin And I press "Add a new query" And I set the following fields to these values: | Query name | Find user | @@ -231,8 +223,7 @@ Feature: Ad-hoc database queries report Scenario: Test reporting when a query exceeds the limit Given the following config values are set as admin: | querylimitdefault | 1 | report_customsql | - When I log in as "admin" - And I navigate to "Reports > Ad-hoc database queries" in site administration + When I am on the "report_customsql > report index" page logged in as admin And I press "Add a new query" And I set the following fields to these values: | Query name | Test query | From e7816005931f628195214cd7daa4a505391b438c Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 29 May 2025 16:06:01 +0100 Subject: [PATCH 05/11] Ad-hoc queries: tidy up existing code #865695 This should not change any behaviour. --- classes/local/category.php | 23 ++++++++++++++++++----- classes/output/index_page.php | 6 +++--- classes/utils.php | 14 -------------- view.php | 4 +--- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/classes/local/category.php b/classes/local/category.php index e73c4d6..0625ab3 100644 --- a/classes/local/category.php +++ b/classes/local/category.php @@ -51,18 +51,18 @@ public function __construct(\stdClass $record) { /** * Load queries of category from records. * - * @param array $queries Records to load. + * @param \stdClass[] $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) { + $filteredqueries = self::get_reports_of_a_particular_runtype($queries, $type); + $statistic[$type] = count($filteredqueries); + if ($filteredqueries) { $queriesdata[] = [ 'type' => $type, - 'queries' => $fitleredqueries, + 'queries' => $filteredqueries, ]; } } @@ -70,6 +70,19 @@ public function load_queries_data(array $queries): void { $this->statistic = $statistic; } + /** + * Get queries for each type. + * + * @param \stdClass[] $queries Array of queries. + * @param string $type Type to filter. + * @return \stdClass[] All queries of type. + */ + public static function get_reports_of_a_particular_runtype(array $queries, string $type) { + return array_filter($queries, function($query) use ($type) { + return $query->runable == $type; + }, ARRAY_FILTER_USE_BOTH); + } + /** * Get category ID. * diff --git a/classes/output/index_page.php b/classes/output/index_page.php index b84fa80..0a4a94b 100644 --- a/classes/output/index_page.php +++ b/classes/output/index_page.php @@ -52,9 +52,9 @@ class index_page implements renderable, templatable { /** 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 \stdClass[] $categories Categories for renderer. + * @param \stdClass[] $queries Queries for renderer. + * @param context $context Context to check the capability (will be system context). * @param moodle_url $returnurl Return url for edit/delete link. * @param int $showcat Showing Category Id. * @param int $hidecat Hiding Category Id. diff --git a/classes/utils.php b/classes/utils.php index dcb27b4..ed60440 100644 --- a/classes/utils.php +++ b/classes/utils.php @@ -61,18 +61,4 @@ public static function group_queries_by_category($queries) { 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/view.php b/view.php index 2c23af6..0048635 100644 --- a/view.php +++ b/view.php @@ -55,9 +55,7 @@ $output = $PAGE->get_renderer('report_customsql'); $context = context_system::instance(); -if (!empty($report->capability)) { - require_capability($report->capability, $context); -} +require_capability($report->capability ?? 'moodle/site:config', $context); report_customsql_log_view($id); From 02e1ecf63a9e20436133a5eadc88162c193c61ec Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 29 May 2025 15:36:55 +0100 Subject: [PATCH 06/11] Ad-hoc queries: only show users queries they can see #865695 This also fixes a bug in the Behat generator, where the option to create a query with a particular capability was broken. --- classes/local/category.php | 13 +++++++++ tests/behat/behat_report_customsql.php | 14 ++++++---- tests/behat/report_customsql.feature | 37 ++++++++++++++++++++++++++ tests/local/category_test.php | 2 ++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/classes/local/category.php b/classes/local/category.php index 0625ab3..bc011e7 100644 --- a/classes/local/category.php +++ b/classes/local/category.php @@ -58,6 +58,7 @@ public function load_queries_data(array $queries): void { $queriesdata = []; foreach (report_customsql_runable_options() as $type => $description) { $filteredqueries = self::get_reports_of_a_particular_runtype($queries, $type); + $filteredqueries = self::filter_reports_by_capability($filteredqueries); $statistic[$type] = count($filteredqueries); if ($filteredqueries) { $queriesdata[] = [ @@ -83,6 +84,18 @@ public static function get_reports_of_a_particular_runtype(array $queries, strin }, ARRAY_FILTER_USE_BOTH); } + /** + * Given an array of qureries, remove any that the current user cannot access. + * + * @param \stdClass[] $queries Array of queries. + * @return \stdClass[] queries the current user is allowed to see. + */ + public static function filter_reports_by_capability(array $queries) { + return array_filter($queries, function($query) { + return has_capability($query->capability ?? 'moodle/site:config', \context_system::instance()); + }, ARRAY_FILTER_USE_BOTH); + } + /** * Get category ID. * diff --git a/tests/behat/behat_report_customsql.php b/tests/behat/behat_report_customsql.php index ad3a4ba..49a1f06 100644 --- a/tests/behat/behat_report_customsql.php +++ b/tests/behat/behat_report_customsql.php @@ -72,7 +72,8 @@ protected function resolve_page_url(string $page): moodle_url { * @param TableNode $data Supplied data */ public function the_following_custom_sql_report_exists(TableNode $data) { - global $DB; + global $CFG, $DB; + require_once($CFG->dirroot . '/report/customsql/locallib.php'); $report = $data->getRowsHash(); @@ -112,14 +113,17 @@ public function the_following_custom_sql_report_exists(TableNode $data) { } // Capability. - if (isset($report['capability']) && - !in_array($report['capability'], report_customsql_capability_options())) { - throw new Exception('Capability ' . $report['capability'] . ' is not a valid choice.'); + if (isset($report['capability'])) { + // If a capability was passed in, check it is valid. + if (!isset(report_customsql_capability_options()[$report['capability']])) { + throw new Exception('Capability ' . $report['capability'] . ' is not a valid choice.'); + } } else { + // Otherwise use a default. $report['capability'] = 'moodle/site:config'; } - // Capability. + // Runnable. if (isset($report['runable']) && !in_array($report['runable'], report_customsql_runable_options())) { throw new Exception('Invalid runable value ' . $report['capability'] . '.'); diff --git a/tests/behat/report_customsql.feature b/tests/behat/report_customsql.feature index a4c1911..a19e4aa 100644 --- a/tests/behat/report_customsql.feature +++ b/tests/behat/report_customsql.feature @@ -139,6 +139,43 @@ Feature: Ad-hoc database queries report Then I should not see "Special reports" And "Delete category 'Miscellaneous'" "link" should not exist + Scenario: Non-admins can only see some queries + Given the following custom sql report exists: + | name | Report for admin and manager | + | querysql | SELECT * FROM {config} WHERE name = 'version' | + | capability | report/customsql:view | + And the following custom sql report exists: + | name | Report for admin only | + | querysql | SELECT * FROM {config} WHERE name = 'version' | + | capability | moodle/site:config | + And the following "role capability" exists: + | role | manager | + | report/customsql:view | allow | + | moodle/site:config | inherit | + And the following "users" exist: + | username | + | manager | + And the following "role assigns" exist: + | user | role | contextlevel | reference | + | manager | manager | System | | + + # Check manager can only see one of the queries. + When I am on the "report_customsql > report index" page logged in as manager + Then I should see "Report for admin and manager" + And I should not see "Report for admin only" + And I follow "Show only Miscellaneous" + And I should see "Report for admin and manager" + And I should not see "Report for admin only" + And I log out + + # Check admin can see both. + And I am on the "report_customsql > report index" page logged in as admin + And I should see "Report for admin and manager" + And I should see "Report for admin only" + And I follow "Show only Miscellaneous" + And I should see "Report for admin and manager" + And I should see "Report for admin only" + Scenario: A query that uses the various auto-formatting options Given the custom sql report "Formatting test" exists with SQL: """ diff --git a/tests/local/category_test.php b/tests/local/category_test.php index 7bb72bb..d84b596 100644 --- a/tests/local/category_test.php +++ b/tests/local/category_test.php @@ -52,6 +52,8 @@ public function test_create_category(): void { */ public function test_load_queries_data(): void { $this->resetAfterTest(); + $this->setAdminUser(); + $fakerecord = (object) [ 'id' => 1, 'name' => 'Category 1', From c384b9a53cbd7a6aa2fbaa07db1a60563db51b16 Mon Sep 17 00:00:00 2001 From: Anupama Sarjoshi Date: Thu, 24 Jul 2025 13:16:25 +0100 Subject: [PATCH 07/11] Update CI workflow --- .github/workflows/ci.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e624585..37e6c41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,22 +8,22 @@ jobs: fail-fast: false matrix: include: - - php: '8.1' - moodle-branch: 'master' + - php: '8.3' + moodle-branch: 'main' database: 'pgsql' - - php: '8.0' - moodle-branch: 'MOODLE_403_STABLE' + - php: '8.3' + moodle-branch: 'MOODLE_500_STABLE' database: 'mariadb' - - php: '8.1' - moodle-branch: 'MOODLE_402_STABLE' + - php: '8.2' + moodle-branch: 'MOODLE_405_STABLE' database: 'pgsql' - - php: '8.0' - moodle-branch: 'MOODLE_401_STABLE' + - php: '8.2' + moodle-branch: 'MOODLE_404_STABLE' database: 'mariadb' services: postgres: - image: postgres:13 + image: postgres:14 env: POSTGRES_USER: 'postgres' POSTGRES_HOST_AUTH_METHOD: 'trust' @@ -109,7 +109,7 @@ jobs: run: moodle-plugin-ci mustache - name: Grunt - if: ${{ matrix.moodle-branch == 'MOODLE_402_STABLE' }} + if: ${{ always() }} run: moodle-plugin-ci grunt --max-lint-warnings 0 - name: PHPUnit tests From b8e86466b295e3afae19e8248a9aa4cdba74b991 Mon Sep 17 00:00:00 2001 From: Anupama Sarjoshi Date: Thu, 24 Jul 2025 17:24:46 +0100 Subject: [PATCH 08/11] Fix codechecker issues --- categoryadd_form.php | 3 +- classes/event/query_deleted.php | 5 + classes/event/query_edited.php | 5 + classes/event/query_viewed.php | 5 + classes/local/query.php | 7 +- classes/output/category.php | 3 +- classes/output/category_query.php | 1 + classes/output/index_page.php | 1 + classes/output/renderer.php | 2 +- classes/privacy/provider.php | 1 + classes/utils.php | 5 + edit_form.php | 4 + lang/en/report_customsql.php | 44 ++--- locallib.php | 198 ++++++++++++++++++++- manage.php | 1 + tests/external/external_get_users_test.php | 8 +- tests/local/category_test.php | 2 +- tests/local/query_test.php | 2 +- tests/privacy_test.php | 6 +- tests/report_test.php | 111 ++++++++++-- view_form.php | 2 + 21 files changed, 359 insertions(+), 57 deletions(-) diff --git a/categoryadd_form.php b/categoryadd_form.php index a84b5de..9f9e36e 100644 --- a/categoryadd_form.php +++ b/categoryadd_form.php @@ -39,7 +39,7 @@ */ class report_customsql_addcategory_form extends moodleform { - // Form definition. + #[\Override] public function definition() { global $CFG, $DB; $mform = $this->_form; @@ -65,6 +65,7 @@ public function definition() { $this->add_action_buttons(true, $strsubmit); } + #[\Override] public function validation($data, $files) { global $DB; $errors = parent::validation($data, $files); diff --git a/classes/event/query_deleted.php b/classes/event/query_deleted.php index 00292a2..ba7012c 100644 --- a/classes/event/query_deleted.php +++ b/classes/event/query_deleted.php @@ -32,20 +32,25 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class query_deleted extends \core\event\base { + + #[\Override] protected function init() { $this->data['crud'] = 'd'; $this->data['edulevel'] = self::LEVEL_OTHER; $this->data['objecttable'] = 'report_customsql_queries'; } + #[\Override] public static function get_name() { return get_string('query_deleted', 'report_customsql'); } + #[\Override] public function get_description() { return "User {$this->userid} has deleted the SQL query with id {$this->objectid}."; } + #[\Override] public function get_url() { return new \moodle_url('/report/customsql/index.php'); } diff --git a/classes/event/query_edited.php b/classes/event/query_edited.php index 89eb797..ad84c72 100644 --- a/classes/event/query_edited.php +++ b/classes/event/query_edited.php @@ -32,20 +32,25 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class query_edited extends \core\event\base { + + #[\Override] protected function init() { $this->data['crud'] = 'u'; $this->data['edulevel'] = self::LEVEL_OTHER; $this->data['objecttable'] = 'report_customsql_queries'; } + #[\Override] public static function get_name() { return get_string('query_edited', 'report_customsql'); } + #[\Override] public function get_description() { return "User {$this->userid} has edited the SQL query with id {$this->objectid}."; } + #[\Override] public function get_url() { return new \moodle_url('/report/customsql/view.php', ['id' => $this->objectid]); } diff --git a/classes/event/query_viewed.php b/classes/event/query_viewed.php index a48eed9..e3d79fa 100644 --- a/classes/event/query_viewed.php +++ b/classes/event/query_viewed.php @@ -32,20 +32,25 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class query_viewed extends \core\event\base { + + #[\Override] protected function init() { $this->data['crud'] = 'r'; $this->data['edulevel'] = self::LEVEL_OTHER; $this->data['objecttable'] = 'report_customsql_queries'; } + #[\Override] public static function get_name() { return get_string('query_viewed', 'report_customsql'); } + #[\Override] public function get_description() { return "User {$this->userid} has viewed the SQL query with id {$this->objectid}."; } + #[\Override] public function get_url() { return new \moodle_url('/report/customsql/view.php', ['id' => $this->objectid]); } diff --git a/classes/local/query.php b/classes/local/query.php index 66c5f35..c01d795 100644 --- a/classes/local/query.php +++ b/classes/local/query.php @@ -70,7 +70,7 @@ public function get_url(): moodle_url { * @param moodle_url|null $returnurl Return url. * @return moodle_url Edit url. */ - public function get_edit_url(moodle_url $returnurl = null): moodle_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); @@ -85,7 +85,7 @@ public function get_edit_url(moodle_url $returnurl = null): moodle_url { * @param moodle_url|null $returnurl Return url. * @return moodle_url Delete url. */ - public function get_delete_url(moodle_url $returnurl = null): moodle_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); @@ -118,7 +118,6 @@ public function get_capability_string() { * * @param \context $context The context to check. * @return bool true if the user has this capability. Otherwise false. - * @covers \report_customsql\local\query */ public function can_edit(\context $context): bool { return has_capability('report/customsql:definequeries', $context); @@ -130,7 +129,7 @@ public function can_edit(\context $context): bool { * @param \context $context The context to check. * @return bool Has capability to view or not? */ - public function can_view(\context $context):bool { + 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 index 67090c8..4a50638 100644 --- a/classes/output/category.php +++ b/classes/output/category.php @@ -68,7 +68,7 @@ class category implements renderable, templatable { * @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) { + int $hidecat = 0, bool $showonlythislink = false, bool $addnewquerybtn = true, ?moodle_url $returnurl = null) { $this->category = $category; $this->context = $context; $this->expandable = $expandable; @@ -79,6 +79,7 @@ public function __construct(report_category $category, context $context, bool $e $this->returnurl = $returnurl ?? $this->category->get_url(); } + #[\Override] public function export_for_template(renderer_base $output) { $queriesdata = $this->category->get_queries_data(); diff --git a/classes/output/category_query.php b/classes/output/category_query.php index 9a0b67c..d9e5e09 100644 --- a/classes/output/category_query.php +++ b/classes/output/category_query.php @@ -59,6 +59,7 @@ public function __construct(query $query, category $category, context $context, $this->returnurl = $returnurl; } + #[\Override] 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')); diff --git a/classes/output/index_page.php b/classes/output/index_page.php index 0a4a94b..98761f8 100644 --- a/classes/output/index_page.php +++ b/classes/output/index_page.php @@ -69,6 +69,7 @@ public function __construct(array $categories, array $queries, context $context, $this->hidecat = $hidecat; } + #[\Override] public function export_for_template(renderer_base $output) { $categoriesdata = []; $grouppedqueries = utils::group_queries_by_category($this->queries); diff --git a/classes/output/renderer.php b/classes/output/renderer.php index a33dbae..f60d53b 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -39,7 +39,7 @@ class renderer extends plugin_renderer_base { * @param context $context context to use for permission checks. * @return string HTML for report actions. */ - public function render_report_actions(stdClass $report, stdClass $category, context $context):string { + public function render_report_actions(stdClass $report, stdClass $category, context $context): string { $editaction = null; $deleteaction = null; if (has_capability('report/customsql:definequeries', $context)) { diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 4af2b26..ec0d4a7 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Privacy Subsystem implementation for report_customsql. * diff --git a/classes/utils.php b/classes/utils.php index ed60440..1894431 100644 --- a/classes/utils.php +++ b/classes/utils.php @@ -58,6 +58,11 @@ public static function group_queries_by_category($queries) { return $grouppedqueries; } + /** + * Retrieves and processes data for the given queries. + * + * @param array $queries An array of query objects. + */ public function get_queries_data($queries) { } diff --git a/edit_form.php b/edit_form.php index a0f9f5c..cd0f7b5 100644 --- a/edit_form.php +++ b/edit_form.php @@ -34,6 +34,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class report_customsql_edit_form extends moodleform { + + #[\Override] public function definition() { global $CFG; @@ -155,6 +157,7 @@ public function definition() { $this->add_action_buttons(); } + #[\Override] public function set_data($currentvalues) { global $DB, $OUTPUT; @@ -181,6 +184,7 @@ public function set_data($currentvalues) { $mform->addElement('html', $reportinfo); } + #[\Override] public function validation($data, $files) { global $CFG, $DB, $USER; diff --git a/lang/en/report_customsql.php b/lang/en/report_customsql.php index 26ad086..bdc608b 100644 --- a/lang/en/report_customsql.php +++ b/lang/en/report_customsql.php @@ -35,8 +35,8 @@ $string['automaticallyweekly'] = 'Scheduled, on the first day of each week'; $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['backtoreportlist'] = 'Back to the list of queries'; $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'; @@ -62,8 +62,8 @@ $string['deletereportx'] = 'Delete query \'{$a}\''; $string['description'] = 'Description'; $string['displayname'] = 'Query name'; -$string['displaynamex'] = 'Query name: {$a}'; $string['displaynamerequired'] = 'You must enter a query name'; +$string['displaynamex'] = 'Query name: {$a}'; $string['downloadthisreportas'] = 'Download these results as'; $string['downloadthisreportascsv'] = 'Download these results as CSV'; $string['edit'] = 'Add/Edit'; @@ -71,9 +71,10 @@ $string['editcategoryx'] = 'Edit category \'{$a}\''; $string['editingareport'] = 'Editing an ad-hoc database query'; $string['editreportx'] = 'Edit query \'{$a}\''; +$string['emailbody'] = 'Dear {$a}'; +$string['emailink'] = 'To access the report, click this link: {$a}'; $string['emailnumberofrows'] = 'Just the number of rows and the link'; $string['emailresults'] = 'Put the results in the email body'; -$string['emailink'] = 'To access the report, click this link: {$a}'; $string['emailrow'] = 'The report returned {$a} row.'; $string['emailrows'] = 'The report returned {$a} rows.'; $string['emailsent'] = 'An email notification has been sent to {$a}'; @@ -82,7 +83,6 @@ $string['emailsubject1row'] = 'Query {$a->name} [1 row] [{$a->env}]'; $string['emailsubjectnodata'] = 'Query {$a->name} [no results] [{$a->env}]'; $string['emailsubjectxrows'] = 'Query {$a->name} [{$a->rows} rows] [{$a->env}]'; -$string['emailbody'] = 'Dear {$a}'; $string['emailto'] = 'Automatically email to'; $string['emailwhat'] = 'What to email'; $string['enterparameters'] = 'Enter parameters for ad-hoc database query'; @@ -92,11 +92,11 @@ $string['errorupdatingreport'] = 'Error updating a query.'; $string['invalidreportid'] = 'Invalid query id {$a}.'; $string['lastexecuted'] = 'This query was last run on {$a->lastrun}. It took {$a->lastexecutiontime}s to run.'; -$string['messageprovider:notification'] = 'Ad-hoc database query notifications'; $string['managecategories'] = 'Manage report categories'; $string['manual'] = 'On-demand'; $string['manualheader'] = 'On-demand'; $string['manualheader_help'] = 'These queries are run on-demand, when you click the link to view the results.'; +$string['messageprovider:notification'] = 'Ad-hoc database query notifications'; $string['monthlyheader'] = 'Monthly'; $string['monthlyheader_help'] = 'These queries are automatically run on the first day of each month, to report on the previous month. These links let you view the results that has already been accumulated.'; $string['monthlynote_help'] = 'These queries are automatically run on the first day of each month, to report on the previous month. These links let you view the results that has already been accumulated.'; @@ -114,28 +114,29 @@ $string['onerow'] = 'The query returns one row, accumulate the results one row at a time'; $string['parametervalue'] = '{$a->name}: {$a->value}'; $string['pluginname'] = 'Ad-hoc database queries'; +$string['privacy:metadata'] = 'The Ad-hoc database queries plugin does not store any personal data.'; $string['privacy:metadata:reportcustomsqlqueries'] = 'Ad-hoc database queries'; -$string['privacy:metadata:reportcustomsqlqueries:displayname'] = 'The name of the report as displayed in the UI'; +$string['privacy:metadata:reportcustomsqlqueries:at'] = 'The time for the daily report'; +$string['privacy:metadata:reportcustomsqlqueries:capability'] = 'The capability that a user needs to have to run this report'; +$string['privacy:metadata:reportcustomsqlqueries:categoryid'] = 'The category ID from report_customsql_categories table'; +$string['privacy:metadata:reportcustomsqlqueries:customdir'] = 'Export csv report to path / directory'; $string['privacy:metadata:reportcustomsqlqueries:description'] = 'A human-readable description of the query.'; $string['privacy:metadata:reportcustomsqlqueries:descriptionformat'] = 'Query description text format'; -$string['privacy:metadata:reportcustomsqlqueries:querysql'] = 'The SQL to run to generate this report'; -$string['privacy:metadata:reportcustomsqlqueries:queryparams'] = 'The SQL parameters to generate this report'; -$string['privacy:metadata:reportcustomsqlqueries:querylimit'] = 'Limit the number of results returned'; -$string['privacy:metadata:reportcustomsqlqueries:capability'] = 'The capability that a user needs to have to run this report'; -$string['privacy:metadata:reportcustomsqlqueries:lastrun'] = 'When this report was last run'; +$string['privacy:metadata:reportcustomsqlqueries:displayname'] = 'The name of the report as displayed in the UI'; +$string['privacy:metadata:reportcustomsqlqueries:emailto'] = 'A comma-separated list of user ids'; +$string['privacy:metadata:reportcustomsqlqueries:emailwhat'] = 'A list of email options in a select menu'; $string['privacy:metadata:reportcustomsqlqueries:lastexecutiontime'] = 'Time this report took to run last time it was executed, in milliseconds'; +$string['privacy:metadata:reportcustomsqlqueries:lastrun'] = 'When this report was last run'; +$string['privacy:metadata:reportcustomsqlqueries:querylimit'] = 'Limit the number of results returned'; +$string['privacy:metadata:reportcustomsqlqueries:queryparams'] = 'The SQL parameters to generate this report'; +$string['privacy:metadata:reportcustomsqlqueries:querysql'] = 'The SQL to run to generate this report'; $string['privacy:metadata:reportcustomsqlqueries:runable'] = 'Runable \'manual\', \'weekly\' or \'monthly\''; $string['privacy:metadata:reportcustomsqlqueries:singlerow'] = 'Only meaningful to set this scheduled reports. Means the report can only return one row of data, and the report builds up a row at a time'; -$string['privacy:metadata:reportcustomsqlqueries:at'] = 'The time for the daily report'; -$string['privacy:metadata:reportcustomsqlqueries:emailto'] = 'A comma-separated list of user ids'; -$string['privacy:metadata:reportcustomsqlqueries:emailwhat'] = 'A list of email options in a select menu'; -$string['privacy:metadata:reportcustomsqlqueries:categoryid'] = 'The category ID from report_customsql_categories table'; -$string['privacy:metadata:reportcustomsqlqueries:customdir'] = 'Export csv report to path / directory'; -$string['privacy:metadata:reportcustomsqlqueries:usermodified'] = 'User modified'; $string['privacy:metadata:reportcustomsqlqueries:timecreated'] = 'Time created'; $string['privacy:metadata:reportcustomsqlqueries:timemodified'] = 'Time modified'; -$string['privacy_you'] = 'You'; +$string['privacy:metadata:reportcustomsqlqueries:usermodified'] = 'User modified'; $string['privacy_somebodyelse'] = 'Somebody else'; +$string['privacy_you'] = 'You'; $string['query_deleted'] = 'Query deleted'; $string['query_edited'] = 'Query edited'; $string['query_viewed'] = 'Query viewed'; @@ -183,14 +184,13 @@ $string['timemodified'] = 'Last modified: {$a}'; $string['typeofresult'] = 'Type of result'; $string['unknowndownloadfile'] = 'Unknown download file.'; -$string['usermodified'] = 'Modified by: {$a}'; -$string['usernotfound'] = 'User with id \'{$a}\' does not exist'; $string['userhasnothiscapability'] = 'User \'{$a->name}\' ({$a->userid}) has not got capability \'{$a->capability}\'. Please delete this user from the list or change the choice in \'{$a->whocanaccess}\'.'; $string['userinvalidinput'] = 'Invalid input, a comma-separated list of user names is required'; -$string['userswhocanviewsitereports'] = 'Users who can see system reports (moodle/site:viewreports)'; +$string['usermodified'] = 'Modified by: {$a}'; +$string['usernotfound'] = 'User with id \'{$a}\' does not exist'; $string['userswhocanconfig'] = 'Only administrators (moodle/site:config)'; +$string['userswhocanviewsitereports'] = 'Users who can see system reports (moodle/site:viewreports)'; $string['verifyqueryandupdate'] = 'Verify the Query SQL text and update the form'; $string['weeklyheader'] = 'Weekly'; $string['weeklyheader_help'] = 'These queries are automatically run on the first day of each week, to report on the previous week. These links let you view the results that has already been accumulated.'; $string['whocanaccess'] = 'Who can access this query'; -$string['privacy:metadata'] = 'The Ad-hoc database queries plugin does not store any personal data.'; diff --git a/locallib.php b/locallib.php index 7c9a6e6..11bc396 100644 --- a/locallib.php +++ b/locallib.php @@ -29,6 +29,15 @@ define('REPORT_CUSTOMSQL_LIMIT_EXCEEDED_MARKER', '-- ROW LIMIT EXCEEDED --'); +/** + * Executes a custom SQL query with optional parameters and row limit. + * + * @param string $sql The SQL query to execute. + * @param array|null $params Optional query parameters. + * @param int|null $limitnum Optional row limit. Uses config value if null. + * @return moodle_recordset The result set. + * @throws Exception If the query fails. + */ function report_customsql_execute_query($sql, $params = null, $limitnum = null) { global $CFG, $DB; @@ -48,6 +57,13 @@ function report_customsql_execute_query($sql, $params = null, $limitnum = null) return $DB->get_recordset_sql($sql, $params, 0, $limitnum); } +/** + * Prepares the customsql for execution. + * + * @param stdClass $report report record from customsql table. + * @param int $timenow unix timestamp - usually "now()" + * @return string The SQL string. + */ function report_customsql_prepare_sql($report, $timenow) { global $USER; $sql = $report->querysql; @@ -100,7 +116,7 @@ function report_customsql_get_element_type($name) { * Generate customsql csv file. * * @param stdclass $report report record from customsql table. - * @param int $timetimenow unix timestamp - usually "now()" + * @param int $timenow unix timestamp - usually "now()" * @param bool $returnheaderwhenempty if true, a CSV file with headers will always be generated, even if there are no results. */ function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty = false) { @@ -197,6 +213,8 @@ function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty } /** + * Check if a value is an integer or a string that looks like an integer. + * * @param mixed $value some value * @return bool whether $value is an integer, or a string that looks like an integer. */ @@ -204,6 +222,13 @@ function report_customsql_is_integer($value) { return (string) (int) $value === (string) $value; } +/** + * Returns the csv filename for download. + * + * @param stdClass $report The report record. + * @param int $timenow The current time as a timestamp. + * @return array [filename, current timestamp]. + */ function report_customsql_csv_filename($report, $timenow) { if ($report->runable == 'manual') { return report_customsql_temp_cvs_name($report->id, $timenow); @@ -217,6 +242,13 @@ function report_customsql_csv_filename($report, $timenow) { } } +/** + * Returns the temp csv filename. + * + * @param int $reportid The report id. + * @param int $timestamp The current time as a timestamp. + * @return array [filename, timestamp]. + */ function report_customsql_temp_cvs_name($reportid, $timestamp) { global $CFG; $path = 'admin_report_customsql/temp/'.$reportid; @@ -225,6 +257,13 @@ function report_customsql_temp_cvs_name($reportid, $timestamp) { $timestamp]; } +/** + * Returns the csv filename for the scheduled reports. + * + * @param int $reportid The report id. + * @param int $timestart The current time as a timestamp. + * @return array [filename, timestamp]. + */ function report_customsql_scheduled_cvs_name($reportid, $timestart) { global $CFG; $path = 'admin_report_customsql/'.$reportid; @@ -233,6 +272,12 @@ function report_customsql_scheduled_cvs_name($reportid, $timestart) { $timestart]; } +/** + * Returns the csv filename for accumulating reports. + * + * @param int $reportid The report id. + * @return array [filename, 0]. + */ function report_customsql_accumulating_cvs_name($reportid) { global $CFG; $path = 'admin_report_customsql/'.$reportid; @@ -240,6 +285,12 @@ function report_customsql_accumulating_cvs_name($reportid) { return [$CFG->dataroot.'/'.$path.'/accumulate.csv', 0]; } +/** + * Returns the report archive times. + * + * @param stdClass $report the report record from customsql table. + * @return array of archive times as unix timestamps. + */ function report_customsql_get_archive_times($report) { global $CFG; if ($report->runable == 'manual' || $report->singlerow) { @@ -257,10 +308,25 @@ function report_customsql_get_archive_times($report) { return $archivetimes; } +/** + * Substitutes the time tokens in the SQL string with the provided start and end times. + * + * @param string $sql The SQL string containing the `%%STARTTIME%%` and `%%ENDTIME%%` tokens. + * @param int $start The start time to substitute into the SQL string. + * @param int $end The end time to substitute into the SQL string. + * @return string The SQL string. + */ function report_customsql_substitute_time_tokens($sql, $start, $end) { return str_replace(['%%STARTTIME%%', '%%ENDTIME%%'], [$start, $end], $sql); } +/** + * Replaces the `%%USERID%%` token in the given SQL string with the provided user ID. + * + * @param string $sql The SQL string containing the `%%USERID%%` token. + * @param int $userid The user ID to substitute into the SQL string. + * @return string The SQL string with the `%%USERID%%` token replaced by the user ID. + */ function report_customsql_substitute_user_token($sql, $userid) { return str_replace('%%USERID%%', $userid, $sql); } @@ -300,6 +366,9 @@ function report_customsql_downloadurl($reportid, $params = []) { return $downloadurl; } +/** + * Returns the options for the capability field in the report_customsql_queries table. + */ function report_customsql_capability_options() { return [ 'report/customsql:view' => get_string('anyonewhocanveiwthisreport', 'report_customsql'), @@ -308,6 +377,11 @@ function report_customsql_capability_options() { ]; } +/** + * Returns the options for the runable field in the report_customsql_queries table. + * + * @param string|null $type If set to 'manual', only the manual option is returned. + */ function report_customsql_runable_options($type = null) { if ($type === 'manual') { return ['manual' => get_string('manual', 'report_customsql')]; @@ -320,6 +394,9 @@ function report_customsql_runable_options($type = null) { ]; } +/** + * Returns the options for the daily time field in the report_customsql_queries table. + */ function report_customsql_daily_at_options() { $time = []; for ($h = 0; $h < 24; $h++) { @@ -329,33 +406,62 @@ function report_customsql_daily_at_options() { return $time; } +/** + * Returns the options for the email settings in the report_customsql_queries table. + */ function report_customsql_email_options() { return ['emailnumberofrows' => get_string('emailnumberofrows', 'report_customsql'), 'emailresults' => get_string('emailresults', 'report_customsql'), ]; } +/** + * Returns a list of SQL keywords that are considered unsafe or undesirable + * to allow in custom SQL queries. + */ function report_customsql_bad_words_list() { return ['ALTER', 'CREATE', 'DELETE', 'DROP', 'GRANT', 'INSERT', 'INTO', 'TRUNCATE', 'UPDATE']; } +/** + * Returns true if the given string contains any of the bad words. + * + * @param string $string The string to check. + */ function report_customsql_contains_bad_word($string) { return preg_match('/\b('.implode('|', report_customsql_bad_words_list()).')\b/i', $string); } +/** + * Triggers an event when a custom SQL query is created. + * + * @param int $id The ID of the query. + */ function report_customsql_log_delete($id) { $event = \report_customsql\event\query_deleted::create( ['objectid' => $id, 'context' => context_system::instance()]); $event->trigger(); } +/** + * Triggers an event when a custom SQL query is edited. + * + * @param int $id The ID of the query. + * @return void + */ function report_customsql_log_edit($id) { $event = \report_customsql\event\query_edited::create( ['objectid' => $id, 'context' => context_system::instance()]); $event->trigger(); } +/** + * Triggers an event when a custom SQL query is viewed. + * + * @param int $id The ID of the query. + * @return void + */ function report_customsql_log_view($id) { $event = \report_customsql\event\query_viewed::create( ['objectid' => $id, 'context' => context_system::instance()]); @@ -366,7 +472,7 @@ function report_customsql_log_view($id) { * Returns all reports for a given type sorted by report 'displayname'. * * @param int $categoryid - * @param string $type, type of report (manual, daily, weekly or monthly) + * @param string $type type of report (manual, daily, weekly or monthly) * @return stdClass[] relevant rows from report_customsql_queries. */ function report_customsql_get_reports_for($categoryid, $type) { @@ -380,8 +486,8 @@ function report_customsql_get_reports_for($categoryid, $type) { /** * Display a list of reports of one type in one category. * - * @param object $reports, the result of DB query - * @param string $type, type of report (manual, daily, weekly or monthly) + * @param object $reports the result of DB query + * @param string $type type of report (manual, daily, weekly or monthly) */ function report_customsql_print_reports_for($reports, $type) { global $OUTPUT; @@ -487,6 +593,13 @@ function report_customsql_display_row($row, $linkcolumns) { return $rowdata; } +/** + * Returns a note about the last run time of the report. + * + * @param stdClass $report the report record from customsql table. + * @param string $tag the HTML tag to use for the note. + * @return string HTML string with the note. + */ function report_customsql_time_note($report, $tag) { if ($report->lastrun) { $a = new stdClass; @@ -502,6 +615,15 @@ function report_customsql_time_note($report, $tag) { } +/** + * Prettify the column names in a row of data. + * + * This is used to make the column names more readable in the CSV export. + * + * @param stdClass $row a row of data from the query result. + * @param string $querysql the original SQL query. + * @return string[] prettified column names. + */ function report_customsql_pretify_column_names($row, $querysql) { $colnames = []; @@ -559,6 +681,14 @@ function report_customsql_read_csv_row($handle) { return fgetcsv($handle, 0, ',', '"', $disablestupidphpescaping); } +/** + * Writes the first row of a CSV file, which contains the column names. + * + * @param resource $handle the file pointer + * @param stdClass $firstrow + * @param stdClass $report The report record. + * @return void + */ function report_customsql_start_csv($handle, $firstrow, $report) { $colnames = report_customsql_pretify_column_names($firstrow, $report->querysql); if ($report->singlerow) { @@ -568,6 +698,8 @@ function report_customsql_start_csv($handle, $firstrow, $report) { } /** + * Get the timestamps for daily reports. + * * @param int $timenow a timestamp. * @param int $at an hour, 0 to 23. * @return array with two elements: the timestamp for hour $at today (where today @@ -585,6 +717,13 @@ function report_customsql_get_daily_time_starts($timenow, $at) { ]; } +/** + * Get the timestamps for weekly reports. + * + * @param int $timenow a timestamp. + * @return array with two elements: the timestamp for the start of this week and + * the timestamp for the start of last week. + */ function report_customsql_get_week_starts($timenow) { $dateparts = getdate($timenow); @@ -603,6 +742,13 @@ function report_customsql_get_week_starts($timenow) { ]; } +/** + * Get the timestamps for monthly reports. + * + * @param int $timenow a timestamp. + * @return array with two elements: the timestamp for the start of this month and + * the timestamp for the start of last month. + */ function report_customsql_get_month_starts($timenow) { $dateparts = getdate($timenow); @@ -612,6 +758,14 @@ function report_customsql_get_month_starts($timenow) { ]; } +/** + * Get the start timestamps for the report. + * + * @param stdClass $report The report record. + * @param int $timenow The current time as a timestamp. + * @return array with two elements: the start timestamp and the end timestamp. + * @throws Exception If the report type is not recognized. + */ function report_customsql_get_starts($report, $timenow) { switch ($report->runable) { case 'daily': @@ -625,6 +779,12 @@ function report_customsql_get_starts($report, $timenow) { } } +/** + * Deletes old temporary CSV files. + * + * @param int $upto timestamp to delete files older than this. + * @return int the number of files deleted. + */ function report_customsql_delete_old_temp_files($upto) { global $CFG; @@ -678,6 +838,12 @@ function report_customsql_validate_users($userids, $capability) { return null; } +/** + * Get the message to send when there is no data returned from the report. + * + * @param stdClass $report report settings from the database. + * @return stdClass message object. + */ function report_customsql_get_message_no_data($report) { // Construct subject. $subject = report_customsql_email_subject(0, $report); @@ -696,6 +862,13 @@ function report_customsql_get_message_no_data($report) { return $message; } +/** + * Get the message to send when there is data returned from the report. + * + * @param stdClass $report report settings from the database. + * @param string $csvfilename the name of the CSV file with the report data. + * @return stdClass message object. + */ function report_customsql_get_message($report, $csvfilename) { $handle = fopen($csvfilename, 'r'); $table = new html_table(); @@ -776,6 +949,12 @@ function report_customsql_email_subject(int $countrows, stdClass $report): strin } } +/** + * Send the email report to all recipients. + * + * @param stdClass $report the report record from customsql table. + * @param string|null $csvfilename the name of the CSV file with the report data, or null if no data. + */ function report_customsql_email_report($report, $csvfilename = null) { global $DB; @@ -801,6 +980,12 @@ function report_customsql_email_report($report, $csvfilename = null) { } } +/** + * Get the reports that are ready to run daily. + * + * @param int $timenow the current time as a timestamp. + * @return stdClass[] relevant rows from report_customsql_queries. + */ function report_customsql_get_ready_to_run_daily_reports($timenow) { global $DB; $reports = $DB->get_records_select('report_customsql_queries', "runable = ?", ['daily'], 'id'); @@ -863,6 +1048,11 @@ function report_customsql_is_daily_report_ready($report, $timenow) { return false; } +/** + * Returns the options for the custom SQL categories. + * + * @return array of category id => name pairs. + */ function report_customsql_category_options() { global $DB; return $DB->get_records_menu('report_customsql_categories', null, 'name ASC', 'id, name'); diff --git a/manage.php b/manage.php index 3640da3..8d3ca2e 100644 --- a/manage.php +++ b/manage.php @@ -27,6 +27,7 @@ * @copyright 2013 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'); diff --git a/tests/external/external_get_users_test.php b/tests/external/external_get_users_test.php index 5a1e1af..c0beb61 100644 --- a/tests/external/external_get_users_test.php +++ b/tests/external/external_get_users_test.php @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . - namespace report_customsql\external; defined('MOODLE_INTERNAL') || die(); @@ -34,8 +33,13 @@ * @covers \report_customsql\external\get_users * @runTestsInSeparateProcesses */ -class external_get_users_test extends \externallib_advanced_testcase { +final class external_get_users_test extends \externallib_advanced_testcase { + /** + * Sets up test users with specific roles and permissions. + * + * @return array An array containing the created users: admin, manager, and course creator. + */ protected function setup_users(): array { global $DB, $USER; diff --git a/tests/local/category_test.php b/tests/local/category_test.php index d84b596..2513d2e 100644 --- a/tests/local/category_test.php +++ b/tests/local/category_test.php @@ -29,7 +29,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \report_customsql\local\category */ -class category_test extends \advanced_testcase { +final class category_test extends \advanced_testcase { /** * Test create category. */ diff --git a/tests/local/query_test.php b/tests/local/query_test.php index 1f1fb95..8c85864 100644 --- a/tests/local/query_test.php +++ b/tests/local/query_test.php @@ -29,7 +29,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \report_customsql\local\query */ -class query_test extends \advanced_testcase { +final class query_test extends \advanced_testcase { /** * Test create query. */ diff --git a/tests/privacy_test.php b/tests/privacy_test.php index 55644dc..687a255 100644 --- a/tests/privacy_test.php +++ b/tests/privacy_test.php @@ -27,7 +27,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \report_customsql\privacy\provider */ -class privacy_test extends \core_privacy\tests\provider_testcase { +final class privacy_test extends \core_privacy\tests\provider_testcase { /** @var \stdClass test user. */ protected $user1; @@ -36,7 +36,11 @@ class privacy_test extends \core_privacy\tests\provider_testcase { /** @var \stdClass test user. */ protected $user3; + /** + * Set up for every test + */ public function setUp(): void { + parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); diff --git a/tests/report_test.php b/tests/report_test.php index cd24ec8..f8ce545 100644 --- a/tests/report_test.php +++ b/tests/report_test.php @@ -28,7 +28,7 @@ * @copyright 2009 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class report_test extends \advanced_testcase { +final class report_test extends \advanced_testcase { /** * Data provider for test_get_week_starts @@ -97,7 +97,11 @@ public function test_get_week_starts_use_calendar_default( $this->assertEquals($expected, report_customsql_get_week_starts(strtotime($datestr))); } - /** @covers ::report_customsql_get_month_starts */ + /** + * Tests plugin report_customsql_get_month_starts method to retrieve the start dates of the current and previous months. + * + * @covers ::report_customsql_get_month_starts + */ public function test_get_month_starts_test(): void { $this->assertEquals([ strtotime('00:00 1 November 2009'), strtotime('00:00 1 October 2009')], @@ -112,7 +116,11 @@ public function test_get_month_starts_test(): void { report_customsql_get_month_starts(strtotime('23:59 29 November 2009'))); } - /** @covers ::report_customsql_get_element_type */ + /** + * Tests plugin report_customsql_get_element_type method to determine the element type based on field names. + * + * @covers ::report_customsql_get_element_type + */ public function test_report_customsql_get_element_type(): void { $this->assertEquals('date_time_selector', report_customsql_get_element_type('start_date')); $this->assertEquals('date_time_selector', report_customsql_get_element_type('startdate')); @@ -124,14 +132,22 @@ public function test_report_customsql_get_element_type(): void { $this->assertEquals('text', report_customsql_get_element_type('mandated')); } - /** @covers ::report_customsql_substitute_user_token */ + /** + * Test plugin report_customsql_substitute_user_token method to substitute a user token in the given SQL query. + * + * @covers ::report_customsql_substitute_user_token + */ public function test_report_customsql_substitute_user_token(): void { $this->assertEquals('SELECT COUNT(*) FROM oh_quiz_attempts WHERE user = 123', report_customsql_substitute_user_token('SELECT COUNT(*) FROM oh_quiz_attempts '. 'WHERE user = %%USERID%%', 123)); } - /** @covers ::report_customsql_capability_options */ + /** + * Tests the report_customsql_capability_options method for retrieving capability options. + * + * @covers ::report_customsql_capability_options + */ public function test_report_customsql_capability_options(): void { $capoptions = [ 'report/customsql:view' => get_string('anyonewhocanveiwthisreport', 'report_customsql'), @@ -142,7 +158,12 @@ public function test_report_customsql_capability_options(): void { } - /** @covers ::report_customsql_runable_options */ + /** + * Test plugin report_customsql_runable_options method to retrieve the options for the runable field + * in the custom SQL report. + * + * @covers ::report_customsql_runable_options + */ public function test_report_customsql_runable_options(): void { $options = [ 'manual' => get_string('manual', 'report_customsql'), @@ -154,7 +175,11 @@ public function test_report_customsql_runable_options(): void { $this->assertEquals($options, report_customsql_runable_options()); } - /** @covers ::report_customsql_daily_at_options */ + /** + * Test plugin report_customsql_daily_at_options method. + * + * @covers ::report_customsql_daily_at_options + */ public function test_report_customsql_daily_at_options(): void { $time = []; for ($h = 0; $h < 24; $h++) { @@ -164,7 +189,11 @@ public function test_report_customsql_daily_at_options(): void { $this->assertEquals($time, report_customsql_daily_at_options()); } - /** @covers ::report_customsql_email_options */ + /** + * Test plugin report_customsql_email_options method to retrieve the options for email settings. + * + * @covers ::report_customsql_email_options + */ public function test_report_customsql_email_options(): void { $options = [ 'emailnumberofrows' => get_string('emailnumberofrows', 'report_customsql'), @@ -173,19 +202,31 @@ public function test_report_customsql_email_options(): void { $this->assertEquals($options, report_customsql_email_options()); } - /** @covers ::report_customsql_bad_words_list */ + /** + * Test plugin report_customsql_bad_words_list method to retrieve the list of words that should not be used in SQL queries. + * + * @covers ::report_customsql_bad_words_list + */ public function test_report_customsql_bad_words_list(): void { $options = ['ALTER', 'CREATE', 'DELETE', 'DROP', 'GRANT', 'INSERT', 'INTO', 'TRUNCATE', 'UPDATE']; $this->assertEquals($options, report_customsql_bad_words_list()); } - /** @covers ::report_customsql_bad_words_list */ + /** + * Test plugin report_customsql_contains_bad_word method to check if a string contains any of the bad words. + * + * @covers ::report_customsql_bad_words_list + */ public function test_report_customsql_contains_bad_word(): void { $string = 'DELETE * FROM prefix_user u WHERE u.id > 0'; $this->assertEquals(1, report_customsql_contains_bad_word($string)); } - /** @covers ::report_customsql_get_daily_time_starts */ + /** + * Test plugin report_customsql_is_daily_report_ready method to check if a daily report is ready to run. + * + * @covers ::report_customsql_get_daily_time_starts + */ public function test_report_customsql_get_ready_to_run_daily_reports(): void { global $DB; $this->resetAfterTest(true); @@ -259,7 +300,11 @@ public function test_report_customsql_get_ready_to_run_daily_reports(): void { $this->assertTrue(report_customsql_is_daily_report_ready($report, $timenow)); } - /** @covers ::report_customsql_is_integer */ + /** + * Test plugin report_customsql_is_integer method to check if a value is an integer. + * + * @covers ::report_customsql_is_integer + */ public function test_report_customsql_is_integer(): void { $this->assertTrue(report_customsql_is_integer(1)); $this->assertTrue(report_customsql_is_integer('1')); @@ -267,7 +312,11 @@ public function test_report_customsql_is_integer(): void { $this->assertFalse(report_customsql_is_integer('2013-10-07')); } - /** @covers ::report_customsql_get_table_headers */ + /** + * Test plugin report_customsql_get_table_headers method to retrieve the headers for a report. + * + * @covers ::report_customsql_get_table_headers + */ public function test_report_customsql_get_table_headers(): void { $rawheaders = [ 'String date', @@ -294,7 +343,11 @@ public function test_report_customsql_get_table_headers(): void { $this->assertEquals([3 => 4, 4 => -1, 5 => 7, 7 => -1], $linkcolumns); } - /** @covers ::report_customsql_pretify_column_names */ + /** + * Test plugin report_customsql_pretify_column_names method to test formatting of column names. + * + * @covers ::report_customsql_pretify_column_names + */ public function test_report_customsql_pretify_column_names(): void { $row = new \stdClass(); $row->column = 1; @@ -305,7 +358,11 @@ public function test_report_customsql_pretify_column_names(): void { report_customsql_pretify_column_names($row, $query)); } - /** @covers ::report_customsql_pretify_column_names */ + /** + * Test plugin report_customsql_pretify_column_names method to test formatting column names with different cases. + * + * @covers ::report_customsql_pretify_column_names + */ public function test_report_customsql_pretify_column_names_multi_line(): void { $row = new \stdClass(); $row->column = 1; @@ -320,7 +377,11 @@ public function test_report_customsql_pretify_column_names_multi_line(): void { report_customsql_pretify_column_names($row, $query)); } - /** @covers ::report_customsql_pretify_column_names */ + /** + * Test plugin report_customsql_pretify_column_names method to handle same name with different capitalisation. + * + * @covers ::report_customsql_pretify_column_names + */ public function test_report_customsql_pretify_column_names_same_name_diff_capitialisation(): void { $row = new \stdClass(); $row->course = 'B747-19B'; @@ -331,7 +392,11 @@ public function test_report_customsql_pretify_column_names_same_name_diff_capiti } - /** @covers ::report_customsql_pretify_column_names */ + /** + * Test plugin report_customsql_pretify_column_names method to handle complex SQL queries with links. + * + * @covers ::report_customsql_pretify_column_names + */ public function test_report_customsql_pretify_column_names_issue(): void { $row = new \stdClass(); $row->website = 'B747-19B'; @@ -362,7 +427,11 @@ public function test_report_customsql_pretify_column_names_issue(): void { } - /** @covers ::report_customsql_display_row */ + /** + * Test plugin report_customsql_display_row method to format a row of data for display. + * + * @covers ::report_customsql_display_row + */ public function test_report_customsql_display_row(): void { $rawdata = [ 'Not a date', @@ -495,7 +564,11 @@ public function test_report_custom_sql_download_report_url(): void { $this->assertEquals($expected, $url->out(false)); } - /** @covers ::report_customsql_write_csv_row */ + /** + * Test plugin report_customsql_write_csv_row method to write a row to a CSV file. + * + * @covers ::report_customsql_write_csv_row + */ public function test_report_customsql_write_csv_row(): void { global $CFG; $this->resetAfterTest(); diff --git a/view_form.php b/view_form.php index d4d9c32..e6ed1b8 100644 --- a/view_form.php +++ b/view_form.php @@ -34,6 +34,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class report_customsql_view_form extends moodleform { + + #[\Override] public function definition() { $mform = $this->_form; From 20cfd937d6bc56b45f6c7fa878e31b25b30a1bb7 Mon Sep 17 00:00:00 2001 From: Anupama Sarjoshi Date: Fri, 25 Jul 2025 09:55:10 +0100 Subject: [PATCH 09/11] Fix grunt errors --- amd/build/userselector.min.js | 2 +- amd/build/userselector.min.js.map | 2 +- amd/src/userselector.js | 13 ++++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/amd/build/userselector.min.js b/amd/build/userselector.min.js index 04a5f85..0c2e04e 100644 --- a/amd/build/userselector.min.js +++ b/amd/build/userselector.min.js @@ -5,6 +5,6 @@ define("report_customsql/userselector",["exports","core/ajax","core/templates"], * @module report_customsql/userselector * @copyright 2020 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.processResults=function(selector,results){return results.map((user=>({value:user.id,label:user._label})))},_exports.transport=function(selector,query,success,failure){_ajax.default.call([{methodname:"report_customsql_get_users",args:{query:query,capability:document.getElementById("id_capability").value}}])[0].then((results=>Promise.all(results.map((user=>_templates.default.render("report_customsql/form-user-selector-suggestion",user).then((html=>(user._label=html,user)))))))).then(success).catch(failure)},_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates)})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.processResults=function(selector,results){return results.map((user=>({value:user.id,label:user._label})))},_exports.transport=function(selector,query,success,failure){_ajax.default.call([{methodname:"report_customsql_get_users",args:{query:query,capability:document.getElementById("id_capability").value}}])[0].then((results=>Promise.all(results.map((async user=>(user._label=await _templates.default.render("report_customsql/form-user-selector-suggestion",user),user)))))).then(success).catch(failure)},_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates)})); //# sourceMappingURL=userselector.min.js.map \ No newline at end of file diff --git a/amd/build/userselector.min.js.map b/amd/build/userselector.min.js.map index 7088de6..c689041 100644 --- a/amd/build/userselector.min.js.map +++ b/amd/build/userselector.min.js.map @@ -1 +1 @@ -{"version":3,"file":"userselector.min.js","sources":["../src/userselector.js"],"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 module to work with the auto-complete of users.\n *\n * @module report_customsql/userselector\n * @copyright 2020 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Templates from 'core/templates';\n\n/**\n * Source of data for Ajax element.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {String} query The query string.\n * @param {Function} success To be called with the results, when received.\n * @param {Function} failure To be called with any errors.\n */\nexport function transport(selector, query, success, failure) {\n Ajax.call([{\n methodname: 'report_customsql_get_users',\n args: {\n query: query,\n capability: document.getElementById('id_capability').value\n }\n }])[0]\n\n .then((results) => {\n // For each user in the result, render the display, and set it on the _label field.\n return Promise.all(results.map((user) => {\n return Templates.render('report_customsql/form-user-selector-suggestion', user)\n .then((html) => {\n user._label = html;\n return user;\n });\n }));\n })\n\n .then(success)\n .catch(failure);\n}\n\n/**\n * Process the results for auto complete elements.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {Array} results An array or results.\n * @return {Array} New array of results.\n */\nexport function processResults(selector, results) {\n return results.map((user) => {\n return {\n value: user.id,\n label: user._label\n };\n });\n}\n"],"names":["selector","results","map","user","value","id","label","_label","query","success","failure","call","methodname","args","capability","document","getElementById","then","Promise","all","Templates","render","html","catch"],"mappings":";;;;;;;8FAiE+BA,SAAUC,gBAC9BA,QAAQC,KAAKC,OACT,CACHC,MAAOD,KAAKE,GACZC,MAAOH,KAAKI,wCAnCEP,SAAUQ,MAAOC,QAASC,uBAC3CC,KAAK,CAAC,CACPC,WAAY,6BACZC,KAAM,CACFL,MAAOA,MACPM,WAAYC,SAASC,eAAe,iBAAiBZ,UAEzD,GAEHa,MAAMhB,SAEIiB,QAAQC,IAAIlB,QAAQC,KAAKC,MACrBiB,mBAAUC,OAAO,iDAAkDlB,MACrEc,MAAMK,OACHnB,KAAKI,OAASe,KACPnB,aAKtBc,KAAKR,SACLc,MAAMb"} \ No newline at end of file +{"version":3,"file":"userselector.min.js","sources":["../src/userselector.js"],"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 module to work with the auto-complete of users.\n *\n * @module report_customsql/userselector\n * @copyright 2020 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Templates from 'core/templates';\n\n/**\n * Source of data for Ajax element.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {String} query The query string.\n * @param {Function} success To be called with the results, when received.\n * @param {Function} failure To be called with any errors.\n */\nexport function transport(selector, query, success, failure) {\n Ajax.call([{\n methodname: 'report_customsql_get_users',\n args: {\n query: query,\n capability: document.getElementById('id_capability').value\n }\n }])[0]\n\n .then((results) => {\n // For each user in the result, render the display, and set it on the _label field.\n return Promise.all(\n results.map(async(user) => {\n user._label = await Templates.render('report_customsql/form-user-selector-suggestion', user);\n return user;\n })\n );\n })\n\n .then(success)\n .catch(failure);\n}\n\n/**\n * Process the results for auto complete elements.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {Array} results An array or results.\n * @return {Array} New array of results.\n */\nexport function processResults(selector, results) {\n return results.map((user) => {\n return {\n value: user.id,\n label: user._label\n };\n });\n}\n"],"names":["selector","results","map","user","value","id","label","_label","query","success","failure","call","methodname","args","capability","document","getElementById","then","Promise","all","async","Templates","render","catch"],"mappings":";;;;;;;8FAgE+BA,SAAUC,gBAC9BA,QAAQC,KAAKC,OACT,CACHC,MAAOD,KAAKE,GACZC,MAAOH,KAAKI,wCAlCEP,SAAUQ,MAAOC,QAASC,uBAC3CC,KAAK,CAAC,CACPC,WAAY,6BACZC,KAAM,CACFL,MAAOA,MACPM,WAAYC,SAASC,eAAe,iBAAiBZ,UAEzD,GAEHa,MAAMhB,SAEIiB,QAAQC,IACXlB,QAAQC,KAAIkB,MAAAA,OACRjB,KAAKI,aAAec,mBAAUC,OAAO,iDAAkDnB,MAChFA,WAKlBc,KAAKR,SACLc,MAAMb"} \ No newline at end of file diff --git a/amd/src/userselector.js b/amd/src/userselector.js index c1ccbd5..5ec31ef 100644 --- a/amd/src/userselector.js +++ b/amd/src/userselector.js @@ -43,13 +43,12 @@ export function transport(selector, query, success, failure) { .then((results) => { // For each user in the result, render the display, and set it on the _label field. - return Promise.all(results.map((user) => { - return Templates.render('report_customsql/form-user-selector-suggestion', user) - .then((html) => { - user._label = html; - return user; - }); - })); + return Promise.all( + results.map(async(user) => { + user._label = await Templates.render('report_customsql/form-user-selector-suggestion', user); + return user; + }) + ); }) .then(success) From c6933c567e4dc3d209eec77bd712c870b3165807 Mon Sep 17 00:00:00 2001 From: Anupama Sarjoshi Date: Fri, 25 Jul 2025 13:36:58 +0100 Subject: [PATCH 10/11] M4.2: External APIs: update to use new base classes This commit adds back the commit 0a47d7730bee38ecb55c0a240bf397f304456e18 and 7e67d5e56268e0dd5b47034bbd746da4ca67c884 --- classes/external/get_users.php | 40 ++++++++++++---------- tests/external/external_get_users_test.php | 12 ++++--- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/classes/external/get_users.php b/classes/external/get_users.php index 3f68b34..bb2c6f4 100644 --- a/classes/external/get_users.php +++ b/classes/external/get_users.php @@ -16,10 +16,12 @@ namespace report_customsql\external; -defined('MOODLE_INTERNAL') || die(); - -global $CFG; -require_once($CFG->libdir . '/externallib.php'); +use core_external\external_api; +use core_external\external_description; +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_single_structure; +use core_external\external_value; /** * Web service used by form autocomplete to get a list of users with a given capability. @@ -28,16 +30,16 @@ * @copyright 2020 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class get_users extends \external_api { +class get_users extends external_api { /** * Parameter declaration. * - * @return \external_function_parameters Parameters + * @return external_function_parameters Parameters */ - public static function execute_parameters(): \external_function_parameters { - return new \external_function_parameters([ - 'query' => new \external_value(PARAM_RAW, 'Contents of the search box.'), - 'capability' => new \external_value(PARAM_CAPABILITY, 'Return only users with this capability in the system context.'), + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'query' => new external_value(PARAM_RAW, 'Contents of the search box.'), + 'capability' => new external_value(PARAM_CAPABILITY, 'Return only users with this capability in the system context.'), ]); } @@ -136,16 +138,16 @@ public static function prepare_result_object(\stdClass $user, array $extrafields /** * Returns type for declaration. * - * @return \external_description Result type + * @return external_description Result type */ - public static function execute_returns(): \external_description { - return new \external_multiple_structure( - new \external_single_structure([ - 'id' => new \external_value(PARAM_INT, 'User id.'), - 'fullname' => new \external_value(PARAM_RAW, 'User full name.'), - 'identity' => new \external_value(PARAM_RAW, 'Additional user identifying info.'), - 'hasidentity' => new \external_value(PARAM_BOOL, 'Whether identity is non-blank.'), - 'profileimageurlsmall' => new \external_value(PARAM_RAW, 'URL of the user profile image.'), + public static function execute_returns(): external_description { + return new external_multiple_structure( + new external_single_structure([ + 'id' => new external_value(PARAM_INT, 'User id.'), + 'fullname' => new external_value(PARAM_RAW, 'User full name.'), + 'identity' => new external_value(PARAM_RAW, 'Additional user identifying info.'), + 'hasidentity' => new external_value(PARAM_BOOL, 'Whether identity is non-blank.'), + 'profileimageurlsmall' => new external_value(PARAM_RAW, 'URL of the user profile image.'), ])); } } diff --git a/tests/external/external_get_users_test.php b/tests/external/external_get_users_test.php index c0beb61..f892e2a 100644 --- a/tests/external/external_get_users_test.php +++ b/tests/external/external_get_users_test.php @@ -16,6 +16,8 @@ namespace report_customsql\external; +use core_external\external_api; + defined('MOODLE_INTERNAL') || die(); global $CFG; @@ -79,7 +81,7 @@ public function test_get_users_site_config(): void { [$admin] = $this->setup_users(); $result = get_users::execute('', 'moodle/site:config'); - $result = \external_api::clean_returnvalue(get_users::execute_returns(), $result); + $result = external_api::clean_returnvalue(get_users::execute_returns(), $result); $this->assertEquals([ [ @@ -99,7 +101,7 @@ public function test_get_users_site_viewreports(): void { [$admin, $manager] = $this->setup_users(); $result = get_users::execute('', 'moodle/site:viewreports'); - $result = \external_api::clean_returnvalue(get_users::execute_returns(), $result); + $result = external_api::clean_returnvalue(get_users::execute_returns(), $result); $this->assertEquals([ [ @@ -126,7 +128,7 @@ public function test_get_users_customsql_view(): void { [$admin, $manager, $coursecreateor] = $this->setup_users(); $result = get_users::execute('', 'report/customsql:view'); - $result = \external_api::clean_returnvalue(get_users::execute_returns(), $result); + $result = external_api::clean_returnvalue(get_users::execute_returns(), $result); $this->assertEquals([ [ @@ -162,7 +164,7 @@ public function test_get_users_serch_without_admins(): void { [, $manager] = $this->setup_users(); $result = get_users::execute('Man', 'report/customsql:view'); - $result = \external_api::clean_returnvalue(get_users::execute_returns(), $result); + $result = external_api::clean_returnvalue(get_users::execute_returns(), $result); $this->assertEquals([ [ @@ -182,7 +184,7 @@ public function test_get_users_serch_with_admin(): void { [$admin] = $this->setup_users(); $result = get_users::execute('n U', 'report/customsql:view'); - $result = \external_api::clean_returnvalue(get_users::execute_returns(), $result); + $result = external_api::clean_returnvalue(get_users::execute_returns(), $result); $this->assertEquals([ [ From 135ce5dd66772038079ad188c9b66b4cb71d9f28 Mon Sep 17 00:00:00 2001 From: Anupama Sarjoshi Date: Fri, 25 Jul 2025 14:17:20 +0100 Subject: [PATCH 11/11] Update readme and version for the 4.4 release --- changes.md | 27 +++++++++++++++++++++++++++ version.php | 6 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/changes.md b/changes.md index 662806c..313ca25 100644 --- a/changes.md +++ b/changes.md @@ -1,5 +1,32 @@ # Change log for the Ad-hoc database queries report +## Changes in 4.4 + +* This version is compatible with Moodle 5.0. +* External APIs: Updated to use new base classes, as the minimum supported version is now Moodle 4.4. +* Fixed a bug so that users only see queries they have permission to view. +* Tidied up various parts of the codebase. +* Improved Behat tests and navigation URLs. +* PHPUnit: Removed unnecessary references to deprecated or removed plugins. +* Accessibility: Required form fields now use the default required field message. +* Fixed #114 — when downloading, return an empty CSV file (with headers) when there are no results. +* Fixed test queries for MySQL containing CHR characters. + + +## Changes in 4.3 + +* The report now shows results for the most recent time the query should have run, + making it clearer when there are no results. +* Improved the heading for the list of past results. +* Log events: Removed legacy logging methods for compatibility with Moodle 4.2. +* Added [wwwroot] to the email subject line to identify which environment sent the email. +* When emailing single-row queries, the number of rows sent is now limited to 5 to avoid building + an overly large list over time. +* Fixed a bug to clean the file report name before generating downloads. +* The session is now unlocked, allowing the users to run more than one report at a time (#113). +* Removed underline on mouse hover + + ## Changes in 4.2 * This version works with Moodle 4.0. diff --git a/version.php b/version.php index d2a47c9..da2a530 100644 --- a/version.php +++ b/version.php @@ -24,10 +24,10 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023121300; -$plugin->requires = 2022112800; +$plugin->version = 2025072400; +$plugin->requires = 2024042200; $plugin->component = 'report_customsql'; $plugin->maturity = MATURITY_STABLE; -$plugin->release = '4.3 for Moodle 4.1+'; +$plugin->release = '4.4 for Moodle 4.4+'; $plugin->outestssufficient = true;