Skip to content

Commit

Permalink
MDL-72756 reportbuilder: Implement privacy provider
Browse files Browse the repository at this point in the history
Co-authored-by: Paul Holden <paulh@moodle.com>
  • Loading branch information
dravek and paulholden committed Jan 27, 2022
1 parent e63604f commit ed55c7e
Show file tree
Hide file tree
Showing 2 changed files with 301 additions and 2 deletions.
22 changes: 21 additions & 1 deletion lang/en/reportbuilder.php
Expand Up @@ -164,7 +164,11 @@
$string['nosortablecolumns'] = 'There are no sortable columns';
$string['or'] = 'or';
$string['privacy:metadata:audience'] = 'Report audience definitions';
$string['privacy:metadata:audience:classname'] = 'Class used by the audience';
$string['privacy:metadata:audience:classname'] = 'The class used by the audience';
$string['privacy:metadata:audience:configdata'] = 'Configuration data used by the audience';
$string['privacy:metadata:audience:heading'] = 'The custom heading used by the audience';
$string['privacy:metadata:audience:timecreated'] = 'The time when the audience was created';
$string['privacy:metadata:audience:timemodified'] = 'The time when the audience was last modified';
$string['privacy:metadata:audience:usercreated'] = 'The ID of the user who created the audience';
$string['privacy:metadata:audience:usermodified'] = 'The ID of the user who last modified the audience';
$string['privacy:metadata:column'] = 'Report column definitions';
Expand All @@ -177,11 +181,27 @@
$string['privacy:metadata:filter:usermodified'] = 'The ID of the user who last modified the filter';
$string['privacy:metadata:preference:reportfilter'] = 'Stored report filter values';
$string['privacy:metadata:report'] = 'Report definitions';
$string['privacy:metadata:report:conditiondata'] = 'Configuration data for the report conditions';
$string['privacy:metadata:report:name'] = 'The name of the report';
$string['privacy:metadata:report:settingsdata'] = 'General configuration for the report';
$string['privacy:metadata:report:source'] = 'The source of the report';
$string['privacy:metadata:report:timecreated'] = 'The time when the report was created';
$string['privacy:metadata:report:timemodified'] = 'The time when the report was last modified';
$string['privacy:metadata:report:uniquerows'] = 'Whether duplicate rows are removed';
$string['privacy:metadata:report:usercreated'] = 'The ID of the user who created the report';
$string['privacy:metadata:report:usermodified'] = 'The ID of the user who last modified the report';
$string['privacy:metadata:schedule'] = 'Report schedule definitions';
$string['privacy:metadata:schedule:audiences'] = 'The audiences this schedule is for';
$string['privacy:metadata:schedule:enabled'] = 'The status of the schedule';
$string['privacy:metadata:schedule:format'] = 'The format of the scheduled report';
$string['privacy:metadata:schedule:message'] = 'The message of the schedule';
$string['privacy:metadata:schedule:name'] = 'The name of the schedule';
$string['privacy:metadata:schedule:recurrence'] = 'The recurrence of the schedule';
$string['privacy:metadata:schedule:reportempty'] = 'Action to take if scheduled report is empty';
$string['privacy:metadata:schedule:subject'] = 'The subject of the schedule';
$string['privacy:metadata:schedule:timecreated'] = 'The time when the schedule was created';
$string['privacy:metadata:schedule:timemodified'] = 'The time when the schedule was last modified';
$string['privacy:metadata:schedule:timescheduled'] = 'The time the schedule will begin';
$string['privacy:metadata:schedule:usercreated'] = 'The ID of the user who created the schedule';
$string['privacy:metadata:schedule:usermodified'] = 'The ID of the user who last modified the schedule';
$string['privacy:metadata:schedule:userviewas'] = 'The ID of the user who the schedule will be viewed as';
Expand Down
281 changes: 280 additions & 1 deletion reportbuilder/classes/privacy/provider.php
Expand Up @@ -18,9 +18,20 @@

namespace core_reportbuilder\privacy;

