Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Merge branch 'MDL-62308_34' of git://github.com/markn86/moodle into M…
…OODLE_34_STABLE
- Loading branch information
Showing
with
396 additions
and 0 deletions.
- +185 −0 backup/tests/privacy_provider_test.php
- +202 −0 backup/util/ui/classes/privacy/provider.php
- +8 −0 lang/en/backup.php
- +1 −0 phpunit.xml.dist
@@ -0,0 +1,185 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
/** | ||
* Privacy provider tests. | ||
* | ||
* @package core_backup | ||
* @copyright 2018 Mark Nelson <markn@moodle.com> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
|
||
use core_backup\privacy\provider; | ||
|
||
defined('MOODLE_INTERNAL') || die(); | ||
|
||
/** | ||
* Privacy provider tests class. | ||
* | ||
* @copyright 2018 Mark Nelson <markn@moodle.com> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class core_backup_privacy_provider_testcase extends \core_privacy\tests\provider_testcase { | ||
|
||
/** | ||
* @var stdClass The user | ||
*/ | ||
protected $user = null; | ||
|
||
/** | ||
* @var stdClass The course | ||
*/ | ||
protected $course = null; | ||
|
||
/** | ||
* Basic setup for these tests. | ||
*/ | ||
public function setUp() { | ||
global $DB; | ||
|
||
$this->resetAfterTest(); | ||
|
||
$this->course = $this->getDataGenerator()->create_course(); | ||
|
||
$this->user = $this->getDataGenerator()->create_user(); | ||
|
||
// Just insert directly into the 'backup_controllers' table. | ||
$bcdata = (object) [ | ||
'backupid' => 1, | ||
'operation' => 'restore', | ||
'type' => 'course', | ||
'itemid' => $this->course->id, | ||
'format' => 'moodle2', | ||
'interactive' => 1, | ||
'purpose' => 10, | ||
'userid' => $this->user->id, | ||
'status' => 1000, | ||
'execution' => 1, | ||
'executiontime' => 0, | ||
'checksum' => 'checksumyolo', | ||
'timecreated' => time(), | ||
'timemodified' => time(), | ||
'controller' => '' | ||
]; | ||
$DB->insert_record('backup_controllers', $bcdata); | ||
|
||
// Create another user who will perform a backup operation. | ||
$user = $this->getDataGenerator()->create_user(); | ||
$bcdata->backupid = 2; | ||
$bcdata->userid = $user->id; | ||
$DB->insert_record('backup_controllers', $bcdata); | ||
} | ||
|
||
/** | ||
* Test getting the context for the user ID related to this plugin. | ||
*/ | ||
public function test_get_contexts_for_userid() { | ||
$contextlist = provider::get_contexts_for_userid($this->user->id); | ||
$this->assertCount(1, $contextlist); | ||
$contextforuser = $contextlist->current(); | ||
$context = context_course::instance($this->course->id); | ||
$this->assertEquals($context->id, $contextforuser->id); | ||
} | ||
|
||
/** | ||
* Test for provider::export_user_data(). | ||
*/ | ||
public function test_export_for_context() { | ||
global $DB; | ||
|
||
// Create another backup_controllers record. | ||
$bcdata = (object) [ | ||
'backupid' => 3, | ||
'operation' => 'backup', | ||
'type' => 'course', | ||
'itemid' => $this->course->id, | ||
'format' => 'moodle2', | ||
'interactive' => 1, | ||
'purpose' => 10, | ||
'userid' => $this->user->id, | ||
'status' => 1000, | ||
'execution' => 1, | ||
'executiontime' => 0, | ||
'checksum' => 'checksumyolo', | ||
'timecreated' => time() + DAYSECS, | ||
'timemodified' => time() + DAYSECS, | ||
'controller' => '' | ||
]; | ||
$DB->insert_record('backup_controllers', $bcdata); | ||
|
||
$coursecontext = context_course::instance($this->course->id); | ||
|
||
// Export all of the data for the context. | ||
$this->export_context_data_for_user($this->user->id, $coursecontext, 'core_backup'); | ||
$writer = \core_privacy\local\request\writer::with_context($coursecontext); | ||
$this->assertTrue($writer->has_any_data()); | ||
|
||
$data = (array) $writer->get_data([get_string('backup'), $this->course->id]); | ||
|
||
$this->assertCount(2, $data); | ||
|
||
$bc1 = array_shift($data); | ||
$this->assertEquals('restore', $bc1['operation']); | ||
|
||
$bc2 = array_shift($data); | ||
$this->assertEquals('backup', $bc2['operation']); | ||
} | ||
|
||
/** | ||
* Test for provider::delete_data_for_all_users_in_context(). | ||
*/ | ||
public function test_delete_data_for_all_users_in_context() { | ||
global $DB; | ||
|
||
// Before deletion, we should have 2 operations. | ||
$count = $DB->count_records('backup_controllers', ['itemid' => $this->course->id]); | ||
$this->assertEquals(2, $count); | ||
|
||
// Delete data based on context. | ||
$coursecontext = context_course::instance($this->course->id); | ||
provider::delete_data_for_all_users_in_context($coursecontext); | ||
|
||
// After deletion, the operations for that course should have been deleted. | ||
$count = $DB->count_records('backup_controllers', ['itemid' => $this->course->id]); | ||
$this->assertEquals(0, $count); | ||
} | ||
|
||
/** | ||
* Test for provider::delete_data_for_user(). | ||
*/ | ||
public function test_delete_data_for_user() { | ||
global $DB; | ||
|
||
// Before deletion, we should have 2 operations. | ||
$count = $DB->count_records('backup_controllers', ['itemid' => $this->course->id]); | ||
$this->assertEquals(2, $count); | ||
|
||
$coursecontext = context_course::instance($this->course->id); | ||
$contextlist = new \core_privacy\local\request\approved_contextlist($this->user, 'core_backup', | ||
[$coursecontext->id]); | ||
provider::delete_data_for_user($contextlist); | ||
|
||
// After deletion, the backup operation for the user should have been deleted. | ||
$count = $DB->count_records('backup_controllers', ['itemid' => $this->course->id, 'userid' => $this->user->id]); | ||
$this->assertEquals(0, $count); | ||
|
||
// Confirm we still have the other users record. | ||
$bcs = $DB->get_records('backup_controllers'); | ||
$this->assertCount(1, $bcs); | ||
$lastsubmission = reset($bcs); | ||
$this->assertNotEquals($this->user->id, $lastsubmission->userid); | ||
} | ||
} |
@@ -0,0 +1,202 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
/** | ||
* Privacy Subsystem implementation for core_backup. | ||
* | ||
* @package core_backup | ||
* @copyright 2018 Mark Nelson <markn@moodle.com> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
|
||
namespace core_backup\privacy; | ||
|
||
use core_privacy\local\metadata\collection; | ||
use core_privacy\local\request\approved_contextlist; | ||
use core_privacy\local\request\contextlist; | ||
use core_privacy\local\request\transform; | ||
use core_privacy\local\request\writer; | ||
|
||
defined('MOODLE_INTERNAL') || die(); | ||
|
||
/** | ||
* Privacy Subsystem implementation for core_backup. | ||
* | ||
* @copyright 2018 Mark Nelson <markn@moodle.com> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class provider implements | ||
\core_privacy\local\metadata\provider, | ||
\core_privacy\local\request\subsystem\provider { | ||
|
||
/** | ||
* Return the fields which contain personal data. | ||
* | ||
* @param collection $items a reference to the collection to use to store the metadata. | ||
* @return collection the updated collection of metadata items. | ||
*/ | ||
public static function get_metadata(collection $items) : collection { | ||
$items->link_external_location( | ||
'Backup', | ||
[ | ||
'detailsofarchive' => 'privacy:metadata:backup:detailsofarchive' | ||
], | ||
'privacy:metadata:backup:externalpurpose' | ||
); | ||
|
||
$items->add_database_table( | ||
'backup_controllers', | ||
[ | ||
'operation' => 'privacy:metadata:backup_controllers:operation', | ||
'type' => 'privacy:metadata:backup_controllers:type', | ||
'itemid' => 'privacy:metadata:backup_controllers:itemid', | ||
'timecreated' => 'privacy:metadata:backup_controllers:timecreated', | ||
'timemodified' => 'privacy:metadata:backup_controllers:timemodified' | ||
], | ||
'privacy:metadata:backup_controllers' | ||
); | ||
|
||
return $items; | ||
} | ||
|
||
/** | ||
* Get the list of contexts that contain user information for the specified user. | ||
* | ||
* @param int $userid The user to search. | ||
* @return contextlist The contextlist containing the list of contexts used in this plugin. | ||
*/ | ||
public static function get_contexts_for_userid(int $userid) : contextlist { | ||
$contextlist = new contextlist(); | ||
|
||
$sql = "SELECT DISTINCT ctx.id | ||
FROM {backup_controllers} bc | ||
JOIN {context} ctx | ||
ON ctx.instanceid = bc.itemid AND ctx.contextlevel = :contextlevel | ||
WHERE bc.userid = :userid"; | ||
$params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $userid]; | ||
$contextlist->add_from_sql($sql, $params); | ||
|
||
return $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) { | ||
global $DB; | ||
|
||
if (empty($contextlist->count())) { | ||
return; | ||
} | ||
|
||
$user = $contextlist->get_user(); | ||
|
||
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); | ||
|
||
$sql = "SELECT bc.* | ||
FROM {backup_controllers} bc | ||
JOIN {context} ctx | ||
ON ctx.instanceid = bc.itemid AND ctx.contextlevel = :contextlevel | ||
WHERE ctx.id {$contextsql} | ||
AND bc.userid = :userid | ||
ORDER BY bc.timecreated ASC"; | ||
$params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $user->id] + $contextparams; | ||
$backupcontrollers = $DB->get_recordset_sql($sql, $params); | ||
self::recordset_loop_and_export($backupcontrollers, 'itemid', [], function($carry, $record) { | ||
$carry[] = [ | ||
'operation' => $record->operation, | ||
'type' => $record->type, | ||
'itemid' => $record->itemid, | ||
'timecreated' => transform::datetime($record->timecreated), | ||
'timemodified' => transform::datetime($record->timemodified), | ||
]; | ||
return $carry; | ||
}, function($courseid, $data) { | ||
$context = \context_course::instance($courseid); | ||
$finaldata = (object) $data; | ||
writer::with_context($context)->export_data([get_string('backup'), $courseid], $finaldata); | ||
}); | ||
} | ||
|
||
/** | ||
* Delete all user data which matches the specified context. | ||
* | ||
* @param \context $context A user context. | ||
*/ | ||
public static function delete_data_for_all_users_in_context(\context $context) { | ||
global $DB; | ||
|
||
if (!$context instanceof \context_course) { | ||
return; | ||
} | ||
|
||
$DB->delete_records('backup_controllers', ['itemid' => $context->instanceid]); | ||
} | ||
|
||
/** | ||
* 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) { | ||
global $DB; | ||
|
||
if (empty($contextlist->count())) { | ||
return; | ||
} | ||
|
||
$userid = $contextlist->get_user()->id; | ||
foreach ($contextlist->get_contexts() as $context) { | ||
if (!$context instanceof \context_course) { | ||
return; | ||
} | ||
|
||
$DB->delete_records('backup_controllers', ['itemid' => $context->instanceid, 'userid' => $userid]); | ||
} | ||
} | ||
|
||
/** | ||
* Loop and export from a recordset. | ||
* | ||
* @param \moodle_recordset $recordset The recordset. | ||
* @param string $splitkey The record key to determine when to export. | ||
* @param mixed $initial The initial data to reduce from. | ||
* @param callable $reducer The function to return the dataset, receives current dataset, and the current record. | ||
* @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. | ||
* @return void | ||
*/ | ||
protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial, | ||
callable $reducer, callable $export) { | ||
$data = $initial; | ||
$lastid = null; | ||
|
||
foreach ($recordset as $record) { | ||
if ($lastid && $record->{$splitkey} != $lastid) { | ||
$export($lastid, $data); | ||
$data = $initial; | ||
} | ||
$data = $reducer($data, $record); | ||
$lastid = $record->{$splitkey}; | ||
} | ||
$recordset->close(); | ||
|
||
if (!empty($lastid)) { | ||
$export($lastid, $data); | ||
} | ||
} | ||
} |
Oops, something went wrong.