+ * @copyright 2024 Moodle US
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class update_query extends \external_api {
+ /**
+ * Parameter declaration.
+ *
+ * @return \external_function_parameters Parameters
+ */
+ public static function execute_parameters(): \external_function_parameters {
+ return new \external_function_parameters([
+ 'queryid' => new \external_value(PARAM_INT, 'id of the query.', VALUE_REQUIRED),
+ 'displayname' => new \external_value(PARAM_ALPHANUMEXT, 'Short name of the query.', VALUE_DEFAULT, ''),
+ 'description' => new \external_value(PARAM_RAW, 'Description of the query.', VALUE_DEFAULT, ''),
+ 'querysql' => new \external_value(PARAM_RAW, 'SQL query.', VALUE_DEFAULT, ''),
+ 'queryparams' => new \external_value(PARAM_RAW, 'Description of the query updated', VALUE_DEFAULT, ''),
+ 'querylimit' => new \external_value(PARAM_INT, 'Limit of the query updated.', VALUE_DEFAULT, 5000),
+ 'capability' => new \external_value(PARAM_CAPABILITY, 'Capability to view the query updated.',
+ VALUE_DEFAULT, 'moodle/site:config'),
+ 'runable' => new \external_value(PARAM_ALPHAEXT, 'manual, weekly, montly.', VALUE_DEFAULT, 'manual'),
+ 'at' => new \external_value(PARAM_TEXT, 'Time of the execution updated.', VALUE_DEFAULT, ''),
+ 'emailto' => new \external_value(PARAM_EMAIL, 'Email to send the report to updated.', VALUE_DEFAULT, ''),
+ 'emailwhat' => new \external_value(PARAM_TEXT, 'What to send in the email updated.', VALUE_DEFAULT, ''),
+ 'categoryid' => new \external_value(PARAM_INT, 'Category of the query updated.', VALUE_DEFAULT, 1),
+ 'customdir' => new \external_value(PARAM_RAW, 'Custom directory of the query updated.', VALUE_DEFAULT, ''),
+ ]);
+ }
+
+ /**
+ * Update a query.
+ *
+ * @param int $queryid Id of the query.
+ * @param string $displayname Short name of the query.
+ * @param string $description Description of the query.
+ * @param string $querysql SQL query.
+ * @param string $queryparams Description of the query.
+ * @param int $querylimit Limit of the query.
+ * @param string $capability Capability to view the query.
+ * @param string $runable manual, weekly, montly.
+ * @param string $at Time of the execution.
+ * @param string $emailto Email to send the report to.
+ * @param string $emailwhat What to send in the email.
+ * @param int $categoryid Category of the query.
+ * @param string $customdir Custom directory of the query.
+ *
+ * @return array
+ */
+ public static function execute(
+ int $queryid,
+ string $displayname,
+ string $description,
+ string $querysql,
+ string $queryparams,
+ int $querylimit,
+ string $capability,
+ string $runable,
+ string $at,
+ string $emailto,
+ string $emailwhat,
+ int $categoryid,
+ string $customdir
+ ): array {
+ global $CFG, $DB, $USER;
+
+ // We need an associative array in order to use the validation functions.
+ $params = [
+ 'queryid' => $queryid,
+ 'displayname' => $displayname,
+ 'description' => $description,
+ 'querysql' => $querysql,
+ 'queryparams' => $queryparams,
+ 'querylimit' => $querylimit,
+ 'capability' => $capability,
+ 'runable' => $runable,
+ 'at' => $at,
+ 'emailto' => $emailto,
+ 'emailwhat' => $emailwhat,
+ 'categoryid' => $categoryid,
+ 'customdir' => $customdir,
+ ];
+
+ // We checkout the parameters.
+ self::validate_parameters(self::execute_parameters(), $params);
+
+ // We checkout the queryid.
+ if (empty($DB->record_exists('report_customsql_queries', ['id' => $queryid]))) {
+ throw new \moodle_exception('error:invalidqueryid', 'report_customsql');
+ }
+
+ // Validate the context.
+ $context = \context_system::instance();
+ self::validate_context($context);
+ require_capability('report/customsql:definequeries', $context);
+
+ // We update the query.
+ $query = $DB->get_record('report_customsql_queries', ['id' => $queryid], '*');
+ $query->displayname = !empty($displayname) ? $displayname : $query->displayname;
+ $query->description = !empty($description) ? $description : $query->description;
+ $query->querysql = !empty($querysql) ? $querysql : $query->querysql;
+ $query->queryparams = !empty($queryparams) ? $queryparams : $query->queryparams;
+ $query->querylimit = !empty($querylimit) ? $querylimit : $query->querylimit;
+ $query->capability = !empty($capability) ? $capability : $query->capability;
+ $query->runable = !empty($runable) ? $runable : $query->runable;
+ $query->at = !empty($at) ? $at : $query->at;
+ $query->emailto = !empty($emailto) ? $emailto : $query->emailto;
+ $query->emailwhat = !empty($emailwhat) ? $emailwhat : $query->emailwhat;
+ $query->categoryid = !empty($categoryid) ? $categoryid : $query->categoryid;
+ $query->customdir = !empty($customdir) ? $customdir : $query->customdir;
+ $query->usermodified = $USER->id;
+ $query->timemodified = time();
+
+ if (empty($DB->update_record('report_customsql_queries', $query))) {
+ throw new \moodle_exception('error:updatefail', 'report_customsql', '');
+ }
+
+ return ['success' => true];
+ }
+
+ /**
+ * Returns true if the query was successfully updated.
+ *
+ * @return \external_description Result type
+ */
+ public static function execute_returns(): \external_description {
+ return new \external_single_structure([
+ 'success' => new \external_value(PARAM_BOOL, 'Succes of the update.'),
+ ]);
+ }
+}
diff --git a/classes/local/query.php b/classes/local/query.php
index 66c5f35..099bc88 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 | null $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 | null $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..0688286 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 | null $returnurl = null) {
$this->category = $category;
$this->context = $context;
$this->expandable = $expandable;
@@ -79,6 +79,12 @@ public function __construct(report_category $category, context $context, bool $e
$this->returnurl = $returnurl ?? $this->category->get_url();
}
+ /**
+ * Export data for template.
+ *
+ * @param renderer_base $output
+ * @return array
+ */
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..6983ab2 100644
--- a/classes/output/category_query.php
+++ b/classes/output/category_query.php
@@ -59,6 +59,12 @@ public function __construct(query $query, category $category, context $context,
$this->returnurl = $returnurl;
}
+ /**
+ * Export data for template.
+ *
+ * @param \renderer_base $output
+ * @return array
+ */
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 b84fa80..9c6e8bc 100644
--- a/classes/output/index_page.php
+++ b/classes/output/index_page.php
@@ -69,6 +69,12 @@ public function __construct(array $categories, array $queries, context $context,
$this->hidecat = $hidecat;
}
+ /**
+ * Export data for template.
+ *
+ * @param renderer_base $output
+ * @return array
+ */
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 dcb27b4..f23dd62 100644
--- a/classes/utils.php
+++ b/classes/utils.php
@@ -58,8 +58,14 @@ public static function group_queries_by_category($queries) {
return $grouppedqueries;
}
+ /**
+ * Get queries data.
+ *
+ * @param array $queries Array of queries.
+ * @return bool
+ */
public function get_queries_data($queries) {
-
+ return false;
}
/**
diff --git a/db/services.php b/db/services.php
index 4006ffc..1593376 100644
--- a/db/services.php
+++ b/db/services.php
@@ -34,4 +34,88 @@
'type' => 'read',
'ajax' => true,
],
+ 'report_customsql_create_query' => [
+ 'classname' => 'report_customsql\external\create_query',
+ 'methodname' => 'execute',
+ 'classpath' => '',
+ 'description' => 'Use to create a new query.',
+ 'capabilities' => 'report/customsql:definequeries',
+ 'type' => 'read',
+ 'ajax' => true,
+ ],
+ 'report_customsql_update_query' => [
+ 'classname' => 'report_customsql\external\update_query',
+ 'methodname' => 'execute',
+ 'classpath' => '',
+ 'description' => 'Use to update a query.',
+ 'capabilities' => 'report/customsql:definequeries',
+ 'type' => 'read',
+ 'ajax' => true,
+ ],
+ 'report_customsql_delete_query' => [
+ 'classname' => 'report_customsql\external\delete_query',
+ 'methodname' => 'execute',
+ 'classpath' => '',
+ 'description' => 'Use to delete a query.',
+ 'capabilities' => 'report/customsql:definequeries',
+ 'type' => 'read',
+ 'ajax' => true,
+ ],
+ 'report_customsql_list_queries' => [
+ 'classname' => 'report_customsql\external\list_queries',
+ 'methodname' => 'execute',
+ 'classpath' => '',
+ 'description' => 'Use to list the sql queries.',
+ 'capabilities' => 'report/customsql:definequeries',
+ 'type' => 'read',
+ 'ajax' => true,
+ ],
+ 'report_customsql_query_details' => [
+ 'classname' => 'report_customsql\external\query_details',
+ 'methodname' => 'execute',
+ 'classpath' => '',
+ 'description' => 'Use to get the details of a query.',
+ 'capabilities' => 'report/customsql:definequeries',
+ 'type' => 'read',
+ 'ajax' => true,
+ ],
+ 'report_customsql_get_query_results' => [
+ 'classname' => 'report_customsql\external\get_query_results',
+ 'methodname' => 'execute',
+ 'classpath' => '',
+ 'description' => 'Use to get the results of a query.',
+ 'capabilities' => 'report/customsql:definequeries',
+ 'type' => 'read',
+ 'ajax' => true,
+ ],
+ 'report_customsql_query_validation' => [
+ 'classname' => 'report_customsql\external\query_validation',
+ 'methodname' => 'execute',
+ 'classpath' => '',
+ 'description' => 'Use to validate a query.',
+ 'capabilities' => 'report/customsql:definequeries',
+ 'type' => 'read',
+ 'ajax' => true,
+ ],
+];
+
+$services = [
+ 'report_customsql_service' => [
+ 'functions' => [
+ 'report_customsql_get_users',
+ 'report_customsql_create_query',
+ 'report_customsql_update_query',
+ 'report_customsql_delete_query',
+ 'report_customsql_list_queries',
+ 'report_customsql_query_details',
+ 'report_customsql_get_query_results',
+ 'report_customsql_query_validation',
+ ],
+ 'requiredcapability' => '',
+ 'restrictedusers' => 0,
+ 'enabled' => 1,
+ 'shortname' => 'customsqlws',
+ 'downloadfiles' => 1,
+ 'uploadfiles' => 0,
+ ],
];
diff --git a/edit_form.php b/edit_form.php
index a0f9f5c..eedc468 100644
--- a/edit_form.php
+++ b/edit_form.php
@@ -34,6 +34,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_customsql_edit_form extends moodleform {
+
+ /**
+ * Define the form.
+ */
public function definition() {
global $CFG;
@@ -43,7 +47,7 @@ public function definition() {
$categoryoptions = report_customsql_category_options();
$mform->addElement('select', 'categoryid', get_string('category', 'report_customsql'),
$categoryoptions);
- if ($customdata['forcecategoryid'] && array_key_exists($customdata['forcecategoryid'], $categoryoptions)) {
+ if (!empty($customdata['forcecategoryid']) && array_key_exists($customdata['forcecategoryid'], $categoryoptions)) {
$catdefault = $customdata['forcecategoryid'];
} else {
$catdefault = isset($categoryoptions[1]) ? 1 : key($categoryoptions);
@@ -70,7 +74,7 @@ public function definition() {
$mform->registerNoSubmitButton('verify');
$hasparameters = 0;
- if ($customdata['queryparams']) {
+ if (!empty($customdata['queryparams'])) {
$mform->addElement('static', 'params', '', get_string('queryparams', 'report_customsql'));
foreach ($customdata['queryparams'] as $queryparam => $formparam) {
$type = report_customsql_get_element_type($queryparam);
@@ -155,6 +159,11 @@ public function definition() {
$this->add_action_buttons();
}
+ /**
+ * Set the form data.
+ *
+ * @param stdClass $currentvalues
+ */
public function set_data($currentvalues) {
global $DB, $OUTPUT;
@@ -181,6 +190,13 @@ public function set_data($currentvalues) {
$mform->addElement('html', $reportinfo);
}
+ /**
+ * Validate the form data.
+ *
+ * @param array $data
+ * @param array $files
+ * @return array
+ */
public function validation($data, $files) {
global $CFG, $DB, $USER;
@@ -262,7 +278,7 @@ public function validation($data, $files) {
// Check querylimit is in range.
$maxlimit = get_config('report_customsql', 'querylimitmaximum');
- if (empty($data['querylimit']) || $data['querylimit'] > $maxlimit) {
+ if ($data['querylimit'] > $maxlimit) {
$errors['querylimit'] = get_string('querylimitrange', 'report_customsql', $maxlimit);
}
diff --git a/lang/en/report_customsql.php b/lang/en/report_customsql.php
index 26ad086..eb0f4f2 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,21 +83,25 @@
$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';
+$string['error:cannotdeletequery'] = 'Error: cannot delete query';
+$string['error:invalidqueryid'] = 'Invalid query id';
+$string['error:updatefail'] = 'Update failed';
$string['errordeletingcategory'] = 'Error deleting a query category.
It must be empty to delete it.
';
$string['errordeletingreport'] = 'Error deleting a query.';
$string['errorinsertingreport'] = 'Error inserting a query.';
$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['limittestrows'] = 'Maximum allowed limit on rows on the ws Query validations';
+$string['limittestrows_desc'] = 'This is the limit on the Query validation WS(Up to 99).';
$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 +119,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 +189,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..a8167b7 100644
--- a/locallib.php
+++ b/locallib.php
@@ -29,6 +29,14 @@
define('REPORT_CUSTOMSQL_LIMIT_EXCEEDED_MARKER', '-- ROW LIMIT EXCEEDED --');
+/**
+ * Execute a custom SQL query.
+ *
+ * @param string $sql the SQL query.
+ * @param array $params the parameters to substitute into the query.
+ * @param int $limitnum the maximum number of rows to return.
+ * @return moodle_recordset a recordset.
+ */
function report_customsql_execute_query($sql, $params = null, $limitnum = null) {
global $CFG, $DB;
@@ -48,6 +56,13 @@ function report_customsql_execute_query($sql, $params = null, $limitnum = null)
return $DB->get_recordset_sql($sql, $params, 0, $limitnum);
}
+/**
+ * Prepare the SQL query for execution.
+ *
+ * @param stdclass $report report record from customsql table.
+ * @param int $timenow unix timestamp - usually "now()"
+ * @return string the SQL query.
+ */
function report_customsql_prepare_sql($report, $timenow) {
global $USER;
$sql = $report->querysql;
@@ -85,6 +100,7 @@ function report_customsql_get_query_placeholders_and_field_names(string $querysq
/**
* Return the type of form field to use for a placeholder, based on its name.
+ *
* @param string $name the placeholder name.
* @return string a formslib element type, for example 'text' or 'date_time_selector'.
*/
@@ -100,8 +116,9 @@ 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.
+ * @return int|null the timestamp of the CSV file, or null if there is no data.
*/
function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty = false) {
global $DB;
@@ -110,7 +127,7 @@ function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty
$sql = report_customsql_prepare_sql($report, $timenow);
$queryparams = !empty($report->queryparams) ? unserialize($report->queryparams) : [];
- $querylimit = $report->querylimit ?? get_config('report_customsql', 'querylimitdefault');
+ $querylimit = !empty($report->querylimit) ? $report->querylimit : get_config('report_customsql', 'querylimitdefault');
if ($returnheaderwhenempty) {
// We want the export to always generate a CSV file so we modify the query slightly
// to generate an extra "null" values row, so we can get the column names,
@@ -120,7 +137,7 @@ function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty
LEFT JOIN ($sql) as subq on true";
}
// Query one extra row, so we can tell if we hit the limit.
- $rs = report_customsql_execute_query($sql, $queryparams, $querylimit + 1);
+ $rs = report_customsql_execute_query($sql, $queryparams, !empty($querylimit) ? $querylimit + 1 : 0);
$csvfilenames = [];
$csvtimestamp = null;
@@ -151,7 +168,7 @@ function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty
}
}
if ($report->singlerow) {
- array_unshift($data, \core_date::strftime('%Y-%m-%d', $timenow));
+ array_unshift($data, date('Y-m-d', $timenow));
}
report_customsql_write_csv_row($handle, $data);
$count += 1;
@@ -159,7 +176,7 @@ function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty
$rs->close();
if (!empty($handle)) {
- if ($count > $querylimit) {
+ if (!empty($querylimit) && $count > $querylimit) {
report_customsql_write_csv_row($handle, [REPORT_CUSTOMSQL_LIMIT_EXCEEDED_MARKER]);
}
@@ -197,6 +214,8 @@ function report_customsql_generate_csv($report, $timenow, $returnheaderwhenempty
}
/**
+ * Validate a value as an integer.
+ *
* @param mixed $value some value
* @return bool whether $value is an integer, or a string that looks like an integer.
*/
@@ -204,6 +223,13 @@ function report_customsql_is_integer($value) {
return (string) (int) $value === (string) $value;
}
+/**
+ * Create a csv file.
+ *
+ * @param stdclass $report report record from customsql table.
+ * @param int $timenow unix timestamp - usually "now()"
+ * @return string $csvfilename the CSV file to copy.
+ */
function report_customsql_csv_filename($report, $timenow) {
if ($report->runable == 'manual') {
return report_customsql_temp_cvs_name($report->id, $timenow);
@@ -217,22 +243,42 @@ function report_customsql_csv_filename($report, $timenow) {
}
}
+/**
+ * Create a temporary csv file.
+ *
+ * @param int $reportid the report id.
+ * @param int $timestamp the timestamp.
+ * @return array with two elements: the filename and the timestamp.
+ */
function report_customsql_temp_cvs_name($reportid, $timestamp) {
global $CFG;
$path = 'admin_report_customsql/temp/'.$reportid;
make_upload_directory($path);
- return [$CFG->dataroot.'/'.$path.'/'.\core_date::strftime('%Y%m%d-%H%M%S', $timestamp).'.csv',
+ return [$CFG->dataroot.'/'.$path.'/' . $timestamp . '.csv',
$timestamp];
}
+/**
+ * Create a scheduled csv file.
+ *
+ * @param int $reportid the report id.
+ * @param int $timestart the timestamp.
+ * @return array with two elements: the filename and the timestart.
+ */
function report_customsql_scheduled_cvs_name($reportid, $timestart) {
global $CFG;
$path = 'admin_report_customsql/'.$reportid;
make_upload_directory($path);
- return [$CFG->dataroot.'/'.$path.'/'.\core_date::strftime('%Y%m%d-%H%M%S', $timestart).'.csv',
+ return [$CFG->dataroot.'/'.$path.'/' . $timestart. '.csv',
$timestart];
}
+/**
+ * Create a accumulating csv file.
+ *
+ * @param int $reportid the report id.
+ * @return array
+ */
function report_customsql_accumulating_cvs_name($reportid) {
global $CFG;
$path = 'admin_report_customsql/'.$reportid;
@@ -240,6 +286,12 @@ function report_customsql_accumulating_cvs_name($reportid) {
return [$CFG->dataroot.'/'.$path.'/accumulate.csv', 0];
}
+/**
+ * Get archive times for a report.
+ *
+ * @param stdclass $report report record from customsql table.
+ * @return array timestamps
+ */
function report_customsql_get_archive_times($report) {
global $CFG;
if ($report->runable == 'manual' || $report->singlerow) {
@@ -257,10 +309,25 @@ function report_customsql_get_archive_times($report) {
return $archivetimes;
}
+/**
+ * Substitutes the time tokens in the SQL query.
+ *
+ * @param string $sql the SQL query.
+ * @param int $start the start time.
+ * @param int $end the end time.
+ * @return string the SQL query with the time tokens substituted.
+ */
function report_customsql_substitute_time_tokens($sql, $start, $end) {
return str_replace(['%%STARTTIME%%', '%%ENDTIME%%'], [$start, $end], $sql);
}
+/**
+ * Substitutes the user token in the SQL query.
+ *
+ * @param string $sql the SQL query.
+ * @param int $userid the user id.
+ * @return string the SQL query with the user token substituted.
+ */
function report_customsql_substitute_user_token($sql, $userid) {
return str_replace('%%USERID%%', $userid, $sql);
}
@@ -300,6 +367,11 @@ function report_customsql_downloadurl($reportid, $params = []) {
return $downloadurl;
}
+/**
+ * Supported capabilities.
+ *
+ * @return array
+ */
function report_customsql_capability_options() {
return [
'report/customsql:view' => get_string('anyonewhocanveiwthisreport', 'report_customsql'),
@@ -308,6 +380,13 @@ function report_customsql_capability_options() {
];
}
+
+/**
+ * Get the list on run options.
+ *
+ * @param string $type the type of report (manual, daily, weekly or monthly).
+ * @return array
+ */
function report_customsql_runable_options($type = null) {
if ($type === 'manual') {
return ['manual' => get_string('manual', 'report_customsql')];
@@ -320,6 +399,11 @@ function report_customsql_runable_options($type = null) {
];
}
+/**
+ * Get the list of time options.
+ *
+ * @return array
+ */
function report_customsql_daily_at_options() {
$time = [];
for ($h = 0; $h < 24; $h++) {
@@ -329,33 +413,67 @@ function report_customsql_daily_at_options() {
return $time;
}
+/**
+ * Get the list of email options.
+ *
+ * @return array
+ */
function report_customsql_email_options() {
return ['emailnumberofrows' => get_string('emailnumberofrows', 'report_customsql'),
'emailresults' => get_string('emailresults', 'report_customsql'),
];
}
+/**
+ * Get the list of bad words.
+ *
+ * @return array
+ */
function report_customsql_bad_words_list() {
return ['ALTER', 'CREATE', 'DELETE', 'DROP', 'GRANT', 'INSERT', 'INTO',
'TRUNCATE', 'UPDATE'];
}
+/**
+ * Validate the query contains no bad words.
+ *
+ * @param string $string the query.
+ * @return bool
+ */
function report_customsql_contains_bad_word($string) {
return preg_match('/\b('.implode('|', report_customsql_bad_words_list()).')\b/i', $string);
}
+/**
+ * Trigger a report_customsql\event\query_deleted event.
+ *
+ * @param int $id the id of the deleted query.
+ * @return void
+ */
function report_customsql_log_delete($id) {
$event = \report_customsql\event\query_deleted::create(
['objectid' => $id, 'context' => context_system::instance()]);
$event->trigger();
}
+/**
+ * Trigger a report_customsql\event\query_edited event.
+ *
+ * @param int $id the id of the edit query.
+ * @return void
+ */
function report_customsql_log_edit($id) {
$event = \report_customsql\event\query_edited::create(
['objectid' => $id, 'context' => context_system::instance()]);
$event->trigger();
}
+/**
+ * Trigger a report_customsql\event\query_viewed event.
+ *
+ * @param int $id the id of the view query.
+ * @return void
+ */
function report_customsql_log_view($id) {
$event = \report_customsql\event\query_viewed::create(
['objectid' => $id, 'context' => context_system::instance()]);
@@ -366,8 +484,8 @@ 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)
- * @return stdClass[] relevant rows from report_customsql_queries.
+ * @param string $type type of report (manual, daily, weekly or monthly)
+ * @return array relevant rows from report_customsql_queries.
*/
function report_customsql_get_reports_for($categoryid, $type) {
global $DB;
@@ -380,8 +498,9 @@ 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 array $reports the result of DB query
+ * @param string $type type of report (manual, daily, weekly or monthly)
+ * @return void
*/
function report_customsql_print_reports_for($reports, $type) {
global $OUTPUT;
@@ -487,6 +606,13 @@ function report_customsql_display_row($row, $linkcolumns) {
return $rowdata;
}
+/**
+ * Time note for a report.
+ *
+ * @param stdClass $report report record from customsql table.
+ * @param string $tag the tag to use for the note.
+ * @return string the note.
+ */
function report_customsql_time_note($report, $tag) {
if ($report->lastrun) {
$a = new stdClass;
@@ -501,7 +627,13 @@ function report_customsql_time_note($report, $tag) {
return html_writer::tag($tag, $note, ['class' => 'admin_note']);
}
-
+/**
+ * Generate pretty column names from a row of data.
+ *
+ * @param stdClass $row a row of data.
+ * @param string $querysql the query that generated the row.
+ * @return string[] the column names.
+ */
function report_customsql_pretify_column_names($row, $querysql) {
$colnames = [];
@@ -559,6 +691,13 @@ function report_customsql_read_csv_row($handle) {
return fgetcsv($handle, 0, ',', '"', $disablestupidphpescaping);
}
+/**
+ * Start a CSV file.
+ *
+ * @param resource $handle the file pointer.
+ * @param stdClass $firstrow the first row of data.
+ * @param stdClass $report report record from customsql table.
+ */
function report_customsql_start_csv($handle, $firstrow, $report) {
$colnames = report_customsql_pretify_column_names($firstrow, $report->querysql);
if ($report->singlerow) {
@@ -568,6 +707,8 @@ function report_customsql_start_csv($handle, $firstrow, $report) {
}
/**
+ * Daily time start.
+ *
* @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 +726,12 @@ function report_customsql_get_daily_time_starts($timenow, $at) {
];
}
+/**
+ * Time start of the week.
+ *
+ * @param int $timenow a timestamp.
+ * @return array with two elements: the timestamp for the start of the week
+ */
function report_customsql_get_week_starts($timenow) {
$dateparts = getdate($timenow);
@@ -603,6 +750,12 @@ function report_customsql_get_week_starts($timenow) {
];
}
+/**
+ * Time start of the month.
+ *
+ * @param int $timenow a timestamp.
+ * @return array with two elements: the timestamp for the start of the month
+ */
function report_customsql_get_month_starts($timenow) {
$dateparts = getdate($timenow);
@@ -612,6 +765,13 @@ function report_customsql_get_month_starts($timenow) {
];
}
+/**
+ * Get the start times for a report.
+ *
+ * @param stdClass $report report record from customsql table.
+ * @param int $timenow unix timestamp - usually "now()"
+ * @return array with two elements: the start time and the end time.
+ */
function report_customsql_get_starts($report, $timenow) {
switch ($report->runable) {
case 'daily':
@@ -625,11 +785,17 @@ function report_customsql_get_starts($report, $timenow) {
}
}
+/**
+ * Get old temp files.
+ *
+ * @param int $upto unix timestamp - usually "now()"
+ * @return int number of files deleted.
+ */
function report_customsql_delete_old_temp_files($upto) {
global $CFG;
$count = 0;
- $comparison = \core_date::strftime('%Y%m%d-%H%M%S', $upto).'csv';
+ $comparison = $upto . 'csv';
$files = glob($CFG->dataroot.'/admin_report_customsql/temp/*/*.csv');
if (empty($files)) {
@@ -678,6 +844,12 @@ function report_customsql_validate_users($userids, $capability) {
return null;
}
+/**
+ * Get message for email when there is no data.
+ *
+ * @param stdClass $report report settings from the database.
+ * @return stdClass the message object.
+ */
function report_customsql_get_message_no_data($report) {
// Construct subject.
$subject = report_customsql_email_subject(0, $report);
@@ -696,6 +868,13 @@ function report_customsql_get_message_no_data($report) {
return $message;
}
+/**
+ * Get message for email when there is data.
+ *
+ * @param stdClass $report report settings from the database.
+ * @param string $csvfilename the CSV file to copy.
+ * @return stdClass the message object.
+ */
function report_customsql_get_message($report, $csvfilename) {
$handle = fopen($csvfilename, 'r');
$table = new html_table();
@@ -776,6 +955,12 @@ function report_customsql_email_subject(int $countrows, stdClass $report): strin
}
}
+/**
+ * Email the report.
+ *
+ * @param stdClass $report report settings from the database.
+ * @param string $csvfilename the CSV file to copy.
+ */
function report_customsql_email_report($report, $csvfilename = null) {
global $DB;
@@ -801,6 +986,12 @@ function report_customsql_email_report($report, $csvfilename = null) {
}
}
+/**
+ * Get the list of reports that are ready to run.
+ *
+ * @param int $timenow unix timestamp - usually "now()"
+ * @return array
+ */
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 +1054,11 @@ function report_customsql_is_daily_report_ready($report, $timenow) {
return false;
}
+/**
+ * Get the list of categories.
+ *
+ * @return array
+ */
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/settings.php b/settings.php
index 06e4664..9e80ae0 100644
--- a/settings.php
+++ b/settings.php
@@ -46,6 +46,10 @@
$settings->add(new admin_setting_configtext_with_maxlength('report_customsql/querylimitmaximum',
get_string('querylimitmaximum', 'report_customsql'),
get_string('querylimitmaximum_desc', 'report_customsql'), 5000, PARAM_INT, null, 10));
+
+ $settings->add(new admin_setting_configtext_with_maxlength('report_customsql/limittestrows',
+ get_string('limittestrows', 'report_customsql'),
+ get_string('limittestrows_desc', 'report_customsql'), 0, PARAM_INT, null, 2));
}
$ADMIN->add('reports', new admin_externalpage('report_customsql',
diff --git a/tests/external/external_create_query_test.php b/tests/external/external_create_query_test.php
new file mode 100644
index 0000000..ed9f01b
--- /dev/null
+++ b/tests/external/external_create_query_test.php
@@ -0,0 +1,81 @@
+.
+
+namespace report_customsql\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+
+/**
+ * Tests for the create_query web service.
+ *
+ * @package report_customsql
+ * @category external
+ * @author Oscar Nadjar
+ * @copyright 2023 Moodle Us
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers \report_customsql\external\create_query
+ * @runTestsInSeparateProcesses
+ */
+final class external_create_query_test extends \externallib_advanced_testcase {
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ }
+
+ public function test_create_query(): void {
+
+ global $DB;
+
+ $displayname = 'test';
+ $description = 'test';
+ $querysql = 'SELECT * FROM {user}';
+ $queryparams = '';
+ $querylimit = 5000;
+ $capability = 'moodle/site:config';
+ $runable = 'manual';
+ $at = '';
+ $emailto = 'test@mail.com';
+ $emailwhat = 'Test email';
+ $categoryid = 1;
+ $customdir = '';
+
+ $result = create_query::execute(
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(create_query::execute_returns(), $result);
+
+ $query = $DB->get_record('report_customsql_queries', []);
+ $this->assertEquals($query->displayname, $displayname);
+ $this->assertEquals($query->description, $description);
+ $this->assertEquals($query->querysql, $querysql);
+ $this->assertEquals($query->queryparams, $queryparams);
+ $this->assertEquals($query->querylimit, $querylimit);
+ $this->assertEquals($query->capability, $capability);
+ $this->assertEquals($query->runable, $runable);
+ $this->assertEquals($query->at, $at);
+ $this->assertEquals($query->emailto, $emailto);
+ $this->assertEquals($query->emailwhat, $emailwhat);
+ $this->assertEquals($query->categoryid, $categoryid);
+ $this->assertEquals($query->customdir, $customdir);
+ }
+}
diff --git a/tests/external/external_delete_query_test.php b/tests/external/external_delete_query_test.php
new file mode 100644
index 0000000..9828115
--- /dev/null
+++ b/tests/external/external_delete_query_test.php
@@ -0,0 +1,75 @@
+.
+
+namespace report_customsql\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+
+/**
+ * Tests for the delete_query web service.
+ *
+ * @package report_customsql
+ * @category external
+ * @author Oscar Nadjar
+ * @copyright 2023 Moodle Us
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers \report_customsql\external\delete_query
+ * @runTestsInSeparateProcesses
+ */
+final class external_delete_query_test extends \externallib_advanced_testcase {
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ }
+
+ public function test_delete_query(): void {
+
+ global $DB;
+
+ $displayname = 'test';
+ $description = 'test';
+ $querysql = 'SELECT * FROM {user}';
+ $queryparams = '';
+ $querylimit = 5000;
+ $capability = 'moodle/site:config';
+ $runable = 'manual';
+ $at = '';
+ $emailto = 'test@mail.com';
+ $emailwhat = 'Test email';
+ $categoryid = 1;
+ $customdir = '';
+
+ $result = create_query::execute(
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(create_query::execute_returns(), $result);
+
+ $query = $DB->get_record('report_customsql_queries', []);
+
+ $result = delete_query::execute($query->id);
+ $result = \external_api::clean_returnvalue(delete_query::execute_returns(), $result);
+
+ $query = $DB->get_record('report_customsql_queries', ['id' => $query->id]);
+ $this->assertFalse($query);
+ }
+}
diff --git a/tests/external/external_get_query_results_test.php b/tests/external/external_get_query_results_test.php
new file mode 100644
index 0000000..25b7c8b
--- /dev/null
+++ b/tests/external/external_get_query_results_test.php
@@ -0,0 +1,81 @@
+.
+
+namespace report_customsql\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+
+/**
+ * Tests for the get_query_results web service.
+ *
+ * @package report_customsql
+ * @category external
+ * @author Oscar Nadjar
+ * @copyright 2023 Moodle Us
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers \report_customsql\external\get_query_results
+ * @runTestsInSeparateProcesses
+ */
+final class external_get_query_results_test extends \externallib_advanced_testcase {
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ }
+
+ public function test_get_query_results(): void {
+
+ global $DB;
+
+ $displayname = 'test';
+ $description = 'test';
+ $querysql = 'SELECT * FROM {user}';
+ $queryparams = '';
+ $querylimit = 5000;
+ $capability = 'moodle/site:config';
+ $runable = 'manual';
+ $at = '';
+ $emailto = 'test@mail.com';
+ $emailwhat = 'Test email';
+ $categoryid = 1;
+ $customdir = '';
+
+ $result = create_query::execute(
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(create_query::execute_returns(), $result);
+
+ $report = $DB->get_record('report_customsql_queries', ['id' => $result['queryid']]);
+ $csvtimestamp = report_customsql_generate_csv($report, time());
+ $result = get_query_results::execute($report->id, 'csv');
+ $result = \external_api::clean_returnvalue(get_query_results::execute_returns(), $result);
+ $result = reset($result['results']);
+
+ $date = date('Y-m-d H:i:s', $csvtimestamp);
+ $this->assertEquals($date, $result['date']);
+
+ $url = new \moodle_url('/webservice/pluginfile.php/' .
+ \context_system::instance()->id . '/report_customsql/download/' . $report->id . '/');
+ $donwloadurl = new \moodle_url($url, ['dataformat' => 'csv', 'timestamp' => $csvtimestamp]);
+ $this->assertEquals($donwloadurl->out(false), $result['downloadurl']);
+ }
+}
diff --git a/tests/external/external_get_users_test.php b/tests/external/external_get_users_test.php
index 5a1e1af..9968ac8 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,12 @@
* @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 {
+ /**
+ * Set up the test case.
+ * @return array
+ */
protected function setup_users(): array {
global $DB, $USER;
diff --git a/tests/external/external_list_queries_test.php b/tests/external/external_list_queries_test.php
new file mode 100644
index 0000000..334253a
--- /dev/null
+++ b/tests/external/external_list_queries_test.php
@@ -0,0 +1,88 @@
+.
+
+namespace report_customsql\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+
+/**
+ * Tests for the create_query web service.
+ *
+ * @package report_customsql
+ * @category external
+ * @author Oscar Nadjar
+ * @copyright 2023 Moodle Us
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers \report_customsql\external\list_queries
+ * @runTestsInSeparateProcesses
+ */
+final class external_list_queries_test extends \externallib_advanced_testcase {
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ }
+
+ public function test_list_queries(): void {
+
+ global $DB;
+
+ $displayname = 'test';
+ $description = 'test';
+ $querysql = 'SELECT * FROM {user}';
+ $queryparams = '';
+ $querylimit = 5000;
+ $capability = 'moodle/site:config';
+ $runable = 'manual';
+ $at = '';
+ $emailto = 'test@mail.com';
+ $emailwhat = 'Test email';
+ $categoryid = 1;
+ $customdir = '';
+
+ $result = create_query::execute(
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(create_query::execute_returns(), $result);
+
+ $displayname = 'test2';
+
+ $result = create_query::execute(
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(create_query::execute_returns(), $result);
+
+ $querylistpage1 = list_queries::execute(1, 1);
+ $querylistpage1 = \external_api::clean_returnvalue(list_queries::execute_returns(), $querylistpage1);
+
+ $queries = $querylistpage1['queries'];
+ $this->assertEquals(1, count($queries));
+ $this->assertEquals('test', $queries[0]['displayname']);
+
+ $querylistpage2 = list_queries::execute(2, 1);
+ $querylistpage2 = \external_api::clean_returnvalue(list_queries::execute_returns(), $querylistpage2);
+
+ $queries = $querylistpage2['queries'];
+ $this->assertEquals(1, count($queries));
+ $this->assertEquals('test2', $queries[0]['displayname']);
+ }
+}
diff --git a/tests/external/external_query_details_test.php b/tests/external/external_query_details_test.php
new file mode 100644
index 0000000..ced1443
--- /dev/null
+++ b/tests/external/external_query_details_test.php
@@ -0,0 +1,84 @@
+.
+
+namespace report_customsql\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+
+/**
+ * Tests for the query_details web service.
+ *
+ * @package report_customsql
+ * @category external
+ * @author Oscar Nadjar
+ * @copyright 2023 Moodle Us
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers \report_customsql\external\query_details
+ * @runTestsInSeparateProcesses
+ */
+final class external_query_details_test extends \externallib_advanced_testcase {
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ }
+
+ public function test_query_details(): void {
+
+ global $DB;
+
+ $displayname = 'test';
+ $description = 'test';
+ $querysql = 'SELECT * FROM {user}';
+ $queryparams = '';
+ $querylimit = 5000;
+ $capability = 'moodle/site:config';
+ $runable = 'manual';
+ $at = '';
+ $emailto = 'test@mail.com';
+ $emailwhat = 'Test email';
+ $categoryid = 1;
+ $customdir = '';
+
+ $result = create_query::execute(
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(create_query::execute_returns(), $result);
+
+ $querydetails = query_details::execute($result['queryid']);
+ $querydetails = \external_api::clean_returnvalue(query_details::execute_returns(), $querydetails);
+ $querydetails = $querydetails['query'];
+
+ $this->assertEquals($querydetails['displayname'], $displayname);
+ $this->assertEquals($querydetails['description'], $description);
+ $this->assertEquals($querydetails['querysql'], $querysql);
+ $this->assertEquals($querydetails['queryparams'], $queryparams);
+ $this->assertEquals($querydetails['querylimit'], $querylimit);
+ $this->assertEquals($querydetails['capability'], $capability);
+ $this->assertEquals($querydetails['runable'], $runable);
+ $this->assertEquals($querydetails['at'], $at);
+ $this->assertEquals($querydetails['emailto'], $emailto);
+ $this->assertEquals($querydetails['emailwhat'], $emailwhat);
+ $this->assertEquals($querydetails['categoryid'], $categoryid);
+ $this->assertEquals($querydetails['customdir'], $customdir);
+ }
+}
diff --git a/tests/external/external_update_query_test.php b/tests/external/external_update_query_test.php
new file mode 100644
index 0000000..77e3391
--- /dev/null
+++ b/tests/external/external_update_query_test.php
@@ -0,0 +1,101 @@
+.
+
+namespace report_customsql\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+
+/**
+ * Tests for the upddate_query web service.
+ *
+ * @package report_customsql
+ * @category external
+ * @author Oscar Nadjar
+ * @copyright 2023 Moodle Us
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @covers \report_customsql\external\upddate_query
+ * @runTestsInSeparateProcesses
+ */
+final class external_update_query_test extends \externallib_advanced_testcase {
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->resetAfterTest();
+ $this->setAdminUser();
+ }
+
+ public function test_upddate_query(): void {
+
+ global $DB;
+
+ $displayname = 'test';
+ $description = 'test';
+ $querysql = 'SELECT * FROM {user}';
+ $queryparams = '';
+ $querylimit = 5000;
+ $capability = 'moodle/site:config';
+ $runable = 'manual';
+ $at = '';
+ $emailto = 'test@mail.com';
+ $emailwhat = 'Test email';
+ $categoryid = 1;
+ $customdir = '';
+
+ $result = create_query::execute(
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(create_query::execute_returns(), $result);
+
+ $query = $DB->get_record('report_customsql_queries', []);
+
+ $displayname = 'testupdate';
+ $description = 'testupdate';
+ $querysql = 'SELECT id FROM {user}';
+ $queryparams = '';
+ $querylimit = 6000;
+ $capability = 'moodle/site:config';
+ $runable = 'manual';
+ $at = '';
+ $emailto = 'testupdate@mail.com';
+ $emailwhat = 'Test email update';
+ $categoryid = 1;
+ $customdir = '/updatedir';
+
+ $result = update_query::execute( $query->id,
+ $displayname, $description, $querysql, $queryparams, $querylimit,
+ $capability, $runable, $at, $emailto, $emailwhat, $categoryid, $customdir);
+ $result = \external_api::clean_returnvalue(update_query::execute_returns(), $result);
+
+ $updatedquery = $DB->get_record('report_customsql_queries', []);
+ $this->assertEquals($updatedquery->displayname, $displayname);
+ $this->assertEquals($updatedquery->description, $description);
+ $this->assertEquals($updatedquery->querysql, $querysql);
+ $this->assertEquals($updatedquery->queryparams, $queryparams);
+ $this->assertEquals($updatedquery->querylimit, $querylimit);
+ $this->assertEquals($updatedquery->capability, $capability);
+ $this->assertEquals($updatedquery->runable, $runable);
+ $this->assertEquals($updatedquery->at, $at);
+ $this->assertEquals($updatedquery->emailto, $emailto);
+ $this->assertEquals($updatedquery->emailwhat, $emailwhat);
+ $this->assertEquals($updatedquery->categoryid, $categoryid);
+ $this->assertEquals($updatedquery->customdir, $customdir);
+ }
+}
diff --git a/tests/local/category_test.php b/tests/local/category_test.php
index 7bb72bb..253320c 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..2562119 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;
@@ -37,6 +37,7 @@ class privacy_test extends \core_privacy\tests\provider_testcase {
protected $user3;
public function setUp(): void {
+ parent::setUp();
$this->resetAfterTest();
$this->setAdminUser();
diff --git a/tests/report_test.php b/tests/report_test.php
index d52efcf..bbd9e0e 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 */
+
+ /**
+ * Test plugin get_month_starts method.
+ * @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,10 @@ 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 */
+ /**
+ * Test element type detection.
+ * @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 +131,20 @@ 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 token substitution.
+ * @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 */
+ /**
+ * Test 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 +155,10 @@ public function test_report_customsql_capability_options(): void {
}
- /** @covers ::report_customsql_runable_options */
+ /**
+ * Test runable options.
+ * @covers ::report_customsql_runable_options
+ */
public function test_report_customsql_runable_options(): void {
$options = [
'manual' => get_string('manual', 'report_customsql'),
@@ -154,7 +170,10 @@ public function test_report_customsql_runable_options(): void {
$this->assertEquals($options, report_customsql_runable_options());
}
- /** @covers ::report_customsql_daily_at_options */
+ /**
+ * Test daily run options.
+ * @covers ::report_customsql_daily_at_options
+ */
public function test_report_customsql_daily_at_options(): void {
$time = [];
for ($h = 0; $h < 24; $h++) {
@@ -164,7 +183,10 @@ public function test_report_customsql_daily_at_options(): void {
$this->assertEquals($time, report_customsql_daily_at_options());
}
- /** @covers ::report_customsql_email_options */
+ /**
+ * Test email options.
+ * @covers ::report_customsql_email_options
+ */
public function test_report_customsql_email_options(): void {
$options = [
'emailnumberofrows' => get_string('emailnumberofrows', 'report_customsql'),
@@ -173,19 +195,28 @@ public function test_report_customsql_email_options(): void {
$this->assertEquals($options, report_customsql_email_options());
}
- /** @covers ::report_customsql_bad_words_list */
+ /**
+ * Test bad words list.
+ * @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 bad words.
+ * @covers ::report_customsql_contains_bad_word
+ * */
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 daily reports.
+ * @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 +290,10 @@ 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 integer detection.
+ * @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 +301,10 @@ public function test_report_customsql_is_integer(): void {
$this->assertFalse(report_customsql_is_integer('2013-10-07'));
}
- /** @covers ::report_customsql_get_table_headers */
+ /**
+ * Test table headers.
+ * @covers ::report_customsql_get_table_headers
+ */
public function test_report_customsql_get_table_headers(): void {
$rawheaders = [
'String date',
@@ -294,7 +331,10 @@ 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 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 +345,10 @@ public function test_report_customsql_pretify_column_names(): void {
report_customsql_pretify_column_names($row, $query));
}
- /** @covers ::report_customsql_pretify_column_names */
+ /**
+ * Test column multi-line names.
+ * @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 +363,10 @@ 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 pretty column names.
+ * @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 +377,10 @@ public function test_report_customsql_pretify_column_names_same_name_diff_capiti
}
- /** @covers ::report_customsql_pretify_column_names */
+ /**
+ * Test pretty column names.
+ * @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 +411,10 @@ public function test_report_customsql_pretify_column_names_issue(): void {
}
- /** @covers ::report_customsql_display_row */
+ /**
+ * Test row display.
+ * @covers ::report_customsql_display_row
+ */
public function test_report_customsql_display_row(): void {
$rawdata = [
'Not a date',
@@ -495,7 +547,10 @@ public function test_report_custom_sql_download_report_url(): void {
$this->assertEquals($expected, $url->out(false));
}
- /** @covers ::report_customsql_write_csv_row */
+ /**
+ * Test writing a CSV row.
+ * @covers ::report_customsql_write_csv_row
+ */
public function test_report_customsql_write_csv_row(): void {
global $CFG;
$this->resetAfterTest();
diff --git a/version.php b/version.php
index d2a47c9..23214fc 100644
--- a/version.php
+++ b/version.php
@@ -24,8 +24,8 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2023121300;
-$plugin->requires = 2022112800;
+$plugin->version = 2024010408;
+$plugin->requires = 2018051700;
$plugin->component = 'report_customsql';
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '4.3 for Moodle 4.1+';
diff --git a/view.php b/view.php
index 2c23af6..c85fb0d 100644
--- a/view.php
+++ b/view.php
@@ -207,7 +207,7 @@
if ($rowlimitexceeded) {
echo html_writer::tag('p', get_string('recordlimitreached', 'report_customsql',
- $report->querylimit ?? get_config('report_customsql', 'querylimitdefault')),
+ !empty($report->querylimit) ? $report->querylimit : get_config('report_customsql', 'querylimitdefault')),
['class' => 'admin_note']);
} else {
echo html_writer::tag('p', get_string('recordcount', 'report_customsql', $count),
diff --git a/view_form.php b/view_form.php
index d4d9c32..ef80341 100644
--- a/view_form.php
+++ b/view_form.php
@@ -34,6 +34,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_customsql_view_form extends moodleform {
+
+ /**
+ * Define the form.
+ */
public function definition() {
$mform = $this->_form;