diff --git a/category.php b/category.php
index b8adef5..e00f01f 100644
--- a/category.php
+++ b/category.php
@@ -37,6 +37,7 @@
$record = $DB->get_record('report_customsql_categories', ['id' => $categoryid], '*', MUST_EXIST);
$queries = $DB->get_records('report_customsql_queries', ['categoryid' => $categoryid], 'displayname, id');
+$queries = \report_customsql\utils::filter_queries_by_visibility($queries, $context);
$category = new \report_customsql\local\category($record);
$category->load_queries_data($queries);
$widget = new \report_customsql\output\category($category, $context);
diff --git a/classes/local/query.php b/classes/local/query.php
index 66c5f35..bdb8fb6 100644
--- a/classes/local/query.php
+++ b/classes/local/query.php
@@ -128,9 +128,22 @@ public function can_edit(\context $context): bool {
* Check the capability to view the query.
*
* @param \context $context The context to check.
- * @return bool Has capability to view or not?
+ * @return bool Has capability and access to view or not?
*/
- public function can_view(\context $context):bool {
- return empty($report->capability) || has_capability($report->capability, $context);
+ public function can_view(\context $context): bool {
+ global $USER;
+
+ $report = $this->record;
+
+ $hascapability = empty($report->capability) || has_capability($report->capability, $context);
+
+ if (!empty($report->useraccess) && !has_capability('moodle/site:config', $context)) {
+ $userids = explode(',', $report->useraccess);
+ $hasaccess = in_array($USER->id, $userids);
+ } else {
+ $hasaccess = true;
+ }
+
+ return $hascapability && $hasaccess;
}
}
diff --git a/classes/output/category.php b/classes/output/category.php
index 67090c8..14c85d3 100644
--- a/classes/output/category.php
+++ b/classes/output/category.php
@@ -88,9 +88,6 @@ public function export_for_template(renderer_base $output) {
$queries = [];
foreach ($querygroup['queries'] as $querydata) {
$query = new report_query($querydata);
- if (!$query->can_view($this->context)) {
- continue;
- }
$querywidget = new category_query($query, $this->category, $this->context, $this->returnurl);
$queries[] = ['categoryqueryitem' => $output->render($querywidget)];
}
diff --git a/classes/output/index_page.php b/classes/output/index_page.php
index b84fa80..5fca15c 100644
--- a/classes/output/index_page.php
+++ b/classes/output/index_page.php
@@ -71,6 +71,7 @@ public function __construct(array $categories, array $queries, context $context,
public function export_for_template(renderer_base $output) {
$categoriesdata = [];
+ $this->queries = utils::filter_queries_by_visibility($this->queries, $this->context);
$grouppedqueries = utils::group_queries_by_category($this->queries);
foreach ($this->categories as $record) {
$category = new report_category($record);
diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php
index 4af2b26..f5677c4 100644
--- a/classes/privacy/provider.php
+++ b/classes/privacy/provider.php
@@ -140,6 +140,7 @@ public static function export_user_data(request\approved_contextlist $contextlis
$data['queryparams'] = $record->queryparams;
$data['querylimit'] = $record->querylimit;
$data['capability'] = $record->capability;
+ $data['useraccess'] = $record->useraccess;
$data['lastrun'] = userdate($record->lastrun);
$data['lastexecutiontime'] = $record->lastexecutiontime;
$data['runable'] = $record->runable;
diff --git a/classes/utils.php b/classes/utils.php
index dcb27b4..25ba2cb 100644
--- a/classes/utils.php
+++ b/classes/utils.php
@@ -16,6 +16,8 @@
namespace report_customsql;
+use report_customsql\local\query as report_query;
+
/**
* Static utility methods to support the report_customsql module.
*
@@ -75,4 +77,18 @@ public static function get_number_of_report_by_type(array $queries, string $type
}, ARRAY_FILTER_USE_BOTH);
}
+ /**
+ * Filters out queries that are not visible.
+ *
+ * @param array $queries Array of queries.
+ * @param \context $context The context to check.
+ * @return array All queries the user can view.
+ */
+ public static function filter_queries_by_visibility(array $queries, \context $context) {
+ return array_filter($queries, function($querydata) use ($context) {
+ $query = new report_query($querydata);
+ return $query->can_view($context);
+ }, ARRAY_FILTER_USE_BOTH);
+ }
+
}
diff --git a/db/install.xml b/db/install.xml
index de5c328..71a42c4 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -14,6 +14,7 @@
+
diff --git a/db/upgrade.php b/db/upgrade.php
index db09dd2..97a9bb5 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -269,5 +269,19 @@ function xmldb_report_customsql_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2021111600, 'report', 'customsql');
}
+ if ($oldversion < 2024061900) {
+ // Define field useraccess to be added to report_customsql_queries.
+ $table = new xmldb_table('report_customsql_queries');
+ $field = new xmldb_field('useraccess', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'capability');
+
+ // Conditionally launch add field usermodified.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Customsql savepoint reached.
+ upgrade_plugin_savepoint(true, 2024061900, 'report', 'customsql');
+ }
+
return true;
}
diff --git a/edit.php b/edit.php
index e1d8917..c410327 100644
--- a/edit.php
+++ b/edit.php
@@ -95,8 +95,14 @@
$newreport->description = $newreport->description['text'];
// Currently, autocomplete can return an empty value in the array. If we get one, strip it out.
+ $newreport->useraccess = trim(implode(',', $newreport->useraccess), ',');
$newreport->emailto = trim(implode(',', $newreport->emailto), ',');
+ // Set the useraccess field to empty if the report capability is moodle/site:config.
+ if ($newreport->capability === 'moodle/site:config') {
+ $newreport->useraccess = '';
+ }
+
// Set the following fields to empty strings if the report is running manually.
if ($newreport->runable === 'manual') {
$newreport->at = '';
diff --git a/edit_form.php b/edit_form.php
index a0f9f5c..2d4fe0f 100644
--- a/edit_form.php
+++ b/edit_form.php
@@ -92,31 +92,7 @@ public function definition() {
end($capabilityoptions);
$mform->setDefault('capability', key($capabilityoptions));
- $mform->addElement('text', 'querylimit', get_string('querylimit', 'report_customsql'));
- $mform->setType('querylimit', PARAM_INT);
- $mform->setDefault('querylimit', get_config('report_customsql', 'querylimitdefault'));
- $mform->addRule('querylimit', get_string('requireint', 'report_customsql'),
- 'numeric', null, 'client');
-
- $runat = [];
- if ($hasparameters) {
- $runat[] = $mform->createElement('select', 'runable', null, report_customsql_runable_options('manual'));
- } else {
- $runat[] = $mform->createElement('select', 'runable', null, report_customsql_runable_options());
- }
- $runat[] = $mform->createElement('select', 'at', null, report_customsql_daily_at_options());
- $mform->addGroup($runat, 'runablegroup', get_string('runable', 'report_customsql'),
- get_string('at', 'report_customsql'), false);
-
- $mform->addElement('checkbox', 'singlerow', get_string('typeofresult', 'report_customsql'),
- get_string('onerow', 'report_customsql'));
-
- $mform->addElement('text', 'customdir', get_string('customdir', 'report_customsql'), 'size = 70');
- $mform->setType('customdir', PARAM_PATH);
- $mform->disabledIf('customdir', 'runable', 'eq', 'manual');
- $mform->addHelpButton('customdir', 'customdir', 'report_customsql');
-
- $options = [
+ $useroptions = [
'ajax' => 'report_customsql/userselector', // Bit of a hack, but the service seems to do what we want.
'multiple' => true,
'valuehtmlcallback' => function($userid) {
@@ -141,7 +117,36 @@ public function definition() {
);
},
];
- $mform->addElement('autocomplete', 'emailto', get_string('emailto', 'report_customsql'), [], $options);
+ $mform->addElement('autocomplete', 'useraccess', get_string('useraccess', 'report_customsql'), [], $useroptions);
+ $mform->setType('useraccess', PARAM_RAW);
+ $mform->addHelpButton('useraccess', 'useraccess', 'report_customsql');
+ $mform->hideIf('useraccess', 'capability', 'eq', 'moodle/site:config');
+
+ $mform->addElement('text', 'querylimit', get_string('querylimit', 'report_customsql'));
+ $mform->setType('querylimit', PARAM_INT);
+ $mform->setDefault('querylimit', get_config('report_customsql', 'querylimitdefault'));
+ $mform->addRule('querylimit', get_string('requireint', 'report_customsql'),
+ 'numeric', null, 'client');
+
+ $runat = [];
+ if ($hasparameters) {
+ $runat[] = $mform->createElement('select', 'runable', null, report_customsql_runable_options('manual'));
+ } else {
+ $runat[] = $mform->createElement('select', 'runable', null, report_customsql_runable_options());
+ }
+ $runat[] = $mform->createElement('select', 'at', null, report_customsql_daily_at_options());
+ $mform->addGroup($runat, 'runablegroup', get_string('runable', 'report_customsql'),
+ get_string('at', 'report_customsql'), false);
+
+ $mform->addElement('checkbox', 'singlerow', get_string('typeofresult', 'report_customsql'),
+ get_string('onerow', 'report_customsql'));
+
+ $mform->addElement('text', 'customdir', get_string('customdir', 'report_customsql'), 'size = 70');
+ $mform->setType('customdir', PARAM_PATH);
+ $mform->disabledIf('customdir', 'runable', 'eq', 'manual');
+ $mform->addHelpButton('customdir', 'customdir', 'report_customsql');
+
+ $mform->addElement('autocomplete', 'emailto', get_string('emailto', 'report_customsql'), [], $useroptions);
$mform->setType('emailto', PARAM_RAW);
$mform->addElement('select', 'emailwhat', get_string('emailwhat', 'report_customsql'),
@@ -158,6 +163,7 @@ public function definition() {
public function set_data($currentvalues) {
global $DB, $OUTPUT;
+ $currentvalues->useraccess = explode(',', $currentvalues->useraccess ?? '');
$currentvalues->emailto = explode(',', $currentvalues->emailto ?? '');
parent::set_data($currentvalues);
@@ -229,6 +235,12 @@ public function validation($data, $files) {
try {
$rs = report_customsql_execute_query($sql, $paramvalues, 2);
+ // Check the list of users to limit access.
+ if ($data['capability'] !== 'moodle/site:config') {
+ if ($invaliduser = report_customsql_validate_users($data['useraccess'], $data['capability'])) {
+ $errors['useraccess'] = $invaliduser;
+ }
+ }
if (!empty($data['singlerow'])) {
// Count rows for Moodle 2 as all Moodle 1.9 useful and more performant
// recordset methods removed.
diff --git a/lang/en/report_customsql.php b/lang/en/report_customsql.php
index 26ad086..f4b877e 100644
--- a/lang/en/report_customsql.php
+++ b/lang/en/report_customsql.php
@@ -90,6 +90,7 @@
$string['errordeletingreport'] = 'Error deleting a query.';
$string['errorinsertingreport'] = 'Error inserting a query.';
$string['errorupdatingreport'] = 'Error updating a query.';
+$string['invalidaccess'] = 'Sorry, but you do not currently have access to this report.';
$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';
@@ -183,6 +184,8 @@
$string['timemodified'] = 'Last modified: {$a}';
$string['typeofresult'] = 'Type of result';
$string['unknowndownloadfile'] = 'Unknown download file.';
+$string['useraccess'] = 'Limit query to';
+$string['useraccess_help'] = 'Limits access to this query to the selected users and administrators (moodle/site:config)';
$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}\'.';
diff --git a/lib.php b/lib.php
index 6c3727c..20dc68e 100644
--- a/lib.php
+++ b/lib.php
@@ -44,7 +44,7 @@
* @return bool false if file not found, does not return if found - just send the file
*/
function report_customsql_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = []) {
- global $DB;
+ global $DB, $USER;
require_once(dirname(__FILE__) . '/locallib.php');
@@ -71,6 +71,13 @@ function report_customsql_pluginfile($course, $cm, $context, $filearea, $args, $
require_capability($report->capability, $context);
}
+ if (!empty($report->useraccess) && !has_capability('moodle/site:config', $context)) {
+ $userids = explode(',', $report->useraccess);
+ if (!in_array($USER->id, $userids)) {
+ throw new moodle_exception('invalidaccess', 'report_customsql');
+ }
+ }
+
$queryparams = report_customsql_get_query_placeholders_and_field_names($report->querysql);
// Get any query param values that are given in the URL.
$paramvalues = [];
diff --git a/locallib.php b/locallib.php
index 7c9a6e6..d1d5064 100644
--- a/locallib.php
+++ b/locallib.php
@@ -384,7 +384,7 @@ function report_customsql_get_reports_for($categoryid, $type) {
* @param string $type, type of report (manual, daily, weekly or monthly)
*/
function report_customsql_print_reports_for($reports, $type) {
- global $OUTPUT;
+ global $OUTPUT, $USER;
if (empty($reports)) {
return;
@@ -403,6 +403,13 @@ function report_customsql_print_reports_for($reports, $type) {
continue;
}
+ if (!empty($report->useraccess) && !has_capability('moodle/site:config', $context)) {
+ $userids = explode(',', $report->useraccess);
+ if (!in_array($USER->id, $userids)) {
+ continue;
+ }
+ }
+
echo html_writer::start_tag('p');
echo html_writer::tag('a', format_string($report->displayname),
['href' => report_customsql_url('view.php?id=' . $report->id)]).
diff --git a/version.php b/version.php
index d2a47c9..e1506f6 100644
--- a/version.php
+++ b/version.php
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2023121300;
+$plugin->version = 2024061900;
$plugin->requires = 2022112800;
$plugin->component = 'report_customsql';
$plugin->maturity = MATURITY_STABLE;
diff --git a/view.php b/view.php
index 2c23af6..af2269d 100644
--- a/view.php
+++ b/view.php
@@ -59,6 +59,13 @@
require_capability($report->capability, $context);
}
+if (!empty($report->useraccess) && !has_capability('moodle/site:config', $context)) {
+ $userids = explode(',', $report->useraccess);
+ if (!in_array($USER->id, $userids)) {
+ throw new moodle_exception('invalidaccess', 'report_customsql');
+ }
+}
+
report_customsql_log_view($id);
// We don't want slow reports blocking the session in other tabs.