use context;
use stdClass;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\core_userlist_provider;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\user_preference_provider;
use core_privacy\local\request\writer;
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\user_filter_manager;
use core_reportbuilder\local\helpers\schedule as schedule_helper;
use core_reportbuilder\local\models\audience;
use core_reportbuilder\local\models\column;
use core_reportbuilder\local\models\filter;
Expand All @@ -36,7 +47,9 @@
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\user_preference_provider {
\core_privacy\local\request\plugin\provider,
core_userlist_provider,
user_preference_provider {

/**
* Returns metadata about the component
Expand All @@ -47,8 +60,14 @@ class provider implements
public static function get_metadata(collection $collection): collection {
$collection->add_database_table(report::TABLE, [
'name' => 'privacy:metadata:report:name',
'source' => 'privacy:metadata:report:source',
'conditiondata' => 'privacy:metadata:report:conditiondata',
'settingsdata' => 'privacy:metadata:report:settingsdata',
'uniquerows' => 'privacy:metadata:report:uniquerows',
'usercreated' => 'privacy:metadata:report:usercreated',
'usermodified' => 'privacy:metadata:report:usermodified',
'timecreated' => 'privacy:metadata:report:timecreated',
'timemodified' => 'privacy:metadata:report:timemodified',
], 'privacy:metadata:report');

$collection->add_database_table(column::TABLE, [
Expand All @@ -65,15 +84,29 @@ public static function get_metadata(collection $collection): collection {

$collection->add_database_table(audience::TABLE, [
'classname' => 'privacy:metadata:audience:classname',
'configdata' => 'privacy:metadata:audience:configdata',
'heading' => 'privacy:metadata:audience:heading',
'usercreated' => 'privacy:metadata:audience:usercreated',
'usermodified' => 'privacy:metadata:audience:usermodified',
'timecreated' => 'privacy:metadata:audience:timecreated',
'timemodified' => 'privacy:metadata:audience:timemodified',
], 'privacy:metadata:audience');

$collection->add_database_table(schedule::TABLE, [
'name' => 'privacy:metadata:schedule:name',
'enabled' => 'privacy:metadata:schedule:enabled',
'audiences' => 'privacy:metadata:schedule:audiences',
'format' => 'privacy:metadata:schedule:format',
'subject' => 'privacy:metadata:schedule:subject',
'message' => 'privacy:metadata:schedule:message',
'userviewas' => 'privacy:metadata:schedule:userviewas',
'timescheduled' => 'privacy:metadata:schedule:timescheduled',
'recurrence' => 'privacy:metadata:schedule:recurrence',
'reportempty' => 'privacy:metadata:schedule:reportempty',
'usercreated' => 'privacy:metadata:schedule:usercreated',
'usermodified' => 'privacy:metadata:schedule:usermodified',
'timecreated' => 'privacy:metadata:schedule:timecreated',
'timemodified' => 'privacy:metadata:schedule:timemodified',
], 'privacy:metadata:schedule');

$collection->add_user_preference('core_reportbuilder', 'privacy:metadata:preference:reportfilter');
Expand All @@ -98,4 +131,250 @@ public static function export_user_preferences(int $userid): void {
);
}
}

/**
* Get export sub context for a report
*
* @param report $report
* @return array
*/
public static function get_export_subcontext(report $report): array {
$reportnode = implode('-', [
$report->get('id'),
clean_filename($report->get_formatted_name()),
]);

return [get_string('reportbuilder', 'core_reportbuilder'), $reportnode];
}

/**
* Get the list of contexts that contain user information for the specified user
*
* @param int $userid
* @return contextlist
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$contextlist = new contextlist();

// Locate all contexts for reports the user has created, or reports they have created audience/schedules for.
$sql = '
SELECT r.contextid
FROM {' . report::TABLE . '} r
WHERE r.type = 0
AND (r.usercreated = ?
OR r.usermodified = ?
OR r.id IN (
SELECT a.reportid
FROM {' . audience::TABLE . '} a
WHERE a.usercreated = ? OR a.usermodified = ?
UNION
SELECT s.reportid
FROM {' . schedule::TABLE . '} s
WHERE s.usercreated = ? OR s.usermodified = ?
)
)';

return $contextlist->add_from_sql($sql, array_fill(0, 6, $userid));
}

/**
* Get users in context
*
* @param userlist $userlist
*/
public static function get_users_in_context(userlist $userlist): void {
$select = 'r.type = :type AND r.contextid = :contextid';
$params = ['type' => 0, 'contextid' => $userlist->get_context()->id];

// Users who have created reports.
$sql = 'SELECT r.usercreated, r.usermodified
FROM {' . report::TABLE . '} r
WHERE ' . $select;
$userlist->add_from_sql('usercreated', $sql, $params);
$userlist->add_from_sql('usermodified', $sql, $params);

// Users who have created audiences.
$sql = 'SELECT a.usercreated, a.usermodified
FROM {' . audience::TABLE . '} a
JOIN {' . report::TABLE . '} r ON r.id = a.reportid
WHERE ' . $select;
$userlist->add_from_sql('usercreated', $sql, $params);
$userlist->add_from_sql('usermodified', $sql, $params);

// Users who have created schedules.
$sql = 'SELECT s.usercreated, s.usermodified
FROM {' . schedule::TABLE . '} s
JOIN {' . report::TABLE . '} r ON r.id = s.reportid
WHERE ' . $select;
$userlist->add_from_sql('usercreated', $sql, $params);
$userlist->add_from_sql('usermodified', $sql, $params);
}

/**
* Export all user data for the specified user in the specified contexts
*
* @param approved_contextlist $contextlist
*/
public static function export_user_data(approved_contextlist $contextlist): void {
if (empty($contextlist->count())) {
return;
}

$user = $contextlist->get_user();

// We need to get all reports that the user has created, or reports they have created audience/schedules for.
$select = 'type = 0 AND (usercreated = ? OR usermodified = ? OR id IN (
SELECT a.reportid
FROM {' . audience::TABLE . '} a
WHERE a.usercreated = ? OR a.usermodified = ?
UNION
SELECT s.reportid
FROM {' . schedule::TABLE . '} s
WHERE s.usercreated = ? OR s.usermodified = ?
))';
$params = array_fill(0, 6, $user->id);

foreach (report::get_records_select($select, $params) as $report) {
$subcontext = static::get_export_subcontext($report);

self::export_report($subcontext, $report);

$select = 'reportid = ? AND (usercreated = ? OR usermodified = ?)';
$params = [$report->get('id'), $user->id, $user->id];

// Audiences.
if ($audiences = audience::get_records_select($select, $params)) {
static::export_audiences($report->get_context(), $subcontext, $audiences);
}

// Schedules.
if ($schedules = schedule::get_records_select($select, $params)) {
static::export_schedules($report->get_context(), $subcontext, $schedules);
}
}
}

