mirror of
https://github.com/moodle/moodle.git
synced 2025-08-10 11:26:41 +02:00
Merge branch 'wip-MDL-62147-34' of git://github.com/marinaglancy/moodle into MOODLE_34_STABLE
This commit is contained in:
commit
4c2a5ce403
18 changed files with 632 additions and 32 deletions
|
@ -51,7 +51,7 @@ class provider implements
|
|||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_database_table('log', [
|
||||
$collection->add_database_table('logstore_standard_log', [
|
||||
'eventname' => 'privacy:metadata:log:eventname',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'relateduserid' => 'privacy:metadata:log:relateduserid',
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
|
||||
namespace block_recent_activity\privacy;
|
||||
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\contextlist;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
|
@ -33,15 +37,61 @@ defined('MOODLE_INTERNAL') || die();
|
|||
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements \core_privacy\local\metadata\null_provider {
|
||||
class provider implements \core_privacy\local\metadata\provider,
|
||||
\core_privacy\local\request\plugin\provider {
|
||||
|
||||
/**
|
||||
* Get the language string identifier with the component's language
|
||||
* file to explain why this plugin stores no data.
|
||||
* Returns metadata.
|
||||
*
|
||||
* @return string
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
|
||||
// This plugin defines a db table but it is not considered personal data and, therefore, not exported or deleted.
|
||||
$collection->add_database_table('block_recent_activity', [
|
||||
'courseid' => 'privacy:metadata:block_recent_activity:courseid',
|
||||
'cmid' => 'privacy:metadata:block_recent_activity:cmid',
|
||||
'timecreated' => 'privacy:metadata:block_recent_activity:timecreated',
|
||||
'userid' => 'privacy:metadata:block_recent_activity:userid',
|
||||
'action' => 'privacy:metadata:block_recent_activity:action',
|
||||
'modname' => 'privacy:metadata:block_recent_activity:modname'
|
||||
], 'privacy:metadata:block_recent_activity');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
return new contextlist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param \context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,13 @@
|
|||
|
||||
$string['pluginname'] = 'Recent activity';
|
||||
$string['privacy:metadata'] = 'The recent activity block contains a cache of data stored elsewhere in Moodle.';
|
||||
$string['privacy:metadata:block_recent_activity'] = 'Temporary log of recent teacher activity. Removed after two days';
|
||||
$string['privacy:metadata:block_recent_activity:action'] = 'Action: created, updated or deleted';
|
||||
$string['privacy:metadata:block_recent_activity:cmid'] = 'Course module id';
|
||||
$string['privacy:metadata:block_recent_activity:courseid'] = 'Course id';
|
||||
$string['privacy:metadata:block_recent_activity:modname'] = 'Module type name (for delete action)';
|
||||
$string['privacy:metadata:block_recent_activity:timecreated'] = 'Time when action was performed';
|
||||
$string['privacy:metadata:block_recent_activity:userid'] = 'User performing the action';
|
||||
$string['recent_activity:addinstance'] = 'Add a new recent activity block';
|
||||
$string['recent_activity:viewaddupdatemodule'] = 'View added and updated modules in recent activity block';
|
||||
$string['recent_activity:viewdeletemodule'] = 'View deleted modules in recent activity block';
|
||||
|
|
|
@ -60,6 +60,7 @@ class provider implements
|
|||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
|
||||
// Tables without 'real' user information.
|
||||
$collection->add_database_table('grade_outcomes', [
|
||||
'timemodified' => 'privacy:metadata:outcomes:timemodified',
|
||||
'usermodified' => 'privacy:metadata:outcomes:usermodified',
|
||||
|
@ -80,6 +81,18 @@ class provider implements
|
|||
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
||||
], 'privacy:metadata:itemshistory');
|
||||
|
||||
$collection->add_database_table('scale', [
|
||||
'userid' => 'privacy:metadata:scale:userid',
|
||||
'timemodified' => 'privacy:metadata:scale:timemodified',
|
||||
], 'privacy:metadata:scale');
|
||||
|
||||
$collection->add_database_table('scale_history', [
|
||||
'userid' => 'privacy:metadata:scale:userid',
|
||||
'timemodified' => 'privacy:metadata:history:timemodified',
|
||||
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
||||
], 'privacy:metadata:scalehistory');
|
||||
|
||||
// Table with user information.
|
||||
$gradescommonfields = [
|
||||
'userid' => 'privacy:metadata:grades:userid',
|
||||
'usermodified' => 'privacy:metadata:grades:usermodified',
|
||||
|
@ -97,9 +110,24 @@ class provider implements
|
|||
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
||||
]), 'privacy:metadata:gradeshistory');
|
||||
|
||||
// The table grade_import_values is not reported because its data is temporary and only
|
||||
// The following tables are reported but not exported/deleted because their data is temporary and only
|
||||
// used during an import. It's content is deleted after a successful, or failed, import.
|
||||
|
||||
$collection->add_database_table('grade_import_newitem', [
|
||||
'itemname' => 'privacy:metadata:grade_import_newitem:itemname',
|
||||
'importcode' => 'privacy:metadata:grade_import_newitem:importcode',
|
||||
'importer' => 'privacy:metadata:grade_import_newitem:importer'
|
||||
], 'privacy:metadata:grade_import_newitem');
|
||||
|
||||
$collection->add_database_table('grade_import_values', [
|
||||
'userid' => 'privacy:metadata:grade_import_values:userid',
|
||||
'finalgrade' => 'privacy:metadata:grade_import_values:finalgrade',
|
||||
'feedback' => 'privacy:metadata:grade_import_values:feedback',
|
||||
'importcode' => 'privacy:metadata:grade_import_values:importcode',
|
||||
'importer' => 'privacy:metadata:grade_import_values:importer',
|
||||
'importonlyfeedback' => 'privacy:metadata:grade_import_values:importonlyfeedback'
|
||||
], 'privacy:metadata:grade_import_values');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
|
@ -118,18 +146,29 @@ class provider implements
|
|||
FROM {grade_outcomes} go
|
||||
JOIN {context} ctx
|
||||
ON (go.courseid > 0 AND ctx.instanceid = go.courseid AND ctx.contextlevel = :courselevel)
|
||||
OR (ctx.id = :syscontextid)
|
||||
OR ((go.courseid IS NULL OR go.courseid < 1) AND ctx.id = :syscontextid)
|
||||
WHERE go.usermodified = :userid";
|
||||
$params = ['userid' => $userid, 'courselevel' => CONTEXT_COURSE, 'syscontextid' => SYSCONTEXTID];
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// Add where appear in the history of outcomes, categories or items.
|
||||
// Add where we modified scales.
|
||||
$sql = "
|
||||
SELECT DISTINCT ctx.id
|
||||
FROM {scale} s
|
||||
JOIN {context} ctx
|
||||
ON (s.courseid > 0 AND ctx.instanceid = s.courseid AND ctx.contextlevel = :courselevel)
|
||||
OR (s.courseid = 0 AND ctx.id = :syscontextid)
|
||||
WHERE s.userid = :userid";
|
||||
$params = ['userid' => $userid, 'courselevel' => CONTEXT_COURSE, 'syscontextid' => SYSCONTEXTID];
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// Add where appear in the history of outcomes, categories, scales or items.
|
||||
$sql = "
|
||||
SELECT DISTINCT ctx.id
|
||||
FROM {context} ctx
|
||||
LEFT JOIN {grade_outcomes_history} goh ON goh.loggeduser = :userid1 AND (
|
||||
(goh.courseid > 0 AND goh.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel1)
|
||||
OR ((goh.courseid IS NULL OR goh.courseid < 1) AND ctx.id = :syscontextid)
|
||||
OR ((goh.courseid IS NULL OR goh.courseid < 1) AND ctx.id = :syscontextid1)
|
||||
)
|
||||
LEFT JOIN {grade_categories_history} gch ON gch.loggeduser = :userid2 AND (
|
||||
gch.courseid = ctx.instanceid
|
||||
|
@ -139,17 +178,28 @@ class provider implements
|
|||
gih.courseid = ctx.instanceid
|
||||
AND ctx.contextlevel = :courselevel3
|
||||
)
|
||||
LEFT JOIN {scale_history} sh
|
||||
ON (sh.userid = :userid4 OR sh.loggeduser = :userid5)
|
||||
AND (
|
||||
(sh.courseid > 0 AND sh.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel4)
|
||||
OR (sh.courseid = 0 AND ctx.id = :syscontextid2)
|
||||
)
|
||||
WHERE goh.id IS NOT NULL
|
||||
OR gch.id IS NOT NULL
|
||||
OR gih.id IS NOT NULL";
|
||||
OR gih.id IS NOT NULL
|
||||
OR sh.id IS NOT NULL";
|
||||
$params = [
|
||||
'syscontextid' => SYSCONTEXTID,
|
||||
'syscontextid1' => SYSCONTEXTID,
|
||||
'syscontextid2' => SYSCONTEXTID,
|
||||
'courselevel1' => CONTEXT_COURSE,
|
||||
'courselevel2' => CONTEXT_COURSE,
|
||||
'courselevel3' => CONTEXT_COURSE,
|
||||
'courselevel4' => CONTEXT_COURSE,
|
||||
'userid1' => $userid,
|
||||
'userid2' => $userid,
|
||||
'userid3' => $userid,
|
||||
'userid4' => $userid,
|
||||
'userid5' => $userid,
|
||||
];
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
|
@ -240,6 +290,9 @@ class provider implements
|
|||
// Export the outcomes.
|
||||
static::export_user_data_outcomes_in_contexts($contextlist);
|
||||
|
||||
// Export the scales.
|
||||
static::export_user_data_scales_in_contexts($contextlist);
|
||||
|
||||
// Export the historical grades which have become orphans (their grade items were deleted).
|
||||
// We place those in ther user context of the graded user.
|
||||
$userids = array_values(array_map(function($context) {
|
||||
|
@ -659,6 +712,100 @@ class provider implements
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the user data related to scales.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
* @return void
|
||||
*/
|
||||
protected static function export_user_data_scales_in_contexts(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
$rootpath = [get_string('grades', 'core_grades')];
|
||||
$relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);
|
||||
$userid = $contextlist->get_user()->id;
|
||||
|
||||
// Reorganise the contexts.
|
||||
$reduced = array_reduce($contextlist->get_contexts(), function($carry, $context) {
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
$carry['in_system'] = true;
|
||||
} else if ($context->contextlevel == CONTEXT_COURSE) {
|
||||
$carry['courseids'][] = $context->instanceid;
|
||||
}
|
||||
return $carry;
|
||||
}, [
|
||||
'in_system' => false,
|
||||
'courseids' => []
|
||||
]);
|
||||
|
||||
// Construct SQL.
|
||||
$sqltemplateparts = [];
|
||||
$templateparams = [];
|
||||
if ($reduced['in_system']) {
|
||||
$sqltemplateparts[] = '{prefix}.courseid = 0';
|
||||
}
|
||||
if (!empty($reduced['courseids'])) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($reduced['courseids'], SQL_PARAMS_NAMED);
|
||||
$sqltemplateparts[] = "{prefix}.courseid $insql";
|
||||
$templateparams = array_merge($templateparams, $inparams);
|
||||
}
|
||||
if (empty($sqltemplateparts)) {
|
||||
return;
|
||||
}
|
||||
$sqltemplate = '(' . implode(' OR ', $sqltemplateparts) . ')';
|
||||
|
||||
// Export edited scales.
|
||||
$sqlwhere = str_replace('{prefix}', 's', $sqltemplate);
|
||||
$sql = "
|
||||
SELECT s.id, s.courseid, s.name, s.timemodified
|
||||
FROM {scale} s
|
||||
WHERE $sqlwhere
|
||||
AND s.userid = :userid
|
||||
ORDER BY s.courseid, s.timemodified, s.id";
|
||||
$params = array_merge($templateparams, ['userid' => $userid]);
|
||||
$recordset = $DB->get_recordset_sql($sql, $params);
|
||||
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
|
||||
$carry[] = [
|
||||
'name' => $record->name,
|
||||
'timemodified' => transform::datetime($record->timemodified),
|
||||
'created_or_modified_by_you' => transform::yesno(true)
|
||||
];
|
||||
return $carry;
|
||||
|
||||
}, function($courseid, $data) use ($relatedtomepath) {
|
||||
$context = $courseid ? context_course::instance($courseid) : context_system::instance();
|
||||
writer::with_context($context)->export_related_data($relatedtomepath, 'scales',
|
||||
(object) ['scales' => $data]);
|
||||
});
|
||||
|
||||
// Export edits of scales history.
|
||||
$sqlwhere = str_replace('{prefix}', 'sh', $sqltemplate);
|
||||
$sql = "
|
||||
SELECT sh.id, sh.courseid, sh.name, sh.userid, sh.timemodified, sh.action, sh.loggeduser
|
||||
FROM {scale_history} sh
|
||||
WHERE $sqlwhere
|
||||
AND sh.loggeduser = :userid1
|
||||
OR sh.userid = :userid2
|
||||
ORDER BY sh.courseid, sh.timemodified, sh.id";
|
||||
$params = array_merge($templateparams, ['userid1' => $userid, 'userid2' => $userid]);
|
||||
$recordset = $DB->get_recordset_sql($sql, $params);
|
||||
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) use ($userid) {
|
||||
$carry[] = [
|
||||
'name' => $record->name,
|
||||
'timemodified' => transform::datetime($record->timemodified),
|
||||
'author_of_change_was_you' => transform::yesno($record->userid == $userid),
|
||||
'author_of_action_was_you' => transform::yesno($record->loggeduser == $userid),
|
||||
'action' => static::transform_history_action($record->action)
|
||||
];
|
||||
return $carry;
|
||||
|
||||
}, function($courseid, $data) use ($relatedtomepath) {
|
||||
$context = $courseid ? context_course::instance($courseid) : context_system::instance();
|
||||
writer::with_context($context)->export_related_data($relatedtomepath, 'scales_history',
|
||||
(object) ['modified_records' => $data]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract grade_grade from a record.
|
||||
*
|
||||
|
|
|
@ -64,6 +64,11 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
$u4 = $dg->create_user();
|
||||
$u5 = $dg->create_user();
|
||||
$u6 = $dg->create_user();
|
||||
$u7 = $dg->create_user();
|
||||
$u8 = $dg->create_user();
|
||||
$u9 = $dg->create_user();
|
||||
$u10 = $dg->create_user();
|
||||
$u11 = $dg->create_user();
|
||||
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
@ -80,16 +85,22 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
'fullname' => 'go2']), false);
|
||||
|
||||
// Nothing as of now.
|
||||
foreach ([$u1, $u2, $u3, $u4] as $u) {
|
||||
foreach ([$u1, $u2, $u3, $u4, $u5, $u6, $u7, $u8, $u9, $u10, $u11] as $u) {
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u->id)->get_contextids());
|
||||
$this->assertEmpty($contexts);
|
||||
}
|
||||
|
||||
$go0 = new grade_outcome(['shortname' => 'go0', 'fullname' => 'go0', 'usermodified' => $u1->id]);
|
||||
$go0->insert();
|
||||
$go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u1->id]);
|
||||
$go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u11->id]);
|
||||
$go1->insert();
|
||||
|
||||
// Create scales.
|
||||
$s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']);
|
||||
$s1->insert();
|
||||
$s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']);
|
||||
$s2->insert();
|
||||
|
||||
// User 2 creates history.
|
||||
$this->setUser($u2);
|
||||
$go0->shortname .= ' edited';
|
||||
|
@ -118,11 +129,18 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
$this->setUser($u6);
|
||||
$gi2a->delete();
|
||||
|
||||
// User 9 creates history.
|
||||
$this->setUser($u9);
|
||||
$s1->name .= ' edited';
|
||||
$s1->update();
|
||||
|
||||
// Assert contexts.
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u1->id)->get_contextids());
|
||||
$this->assertCount(2, $contexts);
|
||||
$this->assertArrayHasKey($c1ctx->id, $contexts);
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertArrayHasKey($sysctx->id, $contexts);
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u11->id)->get_contextids());
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertArrayHasKey($c1ctx->id, $contexts);
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u2->id)->get_contextids());
|
||||
$this->assertCount(2, $contexts);
|
||||
$this->assertArrayHasKey($sysctx->id, $contexts);
|
||||
|
@ -140,6 +158,23 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
$contexts = array_flip(provider::get_contexts_for_userid($u6->id)->get_contextids());
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertArrayHasKey($c2ctx->id, $contexts);
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u7->id)->get_contextids());
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertArrayHasKey($sysctx->id, $contexts);
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u8->id)->get_contextids());
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertArrayHasKey($c1ctx->id, $contexts);
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u9->id)->get_contextids());
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertArrayHasKey($sysctx->id, $contexts);
|
||||
|
||||
// User 10 creates history.
|
||||
$this->setUser($u10);
|
||||
$s2->delete();
|
||||
|
||||
$contexts = array_flip(provider::get_contexts_for_userid($u10->id)->get_contextids());
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertArrayHasKey($c1ctx->id, $contexts);
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid_grades_and_history() {
|
||||
|
@ -609,6 +644,10 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
$u4 = $dg->create_user();
|
||||
$u5 = $dg->create_user();
|
||||
$u6 = $dg->create_user();
|
||||
$u7 = $dg->create_user();
|
||||
$u8 = $dg->create_user();
|
||||
$u9 = $dg->create_user();
|
||||
$u10 = $dg->create_user();
|
||||
|
||||
$sysctx = context_system::instance();
|
||||
$u1ctx = context_user::instance($u1->id);
|
||||
|
@ -641,6 +680,14 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
$go1 = new grade_outcome(['shortname' => 'go1', 'fullname' => 'go1', 'courseid' => $c1->id, 'usermodified' => $u1->id]);
|
||||
$go1->insert();
|
||||
|
||||
// Create scales.
|
||||
$s1 = new grade_scale(['name' => 's1', 'scale' => 'a,b', 'userid' => $u7->id, 'courseid' => 0, 'description' => '']);
|
||||
$s1->insert();
|
||||
$s2 = new grade_scale(['name' => 's2', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c1->id, 'description' => '']);
|
||||
$s2->insert();
|
||||
$s3 = new grade_scale(['name' => 's3', 'scale' => 'a,b', 'userid' => $u8->id, 'courseid' => $c2->id, 'description' => '']);
|
||||
$s3->insert();
|
||||
|
||||
// User 2 creates history.
|
||||
$this->setUser($u2);
|
||||
$go0->shortname .= ' edited';
|
||||
|
@ -669,6 +716,15 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
$this->setUser($u6);
|
||||
$gi2a->delete();
|
||||
|
||||
// User 9 creates history.
|
||||
$this->setUser($u9);
|
||||
$s1->name .= ' edited';
|
||||
$s1->update();
|
||||
|
||||
// User 10 creates history.
|
||||
$this->setUser($u10);
|
||||
$s3->delete();
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Export data for u1.
|
||||
|
@ -755,6 +811,74 @@ class core_grades_privacy_testcase extends provider_testcase {
|
|||
$this->assertEquals(transform::yesno(true), $data->modified_records[0]['logged_in_user_was_you']);
|
||||
$this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'),
|
||||
$data->modified_records[0]['action']);
|
||||
|
||||
// Export data for u7.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u7, 'core_grades', $allcontexts));
|
||||
$data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertCount(1, $data->scales);
|
||||
$this->assertEquals($s1->name, $data->scales[0]['name']);
|
||||
$this->assertEquals(transform::yesno(true), $data->scales[0]['created_or_modified_by_you']);
|
||||
|
||||
// Export data for u8.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u8, 'core_grades', $allcontexts));
|
||||
$data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertCount(1, $data->scales);
|
||||
$this->assertEquals($s2->name, $data->scales[0]['name']);
|
||||
$this->assertEquals(transform::yesno(true), $data->scales[0]['created_or_modified_by_you']);
|
||||
$data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales_history');
|
||||
$this->assertCount(2, $data->modified_records);
|
||||
$this->assertEquals($s3->name, $data->modified_records[0]['name']);
|
||||
$this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_change_was_you']);
|
||||
$this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_action_was_you']);
|
||||
$this->assertEquals(get_string('privacy:request:historyactioninsert', 'core_grades'),
|
||||
$data->modified_records[0]['action']);
|
||||
$this->assertEquals($s3->name, $data->modified_records[1]['name']);
|
||||
$this->assertEquals(transform::yesno(true), $data->modified_records[1]['author_of_change_was_you']);
|
||||
$this->assertEquals(transform::yesno(false), $data->modified_records[1]['author_of_action_was_you']);
|
||||
$this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'),
|
||||
$data->modified_records[1]['action']);
|
||||
|
||||
// Export data for u9.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u9, 'core_grades', $allcontexts));
|
||||
$data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales_history');
|
||||
$this->assertCount(1, $data->modified_records);
|
||||
$this->assertEquals($s1->name, $data->modified_records[0]['name']);
|
||||
$this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_change_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_action_was_you']);
|
||||
$this->assertEquals(get_string('privacy:request:historyactionupdate', 'core_grades'),
|
||||
$data->modified_records[0]['action']);
|
||||
|
||||
// Export data for u10.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u10, 'core_grades', $allcontexts));
|
||||
$data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_related_data($relatedtomepath, 'scales_history');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($sysctx)->get_related_data($relatedtomepath, 'scales_history');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c2ctx)->get_related_data($relatedtomepath, 'scales_history');
|
||||
$this->assertCount(1, $data->modified_records);
|
||||
$this->assertEquals($s3->name, $data->modified_records[0]['name']);
|
||||
$this->assertEquals(transform::yesno(false), $data->modified_records[0]['author_of_change_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->modified_records[0]['author_of_action_was_you']);
|
||||
$this->assertEquals(get_string('privacy:request:historyactiondelete', 'core_grades'),
|
||||
$data->modified_records[0]['action']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -608,6 +608,17 @@ $string['prefrows'] = 'Special rows';
|
|||
$string['prefshow'] = 'Show/hide toggles';
|
||||
$string['previewrows'] = 'Preview rows';
|
||||
$string['privacy:metadata:categorieshistory'] = 'A record of previous versions of grade categories';
|
||||
$string['privacy:metadata:grade_import_newitem'] = 'Temporary table for storing new grade_item names from grade import';
|
||||
$string['privacy:metadata:grade_import_newitem:importcode'] = 'A unique batch code for identifying one batch of imports';
|
||||
$string['privacy:metadata:grade_import_newitem:importer'] = 'User importing the data';
|
||||
$string['privacy:metadata:grade_import_newitem:itemname'] = 'New grade item name';
|
||||
$string['privacy:metadata:grade_import_values'] = 'Temporary table for importing grades';
|
||||
$string['privacy:metadata:grade_import_values:feedback'] = 'Grade feedback';
|
||||
$string['privacy:metadata:grade_import_values:finalgrade'] = 'Raw grade value';
|
||||
$string['privacy:metadata:grade_import_values:importcode'] = 'A unique batch code for identifying one batch of imports';
|
||||
$string['privacy:metadata:grade_import_values:importer'] = 'User importing the data';
|
||||
$string['privacy:metadata:grade_import_values:importonlyfeedback'] = 'Flag if only feedback was imported';
|
||||
$string['privacy:metadata:grade_import_values:userid'] = 'User whose grade was imported';
|
||||
$string['privacy:metadata:grades'] = 'A record of grades';
|
||||
$string['privacy:metadata:grades:aggregationstatus'] = 'The aggregation status';
|
||||
$string['privacy:metadata:grades:aggregationweight'] = 'The weight in aggregation';
|
||||
|
@ -625,6 +636,10 @@ $string['privacy:metadata:outcomes'] = 'A record of outcomes';
|
|||
$string['privacy:metadata:outcomes:timemodified'] = 'Time at which the record was modified';
|
||||
$string['privacy:metadata:outcomes:usermodified'] = 'The user who last modified the record';
|
||||
$string['privacy:metadata:outcomeshistory'] = 'A record of previous versions of outcomes';
|
||||
$string['privacy:metadata:scale'] = 'A record of scales';
|
||||
$string['privacy:metadata:scale:timemodified'] = 'Time at which the record was last modified';
|
||||
$string['privacy:metadata:scale:userid'] = 'The user who last modified the record';
|
||||
$string['privacy:metadata:scalehistory'] = 'A record of previous versions of scales';
|
||||
$string['privacy:path:relatedtome'] = 'Related to me';
|
||||
$string['privacy:request:historyactiondelete'] = 'Delete';
|
||||
$string['privacy:request:historyactioninsert'] = 'Insert';
|
||||
|
|
|
@ -1561,6 +1561,16 @@ $string['privacy:metadata:events_queue:eventdata'] = 'The data stored in the eve
|
|||
$string['privacy:metadata:events_queue:stackdump'] = 'Any stacktrace associated with this event.';
|
||||
$string['privacy:metadata:events_queue:timecreated'] = 'The time that this event was created.';
|
||||
$string['privacy:metadata:events_queue:userid'] = 'The userid associated with this event.';
|
||||
$string['privacy:metadata:log'] = 'A collection of past events';
|
||||
$string['privacy:metadata:log:action'] = 'A description of the action';
|
||||
$string['privacy:metadata:log:cmid'] = 'cmid';
|
||||
$string['privacy:metadata:log:course'] = 'course';
|
||||
$string['privacy:metadata:log:info'] = 'Additional information';
|
||||
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
|
||||
$string['privacy:metadata:log:module'] = 'module';
|
||||
$string['privacy:metadata:log:time'] = 'The date at wich the action took place';
|
||||
$string['privacy:metadata:log:url'] = 'The URL related to the event';
|
||||
$string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action';
|
||||
$string['privacy:metadata:task_adhoc'] = 'The status of adhoc tasks.';
|
||||
$string['privacy:metadata:task_adhoc:component'] = 'The component owning the task.';
|
||||
$string['privacy:metadata:task_adhoc:nextruntime'] = 'The earliest time to run this task.';
|
||||
|
|
|
@ -170,6 +170,16 @@ $string['privacy:metadata'] = 'The portfolio subsystem acts as a channel, passin
|
|||
$string['privacy:metadata:name'] = 'Name of the preference.';
|
||||
$string['privacy:metadata:instance'] = 'Identifier for the portfolio.';
|
||||
$string['privacy:metadata:instancesummary'] = 'This stores portfolio both instances and preferences for the portfolios user is using.';
|
||||
$string['privacy:metadata:portfolio_log'] = 'Log of portfolio transfers (used to later check for duplicates)';
|
||||
$string['privacy:metadata:portfolio_log:caller_class'] = 'Name of the class used to create the transfer';
|
||||
$string['privacy:metadata:portfolio_log:caller_component'] = 'Component name responsible for exporting';
|
||||
$string['privacy:metadata:portfolio_log:time'] = 'Time of transfer (in the case of a queued transfer this is the time the actual transfer ran, not when the user started)';
|
||||
$string['privacy:metadata:portfolio_log:userid'] = 'User who exported content';
|
||||
$string['privacy:metadata:portfolio_tempdata'] = 'Stores temporary data for portfolio exports, cleaned by cron after one day';
|
||||
$string['privacy:metadata:portfolio_tempdata:data'] = 'Export data';
|
||||
$string['privacy:metadata:portfolio_tempdata:expirytime'] = 'Time this record will expire';
|
||||
$string['privacy:metadata:portfolio_tempdata:instance'] = 'Portfolio plugin instance being used';
|
||||
$string['privacy:metadata:portfolio_tempdata:userid'] = 'User performing export';
|
||||
$string['privacy:metadata:value'] = 'Value for the preference';
|
||||
$string['privacy:metadata:userid'] = 'The user Identifier.';
|
||||
$string['privacy:path'] = 'Portfolio instances';
|
||||
|
|
|
@ -75,6 +75,10 @@ $string['privacy:metadata:middlename'] = 'The middle name of the user.';
|
|||
$string['privacy:metadata:mnethostid'] = 'An identifier for the mnet host if used.';
|
||||
$string['privacy:metadata:model'] = 'The device name, occam or iPhone etc..';
|
||||
$string['privacy:metadata:msn'] = 'The MSN identifier of the user.';
|
||||
$string['privacy:metadata:my_pages'] = 'User pages - dashboard and profile. This table does not contain personal data and only used to link dashboard blocks to users';
|
||||
$string['privacy:metadata:my_pages:name'] = 'Page name';
|
||||
$string['privacy:metadata:my_pages:private'] = 'Whether or not the page is private (dashboard) or public (profile)';
|
||||
$string['privacy:metadata:my_pages:userid'] = 'The user who owns this page or 0 for system defaults';
|
||||
$string['privacy:metadata:password'] = 'The password for this user to log into the system.';
|
||||
$string['privacy:metadata:passwordresettablesummary'] = 'A table tracking password reset confirmation tokens';
|
||||
$string['privacy:metadata:passwordtablesummary'] = 'A rotating log of hashes of previously used passwords for the user.';
|
||||
|
@ -87,6 +91,10 @@ $string['privacy:metadata:reason'] = 'The reason for requesting this course.';
|
|||
$string['privacy:metadata:requester'] = 'An identifier to a user that requested this course.';
|
||||
$string['privacy:metadata:requestsummary'] = 'Stores information about requests for courses that users make.';
|
||||
$string['privacy:metadata:suspended'] = 'A flag to show if the user has been suspended on this system.';
|
||||
$string['privacy:metadata:user_preferences'] = 'Preferences associated with the given user';
|
||||
$string['privacy:metadata:user_preferences:name'] = 'Preference name';
|
||||
$string['privacy:metadata:user_preferences:userid'] = 'User id';
|
||||
$string['privacy:metadata:user_preferences:value'] = 'Preference value';
|
||||
$string['privacy:metadata:username'] = 'The username for this user.';
|
||||
$string['privacy:metadata:secret'] = 'Secret.. not sure.';
|
||||
$string['privacy:metadata:sessdata'] = 'Session content';
|
||||
|
|
|
@ -91,6 +91,16 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
|
|||
'timecreated' => 'privacy:metadata:events_queue:timecreated',
|
||||
], 'privacy:metadata:events_queue');
|
||||
|
||||
// The log table is defined in core but used in logstore_legacy.
|
||||
$collection->add_database_table('log', [
|
||||
'time' => 'privacy:metadata:log:time',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'ip' => 'privacy:metadata:log:ip',
|
||||
'action' => 'privacy:metadata:log:action',
|
||||
'url' => 'privacy:metadata:log:url',
|
||||
'info' => 'privacy:metadata:log:info'
|
||||
], 'privacy:metadata:log');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,10 +63,26 @@ class provider implements
|
|||
'timestamp' => 'privacy:metadata:messages:timestamp',
|
||||
], 'privacy:metadata:messages');
|
||||
|
||||
// The tables chat_messages_current and chat_users are not reported here
|
||||
// The tables chat_messages_current and chat_users are not exported/deleted
|
||||
// because they are considered as short-lived data and are deleted on a
|
||||
// regular basis by cron, or during normal requests. MDL-62006 was raised
|
||||
// to discuss and/or implement support for those tables.
|
||||
// regular basis by cron, or during normal requests. TODO MDL-62006.
|
||||
|
||||
$collection->add_database_table('chat_messages_current', [
|
||||
'userid' => 'privacy:metadata:messages:userid',
|
||||
'message' => 'privacy:metadata:messages:message',
|
||||
'issystem' => 'privacy:metadata:messages:issystem',
|
||||
'timestamp' => 'privacy:metadata:messages:timestamp'
|
||||
], 'privacy:metadata:chat_messages_current');
|
||||
|
||||
$collection->add_database_table('chat_users', [
|
||||
'userid' => 'privacy:metadata:chat_users:userid',
|
||||
'version' => 'privacy:metadata:chat_users:version',
|
||||
'ip' => 'privacy:metadata:chat_users:ip',
|
||||
'firstping' => 'privacy:metadata:chat_users:firstping',
|
||||
'lastping' => 'privacy:metadata:chat_users:lastping',
|
||||
'lastmessageping' => 'privacy:metadata:chat_users:lastmessageping',
|
||||
'lang' => 'privacy:metadata:chat_users:lang'
|
||||
], 'privacy:metadata:chat_users');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
|
|
@ -118,6 +118,15 @@ $string['page-mod-chat-x'] = 'Any chat module page';
|
|||
$string['pastchats'] = 'Past chat sessions';
|
||||
$string['pluginadministration'] = 'Chat administration';
|
||||
$string['pluginname'] = 'Chat';
|
||||
$string['privacy:metadata:chat_messages_current'] = 'Current chat session. This data is temporary and is deleted after the chat session is deleted';
|
||||
$string['privacy:metadata:chat_users'] = 'Keeps track of which users are in which chat rooms';
|
||||
$string['privacy:metadata:chat_users:firstping'] = 'Time of the first access to chat room';
|
||||
$string['privacy:metadata:chat_users:ip'] = 'User IP';
|
||||
$string['privacy:metadata:chat_users:lang'] = 'User language';
|
||||
$string['privacy:metadata:chat_users:lastmessageping'] = 'Time of the last message in this chat room';
|
||||
$string['privacy:metadata:chat_users:lastping'] = 'Time of the last access to chat room';
|
||||
$string['privacy:metadata:chat_users:userid'] = 'User id';
|
||||
$string['privacy:metadata:chat_users:version'] = 'How user accessed the chat (sockets/basic/ajax/header_js)';
|
||||
$string['privacy:metadata:messages'] = 'A record of the messages sent during a chat session';
|
||||
$string['privacy:metadata:messages:issystem'] = 'Whether the message is a system-generated message';
|
||||
$string['privacy:metadata:messages:message'] = 'The message';
|
||||
|
|
|
@ -117,6 +117,14 @@ class provider implements
|
|||
'forumid' => 'privacy:metadata:forum_track_prefs:forumid',
|
||||
], 'privacy:metadata:forum_track_prefs');
|
||||
|
||||
// The 'forum_queue' table stores temporary data that is not exported/deleted.
|
||||
$items->add_database_table('forum_queue', [
|
||||
'userid' => 'privacy:metadata:forum_queue:userid',
|
||||
'discussionid' => 'privacy:metadata:forum_queue:discussionid',
|
||||
'postid' => 'privacy:metadata:forum_queue:postid',
|
||||
'timemodified' => 'privacy:metadata:forum_queue:timemodified'
|
||||
], 'privacy:metadata:forum_queue');
|
||||
|
||||
// Forum posts can be tagged and rated.
|
||||
$items->link_subsystem('core_tag', 'privacy:metadata:core_tag');
|
||||
$items->link_subsystem('core_rating', 'privacy:metadata:core_rating');
|
||||
|
|
|
@ -459,6 +459,11 @@ $string['privacy:metadata:forum_posts:parent'] = 'The parent post that was repli
|
|||
$string['privacy:metadata:forum_posts:subject'] = 'The subject of the forum post.';
|
||||
$string['privacy:metadata:forum_posts:totalscore'] = 'The message of the forum post.';
|
||||
$string['privacy:metadata:forum_posts:userid'] = 'The ID of the user who authored the forum post.';
|
||||
$string['privacy:metadata:forum_queue'] = 'Temporary log of posts that will be mailed in digest form';
|
||||
$string['privacy:metadata:forum_queue:discussionid'] = 'Forum discussion id';
|
||||
$string['privacy:metadata:forum_queue:postid'] = 'Forum post id';
|
||||
$string['privacy:metadata:forum_queue:timemodified'] = 'The modified time of the original post';
|
||||
$string['privacy:metadata:forum_queue:userid'] = 'User who needs to be notified of the post';
|
||||
$string['privacy:metadata:forum_read'] = 'Information about which posts have been read by the user.';
|
||||
$string['privacy:metadata:forum_read:discussionid'] = 'The discussion that the post is in.';
|
||||
$string['privacy:metadata:forum_read:firstread'] = 'The first time that the post was read.';
|
||||
|
|
|
@ -29,6 +29,7 @@ use core_privacy\local\metadata\collection;
|
|||
use core_privacy\local\request\context;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
|
||||
/**
|
||||
* Provider for the portfolio API.
|
||||
|
@ -56,6 +57,22 @@ class provider implements
|
|||
'name' => 'privacy:metadata:name',
|
||||
'value' => 'privacy:metadata:value'
|
||||
], 'privacy:metadata:instancesummary');
|
||||
|
||||
$collection->add_database_table('portfolio_log', [
|
||||
'userid' => 'privacy:metadata:portfolio_log:userid',
|
||||
'time' => 'privacy:metadata:portfolio_log:time',
|
||||
'caller_class' => 'privacy:metadata:portfolio_log:caller_class',
|
||||
'caller_component' => 'privacy:metadata:portfolio_log:caller_component',
|
||||
], 'privacy:metadata:portfolio_log');
|
||||
|
||||
// Temporary data is not exported/deleted in privacy API. It is cleaned by cron.
|
||||
$collection->add_database_table('portfolio_tempdata', [
|
||||
'data' => 'privacy:metadata:portfolio_tempdata:data',
|
||||
'expirytime' => 'privacy:metadata:portfolio_tempdata:expirytime',
|
||||
'userid' => 'privacy:metadata:portfolio_tempdata:userid',
|
||||
'instance' => 'privacy:metadata:portfolio_tempdata:instance',
|
||||
], 'privacy:metadata:portfolio_tempdata');
|
||||
|
||||
$collection->add_plugintype_link('portfolio', [], 'privacy:metadata');
|
||||
return $collection;
|
||||
}
|
||||
|
@ -69,9 +86,11 @@ class provider implements
|
|||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {portfolio_instance_user} piu ON ctx.instanceid = piu.userid AND ctx.contextlevel = :usercontext
|
||||
WHERE piu.userid = :userid";
|
||||
$params = ['userid' => $userid, 'usercontext' => CONTEXT_USER];
|
||||
WHERE ctx.instanceid = :userid AND ctx.contextlevel = :usercontext
|
||||
AND (EXISTS (SELECT 1 FROM {portfolio_instance_user} WHERE userid = :userid1) OR
|
||||
EXISTS (SELECT 1 FROM {portfolio_log} WHERE userid = :userid2))
|
||||
";
|
||||
$params = ['userid' => $userid, 'usercontext' => CONTEXT_USER, 'userid1' => $userid, 'userid2' => $userid];
|
||||
$contextlist = new contextlist();
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
return $contextlist;
|
||||
|
@ -95,16 +114,63 @@ class provider implements
|
|||
}
|
||||
});
|
||||
|
||||
if (empty($correctusercontext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$usercontext = array_shift($correctusercontext);
|
||||
|
||||
$sql = "SELECT pi.id AS instanceid, pi.name,
|
||||
piu.id AS preferenceid, piu.name AS preference, piu.value,
|
||||
pl.id AS logid, pl.time AS logtime, pl.caller_class, pl.caller_file,
|
||||
pl.caller_component, pl.returnurl, pl.continueurl
|
||||
FROM {portfolio_instance} pi
|
||||
LEFT JOIN {portfolio_instance_user} piu ON piu.instance = pi.id AND piu.userid = :userid1
|
||||
LEFT JOIN {portfolio_log} pl ON pl.portfolio = pi.id AND pl.userid = :userid2
|
||||
WHERE piu.id IS NOT NULL OR pl.id IS NOT NULL";
|
||||
$params = ['userid1' => $usercontext->instanceid, 'userid2' => $usercontext->instanceid];
|
||||
$instances = [];
|
||||
$rs = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($rs as $record) {
|
||||
$instances += [$record->name =>
|
||||
(object)[
|
||||
'name' => $record->name,
|
||||
'preferences' => [],
|
||||
'logs' => [],
|
||||
]
|
||||
];
|
||||
if ($record->preferenceid) {
|
||||
$instances[$record->name]->preferences[$record->preferenceid] = (object)[
|
||||
'name' => $record->preference,
|
||||
'value' => $record->value,
|
||||
];
|
||||
}
|
||||
if ($record->logid) {
|
||||
$instances[$record->name]->logs[$record->logid] = (object)[
|
||||
'time' => transform::datetime($record->logtime),
|
||||
'caller_class' => $record->caller_class,
|
||||
'caller_file' => $record->caller_file,
|
||||
'caller_component' => $record->caller_component,
|
||||
'returnurl' => $record->returnurl,
|
||||
'continueurl' => $record->continueurl
|
||||
];
|
||||
}
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
$sql = "SELECT pi.name, piu.name AS preference, piu.value
|
||||
FROM {portfolio_instance_user} piu
|
||||
JOIN {portfolio_instance} pi ON piu.instance = pi.id
|
||||
WHERE piu.userid = :userid";
|
||||
$params = ['userid' => $usercontext->instanceid];
|
||||
$instances = $DB->get_records_sql($sql, $params);
|
||||
if (!empty($instances)) {
|
||||
foreach ($instances as &$instance) {
|
||||
if (!empty($instance->preferences)) {
|
||||
$instance->preferences = array_values($instance->preferences);
|
||||
} else {
|
||||
unset($instance->preferences);
|
||||
}
|
||||
if (!empty($instance->logs)) {
|
||||
$instance->logs = array_values($instance->logs);
|
||||
} else {
|
||||
unset($instance->logs);
|
||||
}
|
||||
}
|
||||
\core_privacy\local\request\writer::with_context($contextlist->current())->export_data(
|
||||
[get_string('privacy:path', 'portfolio')], (object) $instances);
|
||||
}
|
||||
|
@ -120,6 +186,8 @@ class provider implements
|
|||
// Context could be anything, BEWARE!
|
||||
if ($context->contextlevel == CONTEXT_USER) {
|
||||
$DB->delete_records('portfolio_instance_user', ['userid' => $context->instanceid]);
|
||||
$DB->delete_records('portfolio_tempdata', ['userid' => $context->instanceid]);
|
||||
$DB->delete_records('portfolio_log', ['userid' => $context->instanceid]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,9 +209,15 @@ class provider implements
|
|||
}
|
||||
});
|
||||
|
||||
if (empty($correctusercontext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$usercontext = array_shift($correctusercontext);
|
||||
|
||||
$DB->delete_records('portfolio_instance_user', ['userid' => $usercontext->instanceid]);
|
||||
$DB->delete_records('portfolio_tempdata', ['userid' => $usercontext->instanceid]);
|
||||
$DB->delete_records('portfolio_log', ['userid' => $usercontext->instanceid]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,6 +47,22 @@ class portfolio_privacy_provider_test extends \core_privacy\tests\provider_testc
|
|||
'value' => $value
|
||||
];
|
||||
$DB->insert_record('portfolio_instance_user', $userinstance);
|
||||
|
||||
$DB->insert_record('portfolio_log', [
|
||||
'portfolio' => $portfolioinstance->id,
|
||||
'userid' => $user->id,
|
||||
'caller_class' => 'forum_portfolio_caller',
|
||||
'caller_component' => 'mod_forum',
|
||||
'time' => time(),
|
||||
]);
|
||||
|
||||
$DB->insert_record('portfolio_log', [
|
||||
'portfolio' => $portfolioinstance->id,
|
||||
'userid' => $user->id,
|
||||
'caller_class' => 'workshop_portfolio_caller',
|
||||
'caller_component' => 'mod_workshop',
|
||||
'time' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,9 +73,11 @@ class portfolio_privacy_provider_test extends \core_privacy\tests\provider_testc
|
|||
$collection = \core_portfolio\privacy\provider::get_metadata($collection);
|
||||
$this->assertNotEmpty($collection);
|
||||
$items = $collection->get_collection();
|
||||
$this->assertEquals(2, count($items));
|
||||
$this->assertEquals(4, count($items));
|
||||
$this->assertInstanceOf(\core_privacy\local\metadata\types\database_table::class, $items[0]);
|
||||
$this->assertInstanceOf(\core_privacy\local\metadata\types\plugintype_link::class, $items[1]);
|
||||
$this->assertInstanceOf(\core_privacy\local\metadata\types\database_table::class, $items[1]);
|
||||
$this->assertInstanceOf(\core_privacy\local\metadata\types\database_table::class, $items[2]);
|
||||
$this->assertInstanceOf(\core_privacy\local\metadata\types\plugintype_link::class, $items[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,6 +123,7 @@ class portfolio_privacy_provider_test extends \core_privacy\tests\provider_testc
|
|||
\core_portfolio\privacy\provider::delete_data_for_all_users_in_context($systemcontext);
|
||||
$records = $DB->get_records('portfolio_instance_user');
|
||||
$this->assertCount(2, $records);
|
||||
$this->assertCount(4, $DB->get_records('portfolio_log'));
|
||||
$context = context_user::instance($user1->id);
|
||||
\core_portfolio\privacy\provider::delete_data_for_all_users_in_context($context);
|
||||
$records = $DB->get_records('portfolio_instance_user');
|
||||
|
@ -112,6 +131,7 @@ class portfolio_privacy_provider_test extends \core_privacy\tests\provider_testc
|
|||
$this->assertCount(1, $records);
|
||||
$data = array_shift($records);
|
||||
$this->assertEquals($user2->id, $data->userid);
|
||||
$this->assertCount(2, $DB->get_records('portfolio_log'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,6 +148,7 @@ class portfolio_privacy_provider_test extends \core_privacy\tests\provider_testc
|
|||
|
||||
$records = $DB->get_records('portfolio_instance_user');
|
||||
$this->assertCount(2, $records);
|
||||
$this->assertCount(4, $DB->get_records('portfolio_log'));
|
||||
|
||||
$context = context_user::instance($user1->id);
|
||||
$contextlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_portfolio', [$context->id]);
|
||||
|
@ -137,5 +158,6 @@ class portfolio_privacy_provider_test extends \core_privacy\tests\provider_testc
|
|||
$this->assertCount(1, $records);
|
||||
$data = array_shift($records);
|
||||
$this->assertEquals($user2->id, $data->userid);
|
||||
$this->assertCount(2, $DB->get_records('portfolio_log'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,10 @@ class provider_testcase extends advanced_testcase {
|
|||
* @return array the array of frankenstyle component names with the relevant class name.
|
||||
*/
|
||||
public function get_component_list() {
|
||||
$components = [];
|
||||
$components = ['core' => [
|
||||
'component' => 'core',
|
||||
'classname' => manager::get_provider_classname_for_component('core')
|
||||
]];
|
||||
// Get all plugins.
|
||||
$plugintypes = \core_component::get_plugin_types();
|
||||
foreach ($plugintypes as $plugintype => $typedir) {
|
||||
|
@ -204,4 +207,72 @@ class provider_testcase extends advanced_testcase {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds user fields in a table
|
||||
*
|
||||
* Returns fields that have foreign key to user table and fields that are named 'userid'.
|
||||
*
|
||||
* @param xmldb_table $table
|
||||
* @return array
|
||||
*/
|
||||
protected function get_userid_fields(xmldb_table $table) {
|
||||
$userfields = [];
|
||||
|
||||
// Find all fields that have a foreign key to 'id' field in 'user' table.
|
||||
$keys = $table->getKeys();
|
||||
foreach ($keys as $key) {
|
||||
$reffields = $key->getRefFields();
|
||||
$fields = $key->getFields();
|
||||
if ($key->getRefTable() === 'user' && count($reffields) == 1 && $reffields[0] == 'id' && count($fields) == 1) {
|
||||
$userfields[$fields[0]] = $fields[0];
|
||||
}
|
||||
}
|
||||
// Find fields with the name 'userid' even if they don't have a foreign key.
|
||||
$fields = $table->getFields();
|
||||
foreach ($fields as $field) {
|
||||
if ($field->getName() == 'userid') {
|
||||
$userfields['userid'] = 'userid';
|
||||
}
|
||||
}
|
||||
|
||||
return $userfields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all tables with user fields are covered by metadata providers
|
||||
*/
|
||||
public function test_table_coverage() {
|
||||
global $DB;
|
||||
$dbman = $DB->get_manager();
|
||||
$schema = $dbman->get_install_xml_schema();
|
||||
$tables = [];
|
||||
foreach ($schema->getTables() as $table) {
|
||||
if ($table->getName() === 'role_sortorder') {
|
||||
// TODO MDL-62459 this table is not used anywhere. Remove the table and this statement.
|
||||
continue;
|
||||
}
|
||||
if ($fields = $this->get_userid_fields($table)) {
|
||||
$tables[$table->getName()] = ' - ' . $table->getName() . ' (' . join(', ', $fields) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$componentlist = $this->metadata_provider_provider();
|
||||
foreach ($componentlist as $componentarray) {
|
||||
$component = $componentarray['component'];
|
||||
$classname = $componentarray['classname'];
|
||||
$collection = new collection($component);
|
||||
$metadata = $classname::get_metadata($collection);
|
||||
foreach ($metadata->get_collection() as $item) {
|
||||
if ($item instanceof database_table) {
|
||||
unset($tables[$item->get_name()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($tables) {
|
||||
$this->fail("The following tables with user fields must be covered with metadata providers: \n".
|
||||
join("\n", $tables));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,6 +154,18 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
|
|||
'requester' => 'privacy:metadata:requester'
|
||||
];
|
||||
|
||||
$mypages = [
|
||||
'userid' => 'privacy:metadata:my_pages:userid',
|
||||
'name' => 'privacy:metadata:my_pages:name',
|
||||
'private' => 'privacy:metadata:my_pages:private',
|
||||
];
|
||||
|
||||
$userpreferences = [
|
||||
'userid' => 'privacy:metadata:user_preferences:userid',
|
||||
'name' => 'privacy:metadata:user_preferences:name',
|
||||
'value' => 'privacy:metadata:user_preferences:value'
|
||||
];
|
||||
|
||||
$collection->add_database_table('user', $userfields, 'privacy:metadata:usertablesummary');
|
||||
$collection->add_database_table('user_password_history', $passwordhistory, 'privacy:metadata:passwordtablesummary');
|
||||
$collection->add_database_table('user_password_resets', $userpasswordresets, 'privacy:metadata:passwordresettablesummary');
|
||||
|
@ -161,6 +173,8 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l
|
|||
$collection->add_database_table('user_devices', $userdevices, 'privacy:metadata:devicetablesummary');
|
||||
$collection->add_database_table('course_request', $courserequest, 'privacy:metadata:requestsummary');
|
||||
$collection->add_database_table('sessions', $usersessions, 'privacy:metadata:sessiontablesummary');
|
||||
$collection->add_database_table('my_pages', $mypages, 'privacy:metadata:my_pages');
|
||||
$collection->add_database_table('user_preferences', $userpreferences, 'privacy:metadata:user_preferences');
|
||||
$collection->add_subsystem_link('core_files', [], 'privacy:metadata:filelink');
|
||||
|
||||
return $collection;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue