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.