/**
* Delete data for all users in context
*
* @param context $context
*/
public static function delete_data_for_all_users_in_context(context $context): void {
// We don't perform any deletion of user data.
}

/**
* Delete data for user
*
* @param approved_contextlist $contextlist
*/
public static function delete_data_for_user(approved_contextlist $contextlist): void {
// We don't perform any deletion of user data.
}

/**
* Delete data for users
*
* @param approved_userlist $userlist
*/
public static function delete_data_for_users(approved_userlist $userlist): void {
// We don't perform any deletion of user data.
}

/**
* Export given report in context
*
* @param array $subcontext
* @param report $report
*/
protected static function export_report(array $subcontext, report $report): void {
// Show the source name, if it exists.
$source = $report->get('source');
if (manager::report_source_exists($source)) {
$source = call_user_func([$source, 'get_name']);
}

$reportdata = (object) [
'name' => $report->get_formatted_name(),
'source' => $source,
'conditiondata' => $report->get('conditiondata'),
'settingsdata' => $report->get('settingsdata'),
'uniquerows' => transform::yesno($report->get('uniquerows')),
'usercreated' => transform::user($report->get('usercreated')),
'usermodified' => transform::user($report->get('usermodified')),
'timecreated' => transform::datetime($report->get('timecreated')),
'timemodified' => transform::datetime($report->get('timemodified')),
];

writer::with_context($report->get_context())->export_data($subcontext, $reportdata);
}

/**
* Export given audiences in context
*
* @param context $context
* @param array $subcontext
* @param audience[] $audiences
*/
protected static function export_audiences(context $context, array $subcontext, array $audiences): void {
$audiencedata = array_map(static function(audience $audience) use ($context): stdClass {
// Show the audience name, if it exists.
$classname = $audience->get('classname');
if (class_exists($classname)) {
$classname = $classname::instance()->get_name();
}

return (object) [
'classname' => $classname,
'heading' => $audience->get_formatted_heading($context),
'configdata' => $audience->get('configdata'),
'usercreated' => transform::user($audience->get('usercreated')),
'usermodified' => transform::user($audience->get('usermodified')),
'timecreated' => transform::datetime($audience->get('timecreated')),
'timemodified' => transform::datetime($audience->get('timemodified')),
];
}, $audiences);

writer::with_context($context)->export_related_data($subcontext, 'audiences', (object) ['data' => $audiencedata]);
}

/**
* Export given schedules in context
*
* @param context $context
* @param array $subcontext
* @param schedule[] $schedules
*/
protected static function export_schedules(context $context, array $subcontext, array $schedules): void {
$formatoptions = schedule_helper::get_format_options();
$recurrenceoptions = schedule_helper::get_recurrence_options();
$viewasoptions = schedule_helper::get_viewas_options();
$reportemptyoptions = schedule_helper::get_report_empty_options();

$scheduledata = array_map(static function(schedule $schedule) use (
$context, $formatoptions, $recurrenceoptions, $viewasoptions, $reportemptyoptions): stdClass {

// The "User view as" property will be either creator, recipient or a specific userid.
$userviewas = $schedule->get('userviewas');

return (object) [
'name' => $schedule->get_formatted_name($context),
'enabled' => transform::yesno($schedule->get('enabled')),
'format' => $formatoptions[$schedule->get('format')],
'timescheduled' => transform::datetime($schedule->get('timescheduled')),
'recurrence' => $recurrenceoptions[$schedule->get('recurrence')],
'userviewas' => $viewasoptions[$userviewas] ?? transform::user($userviewas),
'audiences' => $schedule->get('audiences'),
'subject' => $schedule->get('subject'),
'message' => format_text($schedule->get('message'), $schedule->get('messageformat'), ['context' => $context]),
'reportempty' => $reportemptyoptions[$schedule->get('reportempty')],
'usercreated' => transform::user($schedule->get('usercreated')),
'usermodified' => transform::user($schedule->get('usermodified')),
'timecreated' => transform::datetime($schedule->get('timecreated')),
'timemodified' => transform::datetime($schedule->get('timemodified')),
];
}, $schedules);

writer::with_context($context)->export_related_data($subcontext, 'schedules', (object) ['data' => $scheduledata]);
}
}

0 comments on commit ed55c7e

Please sign in to comment.