Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: add limit query access to user list option #152

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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