Skip to content

Commit

Permalink
Feature: add limit query access to user list option
Browse files Browse the repository at this point in the history
  • Loading branch information
rhell4 committed Jul 10, 2024
1 parent ce26f60 commit 0bdab07
Show file tree
Hide file tree
Showing 15 changed files with 121 additions and 35 deletions.
1 change: 1 addition & 0 deletions category.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 16 additions & 3 deletions classes/local/query.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
3 changes: 0 additions & 3 deletions classes/output/category.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)];
}
Expand Down
1 change: 1 addition & 0 deletions classes/output/index_page.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
16 changes: 16 additions & 0 deletions classes/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

namespace report_customsql;

use report_customsql\local\query as report_query;

/**
* Static utility methods to support the report_customsql module.
*
Expand Down Expand Up @@ -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);
}

}
1 change: 1 addition & 0 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<FIELD NAME="queryparams" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The SQL parameters to generate this report."/>
<FIELD NAME="querylimit" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="5000" SEQUENCE="false" COMMENT="Limit the number of results returned."/>
<FIELD NAME="capability" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The capability that a user needs to have to run this report."/>
<FIELD NAME="useraccess" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="A comma-separated list of user ids that can run this report"/>
<FIELD NAME="lastrun" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Timestamp of when this report was last run."/>
<FIELD NAME="lastexecutiontime" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time this report took to run last time it was executed, in milliseconds."/>
<FIELD NAME="runable" TYPE="char" LENGTH="32" NOTNULL="true" DEFAULT="manual" SEQUENCE="false" COMMENT="'manual', 'weekly' or 'monthly'"/>
Expand Down
14 changes: 14 additions & 0 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 6 additions & 0 deletions edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down
64 changes: 38 additions & 26 deletions edit_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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'),
Expand All @@ -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);

Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions lang/en/report_customsql.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -183,6 +184,8 @@
$string['timemodified'] = '<span class="font-weight-bold">Last modified:</span> {$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'] = '<span class="font-weight-bold">Modified by:</span> {$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}\'.';
Expand Down
9 changes: 8 additions & 1 deletion lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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 = [];
Expand Down
9 changes: 8 additions & 1 deletion locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)]).
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions view.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 0bdab07

Please sign in to comment.