Merge branch 'MDL-61307-34' of git://github.com/andrewnicols/moodle into MOODLE_34_STABLE

This commit is contained in:
Jake Dallimore
2018-03-09 13:36:55 +08:00
78 changed files with 8734 additions and 2 deletions
+130
View File
@@ -0,0 +1,130 @@
<?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 class for requesting user data.
*
* @package core_comment
* @copyright 2018 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_comment\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
/**
* Privacy class for requesting user data.
*
* @package core_comment
* @copyright 2018 Adrian Greeve <[email protected]>
* @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\plugin_provider {
/**
* Returns meta data about this system.
*
* @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_metadata(collection $collection) : collection {
$collection->add_database_table('comments', [
'content' => 'privacy:metadata:comment:content',
'timecreated' => 'privacy:metadata:comment:timecreated',
'userid' => 'privacy:metadata:comment:userid',
], 'privacy:metadata:comment');
return $collection;
}
/**
* Writes user data to the writer for the user to download.
*
* @param array $context Contexts to run through and return data.
* @param string $component The component that is calling this function
* @param string $commentarea The comment area related to the component
* @param int $itemid An identifier for a group of comments
* @param array $subcontext The sub-context in which to export this data
* @param bool $onlyforthisuser Only return the comments this user made.
*/
public static function export_comments($context, $component, $commentarea, $itemid, $subcontext, $onlyforthisuser = true) {
$data = new \stdClass;
$data->context = $context;
$data->area = $commentarea;
$data->itemid = $itemid;
$data->component = $component;
$commentobject = new \comment($data);
$commentobject->set_view_permission(true);
$comments = $commentobject->get_comments(0);
$subcontext[] = get_string('commentsubcontext', 'core_comment');
$comments = array_filter($comments, function($comment) use ($onlyforthisuser) {
global $USER;
return (!$onlyforthisuser || $comment->userid == $USER->id);
});
$comments = array_map(function($comment) {
return (object) [
'content' => $comment->content,
'time' => transform::datetime($comment->timecreated),
'userid' => transform::user($comment->userid),
];
}, $comments);
if (!empty($comments)) {
\core_privacy\local\request\writer::with_context($context)
->export_data($subcontext, (object) [
'comments' => $comments,
]);
}
}
/**
* Deletes all comments for a specified context.
*
* @param \core_privacy\local\request\deletion_criteria $criteria Details about which context to delete comments for.
*/
public static function delete_comments_for_context(\core_privacy\local\request\deletion_criteria $criteria) {
global $DB;
$DB->delete_records('comments', ['contextid' => $criteria->get_context()->id]);
}
/**
* Deletes all records for a user from a list of approved contexts.
*
* @param \core_privacy\local\request\approved_contextlist $contextlist Contains the user ID and a list of contexts to be
* deleted from.
*/
public static function delete_comments_for_user_in_context(\core_privacy\local\request\approved_contextlist $contextlist) {
global $DB;
$userid = $contextlist->get_user()->id;
$contextids = implode(',', $contextlist->get_contextids());
$params = ['userid' => $userid];
list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$params += $inparams;
$select = "userid = :userid and contextid $insql";
$DB->delete_records_select('comments', $select, $params);
}
}
+192
View File
@@ -0,0 +1,192 @@
<?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 tests for core_comment.
*
* @package core_comment
* @category test
* @copyright 2018 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/comment/locallib.php');
require_once($CFG->dirroot . '/comment/lib.php');
use \core_privacy\tests\provider_testcase;
/**
* Unit tests for comment/classes/privacy/policy
*
* @copyright 2018 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_comment_privacy_testcase extends provider_testcase {
/**
* Check the exporting of comments for a user id in a context.
*/
public function test_export_comments() {
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
$comment = $this->get_comment_object($context, $course);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
// Add comments.
$comments = [];
$firstcomment = 'This is the first comment';
$this->setUser($user1);
$comment->add($firstcomment);
$comments[$user1->id] = $firstcomment;
$secondcomment = 'From the second user';
$this->setUser($user2);
$comment->add($secondcomment);
$comments[$user2->id] = $secondcomment;
// Retrieve comments only for user1.
$this->setUser($user1);
$writer = \core_privacy\local\request\writer::with_context($context);
\core_comment\privacy\provider::export_comments($context, 'block_comments', 'page_comments', 0, []);
$data = $writer->get_data([get_string('commentsubcontext', 'core_comment')]);
$exportedcomments = $data->comments;
// There is only one comment made by this user.
$this->assertCount(1, $exportedcomments);
$comment = reset($exportedcomments);
$this->assertEquals($comments[$user1->id], format_string($comment->content, FORMAT_PLAIN));
// Retrieve comments from any user.
\core_comment\privacy\provider::export_comments($context, 'block_comments', 'page_comments', 0, [], false);
$data = $writer->get_data([get_string('commentsubcontext', 'core_comment')]);
$exportedcomments = $data->comments;
// The whole conversation is two comments.
$this->assertCount(2, $exportedcomments);
foreach ($exportedcomments as $comment) {
$this->assertEquals($comments[$comment->userid], format_string($comment->content, FORMAT_PLAIN));
}
}
/**
* Tests the deletion of all comments in a context.
*/
public function test_delete_comments_for_context() {
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$coursecontext1 = context_course::instance($course1->id);
$coursecontext2 = context_course::instance($course2->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$comment1 = $this->get_comment_object($coursecontext1, $course1);
$comment2 = $this->get_comment_object($coursecontext2, $course2);
$this->setUser($user1);
$comment1->add('First comment for user 1 on comment 1');
$comment2->add('First comment for user 1 on comment 2');
$this->setUser($user2);
$comment1->add('First comment for user 2 on comment 1');
$comment2->add('First comment for user 2 on comment 2');
// Delete only for the first context. All records in the comments table for this context should be removed.
$deletioncriteria = new \core_privacy\local\request\deletion_criteria($coursecontext1);
\core_comment\privacy\provider::delete_comments_for_context($deletioncriteria);
// No records left here.
$this->assertCount(0, $comment1->get_comments());
// All of the records are left intact here.
$this->assertCount(2, $comment2->get_comments());
}
/**
* Tests deletion of comments for a specified user and contexts.
*/
public function test_delete_comments_for_user_in_context() {
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$coursecontext1 = context_course::instance($course1->id);
$coursecontext2 = context_course::instance($course2->id);
$coursecontext3 = context_course::instance($course3->id);
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$comment1 = $this->get_comment_object($coursecontext1, $course1);
$comment2 = $this->get_comment_object($coursecontext2, $course2);
$comment3 = $this->get_comment_object($coursecontext3, $course3);
$this->setUser($user1);
$comment1->add('First comment for user 1');
$comment2->add('User 1 comment in second comment');
$this->setUser($user2);
$comment2->add('User two replied in comment two');
$comment3->add('Comment three for user 2.');
// Delete the comments for user 1.
$approvedcontextlist = new core_privacy\tests\request\approved_contextlist($user1, 'block_comments',
[$coursecontext1->id, $coursecontext2->id]);
\core_comment\privacy\provider::delete_comments_for_user_in_context($approvedcontextlist);
// No comments left in comments 1 as only user 1 commented there.
$this->assertCount(0, $comment1->get_comments());
// Only user 2 comments left in comments 2.
$comment2comments = $comment2->get_comments();
$this->assertCount(1, $comment2comments);
$this->assertEquals($user2->id, $comment2comments[0]->userid);
// Nothing changed here as user 1 did not leave a comment.
$comment3comments = $comment3->get_comments();
$this->assertCount(1, $comment3comments);
$this->assertEquals($user2->id, $comment3comments[0]->userid);
}
/**
* Creates a comment object
*
* @param context $context A context object.
* @param stdClass $course A course object.
* @return comment The comment object.
*/
protected function get_comment_object($context, $course) {
// Comment on course page.
$args = new stdClass;
$args->context = $context;
$args->course = $course;
$args->area = 'page_comments';
$args->itemid = 0;
$args->component = 'block_comments';
$comment = new comment($args);
$comment->set_post_permission(true);
return $comment;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?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/>.
/**
* Strings for component 'moodle', language 'en', branch 'MOODLE_20_STABLE'
*
* @package core
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['commentsubcontext'] = 'Comments';
$string['privacy:metadata:comment'] = 'Stores comments of users.';
$string['privacy:metadata:comment:content'] = 'Stores the text of the comment.';
$string['privacy:metadata:comment:timecreated'] = 'Time a comment was created.';
$string['privacy:metadata:comment:userid'] = 'The user who made the comment.';
+1
View File
@@ -2072,6 +2072,7 @@ $string['userselectorpreserveselected'] = 'Keep selected users, even if they no
$string['userselectorsearchanywhere'] = 'Match the search text anywhere in the displayed fields';
$string['usersnew'] = 'New users';
$string['usersnoaccesssince'] = 'Inactive for more than';
$string['userpreferences'] = 'User preferences';
$string['userswithfiles'] = 'Users with files';
$string['useruploadtype'] = 'User upload type: {$a}';
$string['userzones'] = 'User zones';
+1
View File
@@ -29,3 +29,4 @@ $string['configenableplagiarism'] = 'This will allow administrators to configure
$string['manageplagiarism'] = 'Manage plagiarism plugins';
$string['nopluginsinstalled'] = 'No plagiarism plugins are installed.';
$string['plagiarism'] = 'Plagiarism';
$string['privacy:metadata:plagiarism'] = 'The plagiarism subsystem acts as a conduit, passing requets from plugins to the various plagiarism plugins.';
+25
View File
@@ -0,0 +1,25 @@
<?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/>.
/**
* Strings for component 'privacy', language 'en', branch 'master'
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['privacy:metadata'] = 'The privacy subsystem does not store any data of it\'s own and is designed to act as a conduit between components and the interface used to describe, export, and remove their data.';
+6 -1
View File
@@ -54,4 +54,9 @@ $string['ratingtime'] = 'Restrict ratings to items with dates in this range:';
$string['ratings'] = 'Ratings';
$string['rolewarning'] = 'Roles with permission to rate';
$string['rolewarning_help'] = 'To submit ratings users require the moodle/rating:rate capability and any module specific capabilities. Users assigned the following roles should be able to rate items. The list of roles may be amended via the permissions link in the administration block.';
$string['scaleselectionrequired'] = 'When selecting a ratings aggregate type you must also select to use either a scale or set a maximum points.';
$string['scaleselectionrequired'] = 'When selecting a ratings aggregate type you must also select to use either a scale or set a maximum points.';
$string['privacy:metadata:rating'] = 'The user-entered rating is stored alongside a mapping of the item which was rated.';
$string['privacy:metadata:rating:userid'] = 'The user who made the rating.';
$string['privacy:metadata:rating:rating'] = 'The numeric rating that the user entered.';
$string['privacy:metadata:rating:timecreated'] = 'The time that the rating was first made.';
$string['privacy:metadata:rating:timemodified'] = 'The time that the rating was last updated.';
+13
View File
@@ -92,6 +92,19 @@ $string['noresultsfor'] = 'No results for "{$a}"';
$string['nothingtoupdate'] = 'Nothing to update';
$string['owner'] = 'Owner';
$string['prevpage'] = 'Back';
$string['privacy:metadata:tag'] = 'The details of each unique tag are stored alongside their description and other related information';
$string['privacy:metadataetag:name'] = 'The name of the tag - this is the normalised version of the name.';
$string['privacy:metadata:tag:rawname'] = 'The name of the tag - this is the display name.';
$string['privacy:metadata:tag:description'] = 'The description of the tag.';
$string['privacy:metadata:tag:flag'] = 'Whether a tag has been flagged as inappropriate.';
$string['privacy:metadata:tag:timemodified'] = 'The last time that the tag was last modified.';
$string['privacy:metadata:tag:userid'] = 'The user who first created the tag.';
$string['privacy:metadata:taginstance'] = 'The link between each tag and where it is used.';
$string['privacy:metadata:taginstance:tagid'] = 'The link to the tag.';
$string['privacy:metadata:taginstance:ordering'] = 'The relative order of this tag.';
$string['privacy:metadata:taginstance:timecreated'] = 'The time that this tag was linked to the target.';
$string['privacy:metadata:taginstance:timemodified'] = 'The time that this tag was last modified for the target.';
$string['privacy:metadata:taginstance:tiuserid'] = 'Where shared content can be individually tagged by users, tThe owner of the tag instance is stored.';
$string['ptags'] = 'User defined tags (Comma separated)';
$string['relatedblogs'] = 'Most recent blog entries';
$string['relatedtags'] = 'Related tags';
+1
View File
@@ -470,6 +470,7 @@ $cache = '.var_export($cache, true).';
'plagiarism' => $CFG->dirroot.'/plagiarism',
'plugin' => null,
'portfolio' => $CFG->dirroot.'/portfolio',
'privacy' => $CFG->dirroot . '/privacy',
'publish' => $CFG->dirroot.'/course/publish',
'question' => $CFG->dirroot.'/question',
'rating' => $CFG->dirroot.'/rating',
+31
View File
@@ -7713,6 +7713,37 @@ function component_callback_exists($component, $function) {
return false;
}
/**
* Call the specified callback method on the provided class.
*
* If the callback returns null, then the default value is returned instead.
* If the class does not exist, then the default value is returned.
*
* @param string $classname The name of the class to call upon.
* @param string $methodname The name of the staticically defined method on the class.
* @param array $params The arguments to pass into the method.
* @param mixed $default The default value.
* @return mixed The return value.
*/
function component_class_callback($classname, $methodname, array $params, $default = null) {
if (!class_exists($classname)) {
return $default;
}
if (!method_exists($classname, $methodname)) {
return $default;
}
$fullfunction = $classname . '::' . $methodname;
$result = call_user_func_array($fullfunction, $params);
if (null === $result) {
return $default;
} else {
return $result;
}
}
/**
* Checks whether a plugin supports a specified feature.
*
+1 -1
View File
@@ -36,7 +36,7 @@ class core_component_testcase extends advanced_testcase {
* this is defined here to annoy devs that try to add more without any thinking,
* always verify that it does not collide with any existing add-on modules and subplugins!!!
*/
const SUBSYSTEMCOUNT = 67;
const SUBSYSTEMCOUNT = 68;
public function setUp() {
$psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
+61
View File
@@ -0,0 +1,61 @@
<?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/>.
/**
* Fixtures for component_class_callback tests.
*
* @package core
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class fixture for component_class_callback.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_component_class_callback_example {
/**
* Function which returns the input value.
*
* @param mixed $output
* @return mixed
*/
public static function method_returns_value($output) {
return $output;
}
/**
* Function which returns all args.
*
* @return mixed
*/
public static function method_returns_all_params() {
return count(func_get_args());
}
}
/**
* Class fixture for component_class_callback which extends another class.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_component_class_callback_child_example extends test_component_class_callback_example {
}
+126
View File
@@ -3592,4 +3592,130 @@ class core_moodlelib_testcase extends advanced_testcase {
$a = array('aggregatesonly' => [51, 34], 'gradesonly' => [21, 45, 78]);
$this->assertEquals($a, unserialize_array(serialize($a)));
}
/**
* Test that the component_class_callback returns the correct default value when the class was not found.
*
* @dataProvider component_class_callback_default_provider
* @param $default
*/
public function test_component_class_callback_not_found($default) {
$this->assertSame($default, component_class_callback('thisIsNotTheClassYouWereLookingFor', 'anymethod', [], $default));
}
/**
* Test that the component_class_callback returns the correct default value when the class was not found.
*
* @dataProvider component_class_callback_default_provider
* @param $default
*/
public function test_component_class_callback_method_not_found($default) {
require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
$this->assertSame($default, component_class_callback(test_component_class_callback_example::class, 'this_is_not_the_method_you_were_looking_for', ['abc'], $default));
}
/**
* Test that the component_class_callback returns the default when the method returned null.
*
* @dataProvider component_class_callback_default_provider
* @param $default
*/
public function test_component_class_callback_found_returns_null($default) {
require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
$this->assertSame($default, component_class_callback(test_component_class_callback_example::class, 'method_returns_value', [null], $default));
$this->assertSame($default, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_value', [null], $default));
}
/**
* Test that the component_class_callback returns the expected value and not the default when there was a value.
*
* @dataProvider component_class_callback_data_provider
* @param $default
*/
public function test_component_class_callback_found_returns_value($value) {
require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
$this->assertSame($value, component_class_callback(test_component_class_callback_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for'));
$this->assertSame($value, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for'));
}
/**
* Test that the component_class_callback handles multiple params correctly.
*
* @dataProvider component_class_callback_multiple_params_provider
* @param $default
*/
public function test_component_class_callback_found_accepts_multiple($params, $count) {
require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
$this->assertSame($count, component_class_callback(test_component_class_callback_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for'));
$this->assertSame($count, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for'));
}
/**
* Data provider with list of default values for user in component_class_callback tests.
*
* @return array
*/
public function component_class_callback_default_provider() {
return [
'null' => [null],
'empty string' => [''],
'string' => ['This is a string'],
'int' => [12345],
'stdClass' => [(object) ['this is my content']],
'array' => [['a' => 'b',]],
];
}
/**
* Data provider with list of default values for user in component_class_callback tests.
*
* @return array
*/
public function component_class_callback_data_provider() {
return [
'empty string' => [''],
'string' => ['This is a string'],
'int' => [12345],
'stdClass' => [(object) ['this is my content']],
'array' => [['a' => 'b',]],
];
}
/**
* Data provider with list of default values for user in component_class_callback tests.
*
* @return array
*/
public function component_class_callback_multiple_params_provider() {
return [
'empty array' => [
[],
0,
],
'string value' => [
['one'],
1,
],
'string values' => [
['one', 'two'],
2,
],
'arrays' => [
[[], []],
2,
],
'nulls' => [
[null, null, null, null],
4,
],
'mixed' => [
['a', 1, null, (object) [], []],
5,
],
];
}
}
+3
View File
@@ -93,6 +93,9 @@
<testsuite name="core_course_testsuite">
<directory suffix="_test.php">course/tests</directory>
</testsuite>
<testsuite name="core_privacy_testsuite">
<directory suffix="_test.php">privacy/tests</directory>
</testsuite>
<testsuite name="core_question_testsuite">
<directory suffix="_test.php">question/engine/tests</directory>
<directory suffix="_test.php">question/tests</directory>
@@ -0,0 +1,66 @@
<?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 class for requesting user data.
*
* @package core_plagiarism
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_comment\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Provider for the plagiarism API.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface plagiarism_provider extends
// The plagiarism_provider should be implemented by plugins which only provide information to a subsystem.
\core_privacy\local\request\plugin\subsystem_provider,
// All plagiarism plugins should also implement the metadata provider.
\core_privacy\local\metadata\provider {
/**
* Export all plagiarism data from each plagiarism plugin for the specified userid and context.
*
* @param int $userid The user to export.
* @param \context $context The context to export.
* @param array $subcontext The subcontext within the context to export this information to.
* @param array $linkarray The weird and wonderful link array used to display information for a specific item
*/
public static function export_plagiarism_user_data(int $userid, \context $context, array $subcontext, array $linkarray);
/**
* Delete all user information for the provided context.
*
* @param \context $context The context to delete user data for.
*/
public static function delete_plagiarism_for_context(\context $context);
/**
* Delete all user information for the provided user and context.
*
* @param int $userid The user to delete
* @param \context $context The context to refine the deletion.
*/
public static function delete_plagiarism_for_user(int $userid, \context $context);
}
+123
View File
@@ -0,0 +1,123 @@
<?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 class for requesting user data.
*
* @package core_plagiarism
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_plagiarism\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
/**
* Provider for the plagiarism API.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// The Plagiarism subsystem does not store any data itself.
// It has no database tables, and it purely acts as a conduit to the various plagiarism plugins.
\core_privacy\local\metadata\provider,
// The Plagiarism subsystem will be called by other components and will forward requests to each plagiarism plugin implementing its APIs.
\core_privacy\local\request\subsystem\plugin_provider {
/**
* Returns meta data about this system.
*
* @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_metadata(collection $collection) : collection {
$collection->link_plugintype('plagiarism', 'privacy:metadata:plagiarism');
return $collection;
}
/**
* Export all plagiarism data from each plagiarism plugin for the specified userid and context.
*
* @param int $userid The user to export.
* @param \context $context The context to export.
* @param array $subcontext The subcontext within the context to export this information to.
* @param array $linkarray The weird and wonderful link array used to display information for a specific item
*/
public static function export_plagiarism_user_data(int $userid, \context $context, array $subcontext, array $linkarray) {
static::call_plugin_method('export_plagiarism_user_data', [$userid, $context, $subcontext, $linkarray]);
}
/**
* Checks whether the component's provider class implements the specified interface.
* This can either be implemented directly, or by implementing a descendant (extension) of the specified interface.
*
* @param string $component the frankenstyle component name.
* @param string $interface the name of the interface we want to check.
* @return bool True if an implementation was found, false otherwise.
*/
protected static function component_implements(string $providerclass, string $interface) : bool {
if (class_exists($providerclass)) {
$rc = new \ReflectionClass($providerclass);
return $rc->implementsInterface($interface);
}
return false;
}
/**
* Deletes all user content for a context in all plagiarism plugins.
*
* @param \context $context The context to delete user data for.
*/
public static function delete_plagiarism_for_context(\context $context) {
static::call_plugin_method('delete_plagiarism_for_context', [$context]);
}
/**
* Deletes all user content for a user in a context in all plagiarism plugins.
*
* @param int $userid The user to delete
* @param \context $context The context to refine the deletion.
*/
public static function delete_plagiarism_for_user(int $userid, \context $context) {
static::call_plugin_method('delete_plagiarism_for_user', [$userid, $context]);
}
/**
* Internal method for looping through all of the plagiarism plugins and calling a method.
*
* @param string $methodname Name of the method to call on the plugins.
* @param array $params The parameters that go with the method being called.
*/
protected static function call_plugin_method($methodname, $params) {
// Note: Even if plagiarism is _now_ disabled, there may be legacy data to export.
$plugins = \core_component::get_plugin_list('plagiarism');
foreach (array_keys($plugins) as $plugin) {
$component = "plagiarism_{$plugin}";
$classname = manager::get_provider_classname_for_component($component);
if (static::component_implements($classname, plagiarism_provider::class)) {
// This plagiarism plugin implements the plagiarism_provider.
component_class_callback($classname, $methodname, $params);
}
}
}
}
+106
View File
@@ -0,0 +1,106 @@
<?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/>.
/**
* This file contains the polyfill to allow a plugin to operate with Moodle 3.3 up.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local;
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\deletion_criteria;
defined('MOODLE_INTERNAL') || die();
/**
* The trait used to provide a backwards compatability for third-party plugins.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait legacy_polyfill {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return static::_get_reason();
}
/**
* Get the list of items.
*
* @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_metadata(collection $collection) : collection {
return static::_get_metadata($collection);
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
return static::_export_user_preferences($userid);
}
/**
* 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 static::_get_contexts_for_userid($userid);
}
/**
* 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) {
return static::_export_user_data($contextlist);
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param deletion_criteria $criteria An object containing specific deletion criteria to delete for.
*/
public static function delete_for_context(deletion_criteria $criteria) {
return static::_delete_for_context($criteria);
}
/**
* 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_user_data(approved_contextlist $contextlist) {
return static::_delete_user_data($contextlist);
}
}
@@ -0,0 +1,160 @@
<?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/>.
/**
* This file defines the core_privacy\local\metadata\collection class object.
*
* The collection class is used to organize a collection of types
* objects, which contains the privacy field details of a component.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata;
use core_privacy\local\metadata\types\type;
defined('MOODLE_INTERNAL') || die();
/**
* A collection of metadata items.
*
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class collection {
/**
* @var string The component that the items in the collection belong to.
*/
protected $component;
/**
* @var array The collection of metadata items.
*/
protected $collection = [];
/**
* Constructor for a component's privacy collection class.
*
* @param string $component component name.
*/
public function __construct($component) {
$this->component = $component;
}
/**
* Function to add an object that implements type interface to the current collection.
*
* @param type $type to add to collection.
* @return $this
*/
public function add_type(type $type) {
$this->collection[] = $type;
return $this;
}
/**
* Function to add a database table which contains user data to this collection.
*
* @param string $name the name of the database table.
* @param array $privacyfields An associative array of fieldname to description.
* @param string $summary A description of what the table is used for.
* @return $this
*/
public function add_database_table($name, array $privacyfields, $summary = '') {
$this->add_type(new types\database_table($name, $privacyfields, $summary));
return $this;
}
/**
* Function to link a subsystem to the component.
*
* @param string $name the name of the subsystem to link.
* @param string $summary A description of what is stored within this subsystem.
* @return $this
*/
public function link_subsystem($name, $summary = '') {
$this->add_type(new types\subsystem_link($name, $summary));
return $this;
}
/**
* Function to link a plugin to the component.
*
* @param string $name the name of the plugin to link.
* @param string $summary A description of what tis stored within this plugin.
* @return $this
*/
public function link_plugintype($name, $summary = '') {
$this->add_type(new types\plugintype_link($name, $summary));
return $this;
}
/**
* Function to indicate that data may be exported to an external location.
*
* @param string $name A name for the type of data exported.
* @param array $privacyfields A list of fields with their description.
* @param string $summary A description of what the table is used for. This is a language string identifier
* within the component.
* @return $this
*/
public function link_external_location($name, array $privacyfields, $summary = '') {
$this->add_type(new types\external_location($name, $privacyfields, $summary));
return $this;
}
/**
* Add a type of user preference to the collection.
*
* Typically this is a single user preference, but in some cases the
* name of a user preference fits a particular format.
*
* @param string $name The name of the user preference.
* @param string $summary A description of what the preference is used for.
* @return $this
*/
public function add_user_preference($name, $summary = '') {
$this->add_type(new types\user_preference($name, $summary));
return $this;
}
/**
* Function to return the current component name.
*
* @return string
*/
public function get_component() {
return $this->component;
}
/**
* The content of this collection.
*
* @return types\type[]
*/
public function get_collection() {
return $this->collection;
}
}
@@ -0,0 +1,40 @@
<?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/>.
/**
* This file contains the core_privacy\nodata interface.
*
* Plugins implement this interface to declare that they don't store any personal information.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata;
defined('MOODLE_INTERNAL') || die();
interface null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string ;
}
@@ -0,0 +1,43 @@
<?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/>.
/**
* INterface for main metadata provider interface.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata;
defined('MOODLE_INTERNAL') || die();
/**
* INterface for main metadata provider interface.
*
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface provider {
/**
* Returns meta data about this system.
*
* @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_metadata(collection $collection) : collection ;
}
@@ -0,0 +1,110 @@
<?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/>.
/**
* This file defines an item of metadata which encapsulates a database table.
*
* @package core_privacy
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The database_table type.
*
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class database_table implements type {
/**
* @var string Database table name.
*/
protected $name;
/**
* @var array Fields which contain user information within the table.
*/
protected $privacyfields;
/**
* @var string A description of what this table is used for.
*/
protected $summary;
/**
* Constructor to create a new database_table type.
*
* @param string $name The name of the database table being described.
* @param array $privacyfields A list of fields with their description.
* @param string $summary A description of what the table is used for.
*/
public function __construct($name, array $privacyfields = [], $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
if (empty($privacyfields)) {
debugging("Table '{$name}' was supplied without any fields.", DEBUG_DEVELOPER);
}
foreach ($privacyfields as $key => $field) {
$teststring = clean_param($field, PARAM_STRINGID);
if ($teststring !== $field) {
debugging("Field '{$key}' passed for table '{$name}' has an invalid langstring identifier: '{$field}'",
DEBUG_DEVELOPER);
}
}
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for the '{$name}' table has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->privacyfields = $privacyfields;
$this->summary = $summary;
}
/**
* The name of the database table.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* The list of fields within the table which contain user data, with a description of each field.
*
* @return array
*/
public function get_privacy_fields() {
return $this->privacyfields;
}
/**
* A summary of what this table is used for.
*
* @return string
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,112 @@
<?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/>.
/**
* This file defines an item of metadata which encapsulates data which is exported to an external location.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The external_location type.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_location implements type {
/**
* @var string The name to describe the type of information exported.
*/
protected $name;
/**
* @var array The list of data names and descriptions exported.
*/
protected $privacyfields;
/**
* @var string A description of what this table is used for.
* This is a language string identifier.
*/
protected $summary;
/**
* Constructor to create a new external_location type.
*
* @param string $name A name for the type of data exported.
* @param array $privacyfields A list of fields with their description.
* @param string $summary A description of what the table is used for. This is a language string identifier
* within the component.
*/
public function __construct($name, array $privacyfields = [], $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
if (empty($privacyfields)) {
debugging("Location '{$name}' was supplied without any fields.", DEBUG_DEVELOPER);
}
foreach ($privacyfields as $key => $field) {
$teststring = clean_param($field, PARAM_STRINGID);
if ($teststring !== $field) {
debugging("Field '{$key}' passed for location '{$name}' has an invalid langstring identifier: '{$field}'",
DEBUG_DEVELOPER);
}
}
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for the '{$name}' location has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->privacyfields = $privacyfields;
$this->summary = $summary;
}
/**
* The name to describe the type of information exported.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* Get the list of fields which contain user data, with a description of each field.
*
* @return array
*/
public function get_privacy_fields() {
return $this->privacyfields;
}
/**
* A summary of what this type of exported data is used for.
*
* @return string
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,92 @@
<?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/>.
/**
* This file defines a link to another Moodle plugin.
*
* @package core_privacy
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The plugintype link.
*
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugintype_link implements type {
/**
* @var The name of the core plugintype to link.
*/
protected $name;
/**
* @var string A description of what this plugintype is used to store.
*/
protected $summary;
/**
* Constructor for the plugintype_link.
*
* @param string $name The name of the plugintype to link.
* @param string $summary A description of what is stored within this plugintype.
*/
public function __construct($name, $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for use of the '{$name}' plugintype " .
"has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->summary = $summary;
}
/**
* Function to return the name of this plugintype_link type.
*
* @return string $name
*/
public function get_name() {
return $this->name;
}
/**
* A plugintype link does not define any fields itself.
*
* @return array
*/
public function get_privacy_fields() : array {
return null;
}
/**
* A summary of what this plugintype is used for.
*
* @return string $summary
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,92 @@
<?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/>.
/**
* This file defines a link to another Moodle subsystem.
*
* @package core_privacy
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The subsystem link type.
*
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class subsystem_link implements type {
/**
* @var The name of the core subsystem to link.
*/
protected $name;
/**
* @var string A description of what this subsystem is used to store.
*/
protected $summary;
/**
* Constructor for the subsystem_link.
*
* @param string $name The name of the subsystem to link.
* @param string $summary A description of what is stored within this subsystem.
*/
public function __construct($name, $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for use of the '{$name}' subsystem " .
"has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->summary = $summary;
}
/**
* Function to return the name of this subsystem_link type.
*
* @return string $name
*/
public function get_name() {
return $this->name;
}
/**
* A subsystem link does not define any fields itself.
*
* @return array
*/
public function get_privacy_fields() : array {
return null;
}
/**
* A summary of what this subsystem is used for.
*
* @return string $summary
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,56 @@
<?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/>.
/**
* The base type interface which encapsulates a set of data held by a component with Moodle.
*
* @package core_privacy
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The base type interface which all metadata types must implement.
*
* @copyright 2018 Zig Tan <[email protected]>
* @package core_privacy
*/
interface type {
/**
* Get the name describing this type.
*
* @return string
*/
public function get_name();
/**
* A list of the fields and their usage description.
*
* @return array
*/
public function get_privacy_fields();
/**
* A summary of what the metalink type is used for.
*
* @return string $summary
*/
public function get_summary();
}
@@ -0,0 +1,93 @@
<?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/>.
/**
* This file defines an item of metadata which encapsulates a user's preferences.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\metadata\types;
defined('MOODLE_INTERNAL') || die();
/**
* The user_preference type.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_preference implements type {
/**
* @var The name of this user preference.
*/
protected $name;
/**
* @var A description of what this user preference means.
*/
protected $summary;
/**
* Constructor to create a new user_preference types.
*
* @param string $name The name of the user preference.
* @param string $summary A description of what the preference is used for.
*/
public function __construct($name, $summary = '') {
if (debugging('', DEBUG_DEVELOPER)) {
$teststring = clean_param($summary, PARAM_STRINGID);
if ($teststring !== $summary) {
debugging("Summary information for use of the '{$name}' subsystem " .
" has an invalid langstring identifier: '{$summary}'",
DEBUG_DEVELOPER);
}
}
$this->name = $name;
$this->summary = $summary;
}
/**
* The name of the user preference.
*
* @return string
*/
public function get_name() {
return $this->name;
}
/**
* A user preference encapsulates a single field and has no sub-fields.
*
* @return array
*/
public function get_privacy_fields() {
return null;
}
/**
* A summary of what this user preference is used for.
*
* @return string
*/
public function get_summary() {
return $this->summary;
}
}
@@ -0,0 +1,75 @@
<?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/>.
/**
* An implementation of a contextlist which has been filtered and approved.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* An implementation of a contextlist which has been filtered and approved.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class approved_contextlist extends contextlist_base {
/**
* @var \stdClass The user this contextlist belongs to.
*/
protected $user;
/**
* Create a new approved contextlist.
*
* @param \stdClass $user The user record.
* @param string $component the frankenstyle component name.
* @param \int[] $contextids The list of contextids present in this list.
*/
public function __construct(\stdClass $user, string $component, array $contextids) {
$this->set_user($user);
$this->set_component($component);
$this->set_contextids($contextids);
}
/**
* Specify the user which owns this request.
*
* @param \stdClass $user The user record.
* @return $this
*/
protected function set_user(\stdClass $user) : approved_contextlist {
$this->user = $user;
return $this;
}
/**
* Get the user which requested their data.
*
* @return \stdClass
*/
public function get_user() : \stdClass {
return $this->user;
}
}
@@ -0,0 +1,143 @@
<?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/>.
/**
* This file contains the interface required to implmeent a content writer.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The interface for a Moodle content writer.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*/
interface content_writer {
/**
* Constructor for the content writer.
*
* Note: The writer_factory must be passed.
* @param writer $writer The factory.
*/
public function __construct(writer $writer);
/**
* Set the context for the current item being processed.
*
* @param \context $context The context to use
* @return content_writer
*/
public function set_context(\context $context) : content_writer ;
/**
* Export the supplied data within the current context, at the supplied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stdClass $data The data to be exported
* @return content_writer
*/
public function export_data(array $subcontext, \stdClass $data) : content_writer ;
/**
* Export metadata about the supplied subcontext.
*
* Metadata consists of a key/value pair and a description of the value.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The metadata name.
* @param string $value The metadata value.
* @param string $description The description of the value.
* @return content_writer
*/
public function export_metadata(array $subcontext, string $name, $value, string $description) : content_writer ;
/**
* Export a piece of related data.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The name of the file to be exported.
* @param \stdClass $data The related data to export.
* @return content_writer
*/
public function export_related_data(array $subcontext, $name, $data) : content_writer ;
/**
* Export a piece of data in a custom format.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be exported.
* @param string $filecontent The content to be exported.
* @return content_writer
*/
public function export_custom_file(array $subcontext, $filename, $filecontent) : content_writer ;
/**
* Prepare a text area by processing pluginfile URLs within it.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
* @param string $text The text to be processed
* @return string The processed string
*/
public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text) : string;
/**
* Export all files within the specified component, filearea, itemid combination.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
* @return content_writer
*/
public function export_area_files(array $subcontext, $component, $filearea, $itemid) : content_writer ;
/**
* Export the specified file in the target location.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stored_file $file The file to be exported.
* @return content_writer
*/
public function export_file(array $subcontext, \stored_file $file) : content_writer ;
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return content_writer
*/
public function export_user_preference(string $component, string $key, string $value, string $description) : content_writer ;
/**
* Perform any required finalisation steps and return the location of the finalised export.
*
* @return string
*/
public function finalise_content() : string ;
}
@@ -0,0 +1,72 @@
<?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 Fetch Result Set.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Fetch Result Set.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist extends contextlist_base {
/**
* Add a set of contexts from SQL.
*
* The SQL should only return a list of context IDs.
*
* @param string $sql The SQL which will fetch the list of * context IDs
* @param array $params The set of SQL parameters
* @return $this
*/
public function add_from_sql(string $sql, array $params) : contextlist {
global $DB;
$fields = \context_helper::get_preload_record_columns_sql('ctx');
$wrapper = "SELECT {$fields} FROM {context} ctx WHERE id IN ({$sql})";
$contexts = $DB->get_recordset_sql($wrapper, $params);
$contextids = [];
foreach ($contexts as $context) {
$contextids[] = $context->ctxid;
\context_helper::preload_from_record($context);
}
$this->set_contextids(array_merge($this->get_contextids(), $contextids));
return $this;
}
/**
* Sets the component for this contextlist.
*
* @param string $component the frankenstyle component name.
*/
public function set_component($component) {
parent::set_component($component);
}
}
@@ -0,0 +1,161 @@
<?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/>.
/**
* Base implementation of a contextlist.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* Base implementation of a contextlist used to store a set of contexts.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class contextlist_base implements
// Implement an Iterator to fetch the Context objects.
\Iterator,
// Implement the Countable interface to allow the number of returned results to be queried easily.
\Countable {
/**
* @var array List of context IDs.
*
* Note: this must not be updated using set_contextids only as this
* ensures uniqueness.
*/
private $contextids = [];
/**
* @var string component the frankenstyle component name.
*/
protected $component = '';
/**
* @var int Current position of the iterator.
*/
protected $iteratorposition = 0;
/**
* Set the contextids.
*
* @param array $contextids The list of contexts.
*/
protected function set_contextids(array $contextids) {
$this->contextids = array_unique($contextids);
}
/**
* Get the list of context IDs that relate to this request.
*
* @return int[]
*/
public function get_contextids() : array {
return $this->contextids;
}
/**
* Get the complete list of context objects that relate to this
* request.
*
* @return \contect[]
*/
public function get_contexts() : array {
$contexts = [];
foreach ($this->contextids as $contextid) {
$contexts[] = \context::instance_by_id($contextid);
}
return $contexts;
}
/**
* Sets the component for this contextlist.
*
* @param string $component the frankenstyle component name.
*/
protected function set_component($component) {
$this->component = $component;
}
/**
* Get the name of the component to which this contextlist belongs.
*
* @return string the component name associated with this contextlist.
*/
public function get_component() : string {
return $this->component;
}
/**
* Return the current context.
*
* @return \context
*/
public function current() {
return \context::instance_by_id($this->contextids[$this->iteratorposition]);
}
/**
* Return the key of the current element.
*
* @return mixed
*/
public function key() {
return $this->iteratorposition;
}
/**
* Move to the next context in the list.
*/
public function next() {
++$this->iteratorposition;
}
/**
* Check if the current position is valid.
*
* @return bool
*/
public function valid() {
return isset($this->contextids[$this->iteratorposition]);
}
/**
* Rewind to the first found context.
*
* The list of contexts is uniqued during the rewind.
* The rewind is called at the start of most iterations.
*/
public function rewind() {
$this->iteratorposition = 0;
}
/**
* Return the number of contexts.
*/
public function count() {
return count($this->contextids);
}
}
@@ -0,0 +1,180 @@
<?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/>.
/**
* This file defines the contextlist_collection class object.
*
* The contextlist_collection is used to organize a collection of contextlists.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* A collection of contextlist items.
*
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist_collection implements \Iterator, \Countable {
/**
* @var int $userid The ID of the user that the contextlist collection belongs to.
*/
protected $userid = null;
/**
* @var array $contextlists the internal array of contextlist objects.
*/
protected $contextlists = [];
/**
* @var int Current position of the iterator.
*/
protected $iteratorposition = 0;
/**
* Constructor to create a new contextlist_collection.
*
* @param int $userid The userid to which this collection belongs.
*/
public function __construct($userid) {
$this->userid = $userid;
}
/**
* Return the ID of the user whose collection this is.
*
* @return int
*/
public function get_userid() : int {
return $this->userid;
}
/**
* Add a contextlist to this collection.
*
* @param contextlist_base $contextlist the contextlist to export.
* @return $this
*/
public function add_contextlist(contextlist_base $contextlist) {
$component = $contextlist->get_component();
if (empty($component)) {
throw new \moodle_exception("The contextlist must have a component set");
}
if (isset($this->contextlists[$component])) {
throw new \moodle_exception("A contextlist has already been added for the '{$component}' component");
}
$this->contextlists[$component] = $contextlist;
return $this;
}
/**
* Get the contextlists in this collection.
*
* @return array the associative array of contextlists in this collection, indexed by component name.
* E.g. mod_assign => contextlist, core_comment => contextlist.
*/
public function get_contextlists() : array {
return $this->contextlists;
}
/**
* Get the contextlist for the specified component.
*
* @param string $component the frankenstyle name of the component to fetch for.
* @return contextlist_base|null
*/
public function get_contextlist_for_component(string $component) {
if (isset($this->contextlists[$component])) {
return $this->contextlists[$component];
}
return null;
}
/**
* Return the current contexlist.
*
* @return \context
*/
public function current() {
$key = $this->get_key_from_position();
return $this->contextlists[$key];
}
/**
* Return the key of the current element.
*
* @return mixed
*/
public function key() {
return $this->get_key_from_position();
}
/**
* Move to the next context in the list.
*/
public function next() {
++$this->iteratorposition;
}
/**
* Check if the current position is valid.
*
* @return bool
*/
public function valid() {
return ($this->iteratorposition < count($this->contextlists));
}
/**
* Rewind to the first found context.
*
* The list of contexts is uniqued during the rewind.
* The rewind is called at the start of most iterations.
*/
public function rewind() {
$this->iteratorposition = 0;
}
/**
* Get the key for the current iterator position.
*
* @return string
*/
protected function get_key_from_position() {
$keylist = array_keys($this->contextlists);
if (isset($keylist[$this->iteratorposition])) {
return $keylist[$this->iteratorposition];
}
return null;
}
/**
* Return the number of contexts.
*/
public function count() {
return count($this->contextlists);
}
}
@@ -0,0 +1,44 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\core_data_provider interface to describe
* classes which provide data in some form to core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The core_data_provider interface is used to describe a provider which
* services user requests between components and core.
*
* It does not define a specific way of doing so and different types of
* data will need to extend this interface in order to define their own
* contract.
*
* It should not be implemented directly, but should be extended by other
* interfaces in core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*/
interface core_data_provider extends data_provider {
}
@@ -0,0 +1,69 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\core_user_data_provider interface to describe
* classes which provide user data in some form to core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The core_user_data_provider interface is used to describe a provider
* which services user requests between components and core.
*
* It describes data how these requests are serviced in a specific format.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*/
interface core_user_data_provider extends core_data_provider {
/**
* 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;
/**
* 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 use data which matches the specified deletion_criteria.
*
* @param deletion_criteria $criteria An object containing specific deletion criteria to delete for.
*/
public static function delete_for_context(deletion_criteria $criteria);
/**
* 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_user_data(approved_contextlist $contextlist);
}
@@ -0,0 +1,49 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\data_provider interface to describe
* a class which provides data in some form.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The data_provider interface is used to describe a provider
* which services user requests in any fashion. This includes both
* -- component <-> core; and
* -- component <-> component.
*
* It does not define a specific way of doing so and different types of
* data will need to extend this interface in order to define their own
* contract.
*
* It should not be implemented directly, but should be extended by other
* interfaces in core.
*
* This is the base interface for any component which stores any form of
* user data.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*/
interface data_provider {
}
@@ -0,0 +1,58 @@
<?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/>.
/**
* The \core_privacy\local\request\deletion_criteria class.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The deletion_criteria class is used to describe conditions for a set of
* data due to be deleted.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class deletion_criteria {
/**
* @var context The context being deleted.
*/
protected $context = null;
/**
* Constructor for a new deletion_criteria.
*
* @param \context $context The context being deleted.
*/
public function __construct(\context $context) {
$this->context = $context;
}
/**
* Get the context to be deleted.
*
* @return \context
*/
public function get_context() : \context {
return $this->context;
}
}
+301
View File
@@ -0,0 +1,301 @@
<?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/>.
/**
* This file contains the core_privacy\local\request helper.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
use \core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/modinfolib.php');
require_once($CFG->dirroot . '/course/modlib.php');
/**
* The core_privacy\local\request\helper class with useful shared functionality.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Add core-controlled contexts which are related to a component but that component may know about.
*
* For example, most activities are not aware of activity completion, but the course implements it for them.
* These should be included.
*
* @param int $userid The user being added for.
* @param contextlist $contextlist The contextlist being appended to.
* @return contextlist The final contextlist
*/
public static function add_shared_contexts_to_contextlist_for(int $userid, contextlist $contextlist) : contextlist {
if (strpos($contextlist->get_component(), 'mod_') === 0) {
// Activity modules support data stored by core about them - for example, activity completion.
$contextlist = static::add_shared_contexts_to_contextlist_for_course_module($userid, $contextlist);
}
return $contextlist;
}
/**
* Handle export of standard data for a plugin which implements the null provider and does not normally store data
* of its own.
*
* This is used in cases such as activities like mod_resource, which do not store their own data, but may still have
* data on them (like Activity Completion).
*
* Any context provided in a contextlist should have base data exported as a minimum.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_data_for_null_provider(approved_contextlist $contextlist) {
$user = $contextlist->get_user();
foreach ($contextlist as $context) {
$data = static::get_context_data($context, $user);
static::export_context_files($context, $user);
writer::with_context($context)->export_data([], $data);
}
}
/**
* Handle removal of 'standard' data for any plugin.
*
* This will handle deletion for things such as activity completion.
*
* @param string $component The component being deleted for.
* @param deletion_criteria $criteria An object containing specific deletion criteria to delete for.
*/
public static function delete_for_context(string $component, deletion_criteria $criteria) {
if (strpos($component, 'mod_') === 0) {
// Activity modules support data stored by core about them - for example, activity completion.
static::delete_for_context_course_module($component, $criteria->get_context());
}
}
/**
* Delete all 'standard' user data for the specified user, in the specified contexts.
*
* This will handle deletion for things such as activity completion.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_user_data(approved_contextlist $contextlist) {
$component = $contextlist->get_component();
if (strpos($component, 'mod_') === 0) {
// Activity modules support data stored by core about them - for example, activity completion.
static::delete_user_data_for_course_module($contextlist);
}
}
/**
* Get all general data for this context.
*
* @param \context $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
public static function get_context_data(\context $context, \stdClass $user) : \stdClass {
global $DB;
$basedata = (object) [];
if ($context instanceof \context_module) {
return static::get_context_module_data($context, $user);
}
if ($context instanceof \context_block) {
return static::get_context_block_data($context, $user);
}
return $basedata;
}
/**
* Export all files for this context.
*
* @param \context $context The context to export files for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
public static function export_context_files(\context $context, \stdClass $user) {
if ($context instanceof \context_module) {
return static::export_context_module_files($context, $user);
}
}
/**
* Add core-controlled contexts which are related to a component but that component may know about.
*
* For example, most activities are not aware of activity completion, but the course implements it for them.
* These should be included.
*
* @param int $userid The user being added for.
* @param contextlist $contextlist The contextlist being appended to.
* @return contextlist The final contextlist
*/
protected static function add_shared_contexts_to_contextlist_for_course_module(int $userid, contextlist $contextlist) : contextlist {
// Fetch all contexts where the user has activity completion enabled.
$sql = "SELECT
c.id
FROM {course_modules_completion} cmp
INNER JOIN {course_modules} cm ON cm.id = cmp.coursemoduleid
INNER JOIN {modules} m ON m.id = cm.module
INNER JOIN {context} c ON c.instanceid = cm.id AND c.contextlevel = :contextlevel
WHERE cmp.userid = :userid
AND m.name = :modname";
$params = [
'userid' => $userid,
// Strip the mod_ from the name.
'modname' => substr($contextlist->get_component(), 4),
'contextlevel' => CONTEXT_MODULE,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get all general data for the activity module at this context.
*
* @param \context_module $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
protected static function get_context_module_data(\context_module $context, \stdClass $user) : \stdClass {
global $DB;
$coursecontext = $context->get_course_context();
$modinfo = get_fast_modinfo($coursecontext->instanceid);
$cm = $modinfo->cms[$context->instanceid];
$component = "mod_{$cm->modname}";
$course = $cm->get_course();
$moduledata = $DB->get_record($cm->modname, ['id' => $cm->instance]);
$basedata = (object) [
'name' => $cm->get_formatted_name(),
];
if (plugin_supports('mod', $cm->modname, FEATURE_MOD_INTRO, true)) {
$intro = $moduledata->intro;
$intro = writer::with_context($context)
->rewrite_pluginfile_urls([], $component, 'intro', 0, $intro);
$options = [
'noclean' => true,
'para' => false,
'context' => $context,
'overflowdiv' => true,
];
$basedata->intro = format_text($intro, $moduledata->introformat, $options);
}
// Completion tracking.
$completioninfo = new \completion_info($course);
$completion = $completioninfo->is_enabled($cm);
if ($completion != COMPLETION_TRACKING_NONE) {
$completiondata = $completioninfo->get_data($cm, true, $user->id);
$basedata->completion = (object) [
'state' => $completiondata->completionstate,
];
}
return $basedata;
}
/**
* Get all general data for the block at this context.
*
* @param \context_block $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass General data about this block instance.
*/
protected static function get_context_block_data(\context_block $context, \stdClass $user) : \stdClass {
global $DB;
$block = $DB->get_record('block_instances', ['id' => $context->instanceid]);
$basedata = (object) [
'blocktype' => get_string('pluginname', 'block_' . $block->blockname)
];
return $basedata;
}
/**
* Get all general data for the activity module at this context.
*
* @param \context_module $context The context to retrieve data for.
* @param \stdClass $user The user being written.
* @return \stdClass
*/
protected static function export_context_module_files(\context_module $context, \stdClass $user) {
$coursecontext = $context->get_course_context();
$modinfo = get_fast_modinfo($coursecontext->instanceid);
$cm = $modinfo->cms[$context->instanceid];
$component = "mod_{$cm->modname}";
writer::with_context($context)
// Export the files for the intro.
->export_area_files([], $component, 'intro', 0);
}
/**
* Handle removal of 'standard' data for course modules.
*
* This will handle deletion for things such as activity completion.
*
* @param string $component The component being deleted for.
* @param \context_module $context The context to delete all data for.
*/
public static function delete_for_context_course_module(string $component, \context_module $context) {
global $DB;
// Delete course completion data for this context.
$DB->delete_records('course_modules_completion', ['coursemoduleid' => $context->instanceid]);
}
/**
* Delete all 'standard' user data for the specified user in course modules.
*
* This will handle deletion for things such as activity completion.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
protected static function delete_user_data_for_course_module(approved_contextlist $contextlist) {
global $DB;
foreach ($contextlist as $context) {
// Delete course completion data for this context.
$DB->delete_records('course_modules_completion', [
'coursemoduleid' => $context->instanceid,
'userid' => $contextlist->get_user()->id,
]);
}
}
}
@@ -0,0 +1,320 @@
<?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/>.
/**
* This file contains the moodle format implementation of the content writer.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The moodle_content_writer is the default Moodle implementation of a content writer.
*
* It exports data to a rich tree structure using Moodle's context system,
* and produces a single zip file with all content.
*
* Objects of data are stored as JSON.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodle_content_writer implements content_writer {
/**
* @var string The base path on disk for this instance.
*/
protected $path = null;
/**
* @var \context The current context of the writer.
*/
protected $context = null;
/**
* @var \stored_file[] The list of files to be exported.
*/
protected $files = [];
/**
* Constructor for the content writer.
*
* Note: The writer factory must be passed.
*
* @param writer $writer The factory.
*/
public function __construct(writer $writer) {
$this->path = make_request_directory();
}
/**
* Set the context for the current item being processed.
*
* @param \context $context The context to use
*/
public function set_context(\context $context) : content_writer {
$this->context = $context;
return $this;
}
/**
* Export the supplied data within the current context, at the supplied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stdClass $data The data to be exported
*/
public function export_data(array $subcontext, \stdClass $data) : content_writer {
$path = $this->get_path($subcontext, 'data.json');
$this->write_data($path, json_encode($data));
return $this;
}
/**
* Export metadata about the supplied subcontext.
*
* Metadata consists of a key/value pair and a description of the value.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $key The metadata name.
* @param string $value The metadata value.
* @param string $description The description of the value.
*/
public function export_metadata(array $subcontext, string $key, $value, string $description) : content_writer {
$path = $this->get_full_path($subcontext, 'metadata.json');
if (file_exists($path)) {
$data = json_decode(file_get_contents($path));
} else {
$data = (object) [];
}
$data->$key = (object) [
'value' => $value,
'description' => $description,
];
$path = $this->get_path($subcontext, 'metadata.json');
$this->write_data($path, json_encode($data));
return $this;
}
/**
* Export a piece of related data.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The name of the file to be exported.
* @param \stdClass $data The related data to export.
*/
public function export_related_data(array $subcontext, $name, $data) : content_writer {
$path = $this->get_path($subcontext, "{$name}.json");
$this->write_data($path, json_encode($data));
return $this;
}
/**
* Export a piece of data in a custom format.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be exported.
* @param string $filecontent The content to be exported.
*/
public function export_custom_file(array $subcontext, $filename, $filecontent) : content_writer {
$filename = clean_param($filename, PARAM_FILE);
$path = $this->get_path($subcontext, $filename);
$this->write_data($path, $filecontent);
return $this;
}
/**
* Prepare a text area by processing pluginfile URLs within it.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
* @param string $text The text to be processed
* @return string The processed string
*/
public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text) : string {
return str_replace('@@PLUGINFILE@@/', 'files/', $text);
}
/**
* Export all files within the specified component, filearea, itemid combination.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
*/
public function export_area_files(array $subcontext, $component, $filearea, $itemid) : content_writer {
$fs = get_file_storage();
$files = $fs->get_area_files($this->context->id, $component, $filearea, $itemid);
foreach ($files as $file) {
$this->export_file($subcontext, $file);
}
return $this;
}
/**
* Export the specified file in the target location.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stored_file $file The file to be exported.
*/
public function export_file(array $subcontext, \stored_file $file) : content_writer {
if (!$file->is_directory()) {
$subcontextextra = [
get_string('files'),
$file->get_filepath(),
];
$path = $this->get_path(array_merge($subcontext, $subcontextextra), $file->get_filename());
check_dir_exists(dirname($path), true, true);
$this->files[$path] = $file;
}
return $this;
}
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return content_writer
*/
public function export_user_preference(string $component, string $key, string $value, string $description) : content_writer {
if ($this->context !== \context_system::instance()) {
throw new \coding_exception('export_user_preference must be called against the system context');
}
$subcontext = [
get_string('userpreferences'),
];
$fullpath = $this->get_full_path($subcontext, "{$component}.json");
$path = $this->get_path($subcontext, "{$component}.json");
if (file_exists($fullpath)) {
$data = json_decode(file_get_contents($fullpath));
} else {
$data = (object) [];
}
$data->$key = (object) [
'value' => $value,
'description' => $description,
];
$this->write_data($path, json_encode($data));
return $this;
}
/**
* Determine the path for the current context.
*
* @return array The context path.
*/
protected function get_context_path() : Array {
$path = [];
$contexts = array_reverse($this->context->get_parent_contexts(true));
foreach ($contexts as $context) {
$path[] = clean_param($context->get_context_name(), PARAM_FILE);
}
return $path;
}
/**
* Get the relative file path within the current context, and subcontext, using the specified filename.
*
* @param string[] $subcontext The location within the current context to export this data.
* @param string $name The intended filename, including any extensions.
* @return string The fully-qualfiied file path.
*/
protected function get_path(array $subcontext, string $name) : string {
// Combine the context path, and the subcontext data.
$path = array_merge(
$this->get_context_path(),
$subcontext
);
// Join the directory together with the name.
$filepath = implode(DIRECTORY_SEPARATOR, $path) . DIRECTORY_SEPARATOR . $name;
return preg_replace('@' . DIRECTORY_SEPARATOR . '+@', DIRECTORY_SEPARATOR, $filepath);
}
/**
* Get the fully-qualified file path within the current context, and subcontext, using the specified filename.
*
* @param string[] $subcontext The location within the current context to export this data.
* @param string $name The intended filename, including any extensions.
* @return string The fully-qualfiied file path.
*/
protected function get_full_path(array $subcontext, string $name) : string {
$path = array_merge(
[$this->path],
[$this->get_path($subcontext, $name)]
);
// Join the directory together with the name.
$filepath = implode(DIRECTORY_SEPARATOR, $path);
return preg_replace('@' . DIRECTORY_SEPARATOR . '+@', DIRECTORY_SEPARATOR, $filepath);
}
/**
* Write the data to the specified path.
*
* @param string $path The path to export the data at.
* @param string $data The data to be exported.
*/
protected function write_data(string $path, string $data) {
$targetpath = $this->path . DIRECTORY_SEPARATOR . $path;
check_dir_exists(dirname($targetpath), true, true);
file_put_contents($targetpath, $data);
$this->files[$path] = $targetpath;
}
/**
* Perform any required finalisation steps and return the location of the finalised export.
*
* @return string
*/
public function finalise_content() : string {
$exportfile = make_request_directory() . '/export.zip';
$fp = get_file_packer();
$fp->archive_to_pathname($this->files, $exportfile);
// Reset the writer to prevent any further writes.
writer::reset();
return $exportfile;
}
}
@@ -0,0 +1,39 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\plugin\provider interface to describe
* a class which provides data in some form for a plugin.
*
* Plugins should implement this if they store any personal information.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\plugin;
defined('MOODLE_INTERNAL') || die();
/**
* The provider interface for plugins which provide data from a plugin
* directly to the Privacy subsystem.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface provider extends \core_privacy\local\request\core_user_data_provider {
}
@@ -0,0 +1,42 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\plugin\subplugin_provider
* interface to describe a class which provides data in some form for the
* subplugin of another plugin.
*
* It should not be implemented directly, but should be extended by the
* plugin providing a subplugin.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\plugin;
defined('MOODLE_INTERNAL') || die();
/**
* The subplugin_provider interface is for plugins which are sub-plugins of
* a plugin. They do not provide data directly to the core Privacy
* subsystem, but will be accessed and called via the plugin itself.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface subplugin_provider extends \core_privacy\local\request\shared_data_provider {
}
@@ -0,0 +1,54 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\plugin\subsystem_provider
* interface to describe a class which provides data in some form for a
* subsystem.
*
* It should not be implemented directly, but should be extended by the
* subsystem responsible for the plugintype.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\plugin;
defined('MOODLE_INTERNAL') || die();
/**
* The subsystem_provider interface is for plugins which may not
* necessarily be called directly, but instead via a subsystem.
*
* One example of this is the questiontype plugintype. These are
* intrinsically linked against the question subsystem and the question
* subsystem should define an interface extending this one through which it
* can query and retrieve specific data from each questiontype as required.
*
* Each questiontype may additionally respond directly to the privacy API
* if it also impleents the \core_privacay\local\request\plugin\provider
* interface directly.
*
* Care should be taken when extending this provider to not conflict with
* the \core_privacay\local\request\plugin\provider interface.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface subsystem_provider extends \core_privacy\local\request\shared_data_provider {
}
@@ -0,0 +1,48 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\shared_data_provider interface to describe
* a class which provides data in some form.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The shared_data_provider interface is used to describe a provider which
* services user requests between components and and other components.
*
* This includes communication between subplugin, subsystems, and plugins
* which are designed to interact closely with subsystems.
*
* It does not define a specific way of doing so and different types of
* data will need to extend this interface in order to define their own
* contract.
*
* It should not be implemented directly, but should be extended by other
* interfaces in core.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
*/
interface shared_data_provider extends data_provider {
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\subsystem\plugin_provider interface to describe
* a class which provides data in some form for a subsystem.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\subsystem;
defined('MOODLE_INTERNAL') || die();
/**
* The plugin_provider interface for subsystems which provide data directly to a plugin.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
*/
interface plugin_provider extends \core_privacy\local\request\shared_data_provider {
}
@@ -0,0 +1,39 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\subsystem\provider interface to describe
* a class which provides data in some form for a subsystem.
*
* Plugins should implement this if they directly store any personal information.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request\subsystem;
defined('MOODLE_INTERNAL') || die();
/**
* The provider interface for plugins which provide data from a subsystem
* directly to the Privacy subsystem.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
*/
interface provider extends \core_privacy\local\request\core_user_data_provider {
}
@@ -0,0 +1,83 @@
<?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/>.
/**
* This file contains the core_privacy\local\request helper.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* A class containing a set of data transformations for core data types.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class transform {
/**
* Translate a userid into the standard user format for exports.
*
* We have not determined if we will do this or not, but we provide the functionality and encourgae people to use
* it so that it can be retrospectively fitted if required.
*
* @param int $userid the userid to translate
* @return mixed
*/
public static function user(int $userid) {
// For the moment we do not think we should transform as this reveals information about other users.
// However this function is implemented should the need arise in the future.
return $userid;
}
/**
* Translate a unix timestamp into a datetime string.
*
* @param int $datetime the unixtimestamp to translate.
* @return string The translated string.
*/
public static function datetime($datetime) {
return userdate($datetime, get_string('strftimedaydatetime', 'langconfig'));
}
/**
* Translate a unix timestamp into a date string.
*
* @param int $date the unixtimestamp to translate.
* @return string The translated string.
*/
public static function date($date) {
return userdate($date, get_string('strftimetime', 'langconfig'));
}
/**
* Translate a bool or int (0/1) value into a translated yes/no string.
*
* @param bool $value The value to translate
* @return string
*/
public static function yesno($value) {
if ($value) {
return get_string('yes');
} else {
return get_string('no');
}
}
}
@@ -0,0 +1,46 @@
<?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/>.
/**
* This file contains the \core_privacy\local\request\user_preference_provider interface to describe
* a class which provides preference data in some form to core.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The user_preference_provider interface is an interface designed to be
* implemented by components directly to describe a case where that
* component is responsible for storing some form of system-wide user
* preference.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface user_preference_provider extends core_data_provider {
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid);
}
+118
View File
@@ -0,0 +1,118 @@
<?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/>.
/**
* This file contains the interface required to implmeent a content writer.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* The writer factory class used to fetch and work with the content_writer.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class writer {
/**
* @var writer The singleton instance of this writer.
*/
protected static $instance = null;
/**
* @var content_writer The current content_writer instance.
*/
protected $realwriter = null;
/**
* Constructor for the content writer.
*
* Protected to prevent direct instantiation.
*/
protected function __construct() {
}
/**
* Singleton to return or create and return a copy of a content_writer.
*
* @return content_writer
*/
protected function get_writer_instance() : content_writer {
if (null === $this->realwriter) {
if (PHPUNIT_TEST) {
$this->realwriter = new \core_privacy\tests\request\content_writer(static::instance());
} else {
$this->realwriter = new moodle_content_writer(static::instance());
}
}
return $this->realwriter;
}
/**
* Return an instance of
*/
protected static final function instance() {
if (null === self::$instance) {
self::$instance = new static();
}
return self::$instance;
}
/**
* Reset the writer and content_writer.
*/
public static final function reset() {
static::$instance = null;
}
/**
* Provide an instance of the writer with the specified context applied.
*
* @param \context $context The context to apply
* @return content_writer The content_writer
*/
public static function with_context(\context $context) : content_writer {
return static::instance()
->get_writer_instance()
->set_context($context);
}
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return content_writer
*/
public static function export_user_preference(
string $component,
string $key,
string $value,
string $description
) : content_writer {
return static::with_context(\context_system::instance())
->export_user_preference($component, $key, $value, $description);
}
}
+342
View File
@@ -0,0 +1,342 @@
<?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/>.
/**
* This file contains the core_privacy\manager class.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist_collection;
use core_privacy\local\request\deletion_criteria;
defined('MOODLE_INTERNAL') || die();
/**
* The core_privacy\manager class, providing a facade to describe, export and delete personal data across Moodle and its components.
*
* This class is responsible for communicating with and collating privacy data from all relevant components, where relevance is
* determined through implementations of specific marker interfaces. These marker interfaces describe the responsibilities (in terms
* of personal data storage) as well as the relationship between the component and the core_privacy subsystem.
*
* The interface hierarchy is as follows:
* ├── local\metadata\null_provider
* ├── local\metadata\provider
* ├── local\request\data_provider
* └── local\request\core_data_provider
* └── local\request\core_user_data_provider
* └── local\request\plugin\provider
* └── local\request\subsystem\provider
* └── local\request\user_preference_provider
* └── local\request\shared_data_provider
* └── local\request\plugin\subsystem_provider
* └── local\request\plugin\subplugin_provider
* └── local\request\subsystem\plugin_provider
*
* Describing personal data:
* -------------------------
* All components must state whether they store personal data (and DESCRIBE it) by implementing one of the metadata providers:
* - local\metadata\null_provider (indicating they don't store personal data)
* - local\metadata\provider (indicating they do store personal data, and describing it)
*
* The manager requests metadata for all Moodle components implementing the local\metadata\provider interface.
*
* Export and deletion of personal data:
* -------------------------------------
* Those components storing personal data need to provide EXPORT and DELETION of this data by implementing a request provider.
* Which provider implementation depends on the nature of the component; whether it's a sub-component and which components it
* stores data for.
*
* Export and deletion for sub-components (or any component storing data on behalf of another component) is managed by the parent
* component. If a component contains sub-components, it must ask those sub-components to provide the relevant data. Only certain
* 'core provider' components are called directly from the manager and these must provide the personal data stored by both
* themselves, and by all sub-components. Because of this hierarchical structure, the core_privacy\manager needs to know which
* components are to be called directly by core: these are called core data providers. The providers implemented by sub-components
* are called shared data providers.
*
* The following are interfaces are not implemented directly, but are marker interfaces uses to classify components by nature:
* - local\request\data_provider:
* Not implemented directly. Used to classify components storing personal data of some kind. Includes both components storing
* personal data for themselves and on behalf of other components.
* Include: local\request\core_data_provider and local\request\shared_data_provider.
* - local\request\core_data_provider:
* Not implemented directly. Used to classify components storing personal data for themselves and which are to be called by the
* core_privacy subsystem directly.
* Includes: local\request\core_user_data_provider and local\request\user_preference_provider.
* - local\request\core_user_data_provider:
* Not implemented directly. Used to classify components storing personal data for themselves, which are either a plugin or
* subsystem and which are to be called by the core_privacy subsystem directly.
* Includes: local\request\plugin\provider and local\request\subsystem\provider.
* - local\request\shared_data_provider:
* Not implemented directly. Used to classify components storing personal data on behalf of other components and which are
* called by the owning component directly.
* Includes: local\request\plugin\subsystem_provider, local\request\plugin\subplugin_provider and local\request\subsystem\plugin_provider
*
* The manager only requests the export or deletion of personal data for components implementing the local\request\core_data_provider
* interface or one of its descendants; local\request\plugin\provider, local\request\subsystem\provider or local\request\user_preference_provider.
* Implementing one of these signals to the core_privacy subsystem that the component must be queried directly from the manager.
*
* Any component using another component to store personal data on its behalf, is responsible for making the relevant call to
* that component's relevant shared_data_provider class.
*
* For example:
* The manager calls a core_data_provider component (e.g. mod_assign) which, in turn, calls relevant subplugins or subsystems
* (which assign uses to store personal data) to get that data. All data for assign and its sub-components is aggregated by assign
* and returned to the core_privacy subsystem.
*
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/**
* Checks whether the given component is compliant with the core_privacy API.
* To be considered compliant, a component must declare whether (and where) it stores personal data.
*
* Components which do store personal data must:
* - Have implemented the core_privacy\local\metadata\provider interface (to describe the data it stores) and;
* - Have implemented the core_privacy\local\request\data_provider interface (to facilitate export of personal data)
* - Have implemented the core_privacy\local\request\deleter interface
*
* Components which do not store personal data must:
* - Have implemented the core_privacy\local\metadata\null_provider interface to signal that they don't store personal data.
*
* @param string $component frankenstyle component name, e.g. 'mod_assign'
* @return bool true if the component is compliant, false otherwise.
*/
public function component_is_compliant(string $component) : bool {
// Components which don't store user data need only implement the null_provider.
if ($this->component_implements($component, \core_privacy\local\metadata\null_provider::class)) {
return true;
}
// Components which store user data must implement the local\metadata\provider and the local\request\data_provider.
if ($this->component_implements($component, \core_privacy\local\metadata\provider::class) &&
$this->component_implements($component, \core_privacy\local\request\data_provider::class)) {
return true;
}
return false;
}
/**
* Get the privacy metadata for all components.
*
* @return collection[] The array of collection objects, indexed by frankenstyle component name.
*/
public function get_metadata_for_components() : array {
// Get the metadata, and put into an assoc array indexed by component name.
$metadata = [];
foreach ($this->get_component_list() as $component) {
if ($this->component_implements($component, \core_privacy\local\metadata\provider::class)) {
$metadata[$component] = $this->get_provider_classname($component)::get_metadata(new collection($component));
}
}
return $metadata;
}
/**
* Gets a collection of resultset objects for all components.
*
* @param int $userid the id of the user we're fetching contexts for.
* @return contextlist_collection the collection of contextlist items for the respective components.
*/
public function get_contexts_for_userid(int $userid) : contextlist_collection {
$clcollection = new contextlist_collection($userid);
foreach ($this->get_component_list() as $component) {
if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
$contextlist = $this->get_provider_classname($component)::get_contexts_for_userid($userid);
} else {
$contextlist = new local\request\contextlist();
}
// Each contextlist is tied to its respective component.
$contextlist->set_component($component);
// Add contexts that the component may not know about.
// Example of these include activity completion which modules do not know about themselves.
$contextlist = local\request\helper::add_shared_contexts_to_contextlist_for($userid, $contextlist);
if (count($contextlist)) {
$clcollection->add_contextlist($contextlist);
}
}
return $clcollection;
}
/**
* Export all user data for the specified approved_contextlist items.
*
* Note: userid and component are stored in each respective approved_contextlist.
*
* @param contextlist_collection $contextlistcollection the collection of contextlists for all components.
* @return string the location of the exported data.
* @throws \moodle_exception if the contextlist_collection does not contain all approved_contextlist items or if one of the
* approved_contextlists' components is not a core_data_provider.
*/
public function export_user_data(contextlist_collection $contextlistcollection) {
// Export for the various components/contexts.
foreach ($contextlistcollection as $approvedcontextlist) {
if (!$approvedcontextlist instanceof \core_privacy\local\request\approved_contextlist) {
throw new \moodle_exception('Contextlist must be an approved_contextlist');
}
$component = $approvedcontextlist->get_component();
// Core user data providers.
if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
if (count($approvedcontextlist)) {
// This plugin has data it knows about. It is responsible for storing basic data about anything it is
// told to export.
$this->get_provider_classname($component)::export_user_data($approvedcontextlist);
}
} else {
// This plugin does not know that it has data - export the shared data it doesn't know about.
local\request\helper::export_data_for_null_provider($approvedcontextlist);
}
}
// Check each component for non contextlist items too.
foreach ($this->get_component_list() as $component) {
// Core user preference providers.
if ($this->component_implements($component, \core_privacy\local\request\user_preference_provider::class)) {
$this->get_provider_classname($component)::export_user_preferences($contextlistcollection->get_userid());
}
}
return local\request\writer::with_context(\context_system::instance())->finalise_content();
}
/**
* Delete all user data for approved contexts lists provided in the collection.
*
* This call relates to the forgetting of an entire user.
*
* Note: userid and component are stored in each respective approved_contextlist.
*
* @param contextlist_collection $contextlistcollection the collections of approved_contextlist items on which to call deletion.
* @throws \moodle_exception if the contextlist_collection doesn't contain all approved_contextlist items, or if the component
* for an approved_contextlist isn't a core provider.
*/
public function delete_user_data(contextlist_collection $contextlistcollection) {
// Delete the data.
foreach ($contextlistcollection as $approvedcontextlist) {
if (!$approvedcontextlist instanceof \core_privacy\local\request\approved_contextlist) {
throw new \moodle_exception('Contextlist must be an approved_contextlist');
}
if ($this->component_is_core_provider($approvedcontextlist->get_component())) {
if (count($approvedcontextlist)) {
// The component knows about data that it has.
// Have it delete its own data.
$this->get_provider_classname($approvedcontextlist->get_component())::delete_user_data($approvedcontextlist);
}
}
// Delete any shared user data it doesn't know about.
local\request\helper::delete_user_data($approvedcontextlist);
}
}
/**
* Delete user data for all users using the specified deletion_criteria.
*
* @param deletion_criteria $criteria the criteria object dictating what contexts will be deleted.
*/
public function delete_for_context(deletion_criteria $criteria) {
foreach ($this->get_component_list() as $component) {
if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
// This component knows about specific data that it owns.
// Have it delete all of that user data for the context.
$this->get_provider_classname($component)::delete_for_context($criteria);
}
// Delete any shared user data it doesn't know about.
local\request\helper::delete_for_context($component, $criteria);
}
}
/**
* Check whether the specified component is a core provider.
*
* @param string $component the frankenstyle component name.
* @return bool true if the component is a core provider, false otherwise.
*/
protected function component_is_core_provider($component) {
return $this->component_implements($component, \core_privacy\local\request\core_data_provider::class);
}
/**
* Returns a list of frankenstyle names of core components (plugins and subsystems).
*
* @return array the array of frankenstyle component names.
*/
protected function get_component_list() {
$components = [];
// Get all plugins.
$plugintypes = \core_component::get_plugin_types();
foreach ($plugintypes as $plugintype => $typedir) {
$plugins = \core_component::get_plugin_list($plugintype);
foreach ($plugins as $pluginname => $plugindir) {
$components[] = $plugintype . '_' . $pluginname;
}
}
// Get all subsystems.
foreach (\core_component::get_core_subsystems() as $name => $path) {
if (isset($path)) {
$components[] = 'core_' . $name;
}
}
return $components;
}
/**
* Return the fully qualified provider classname for the component.
*
* @param string $component the frankenstyle component name.
* @return string the fully qualified provider classname.
*/
protected function get_provider_classname($component) {
return static::get_provider_classname_for_component($component);
}
/**
* Return the fully qualified provider classname for the component.
*
* @param string $component the frankenstyle component name.
* @return string the fully qualified provider classname.
*/
public static function get_provider_classname_for_component(string $component) {
return "$component\privacy\provider";
}
/**
* Checks whether the component's provider class implements the specified interface.
* This can either be implemented directly, or by implementing a descendant (extension) of the specified interface.
*
* @param string $component the frankenstyle component name.
* @param string $interface the name of the interface we want to check.
* @return bool True if an implementation was found, false otherwise.
*/
protected function component_implements(string $component, string $interface) : bool {
$providerclass = $this->get_provider_classname($component);
if (class_exists($providerclass)) {
$rc = new \ReflectionClass($providerclass);
return $rc->implementsInterface($interface);
}
return false;
}
}
+47
View File
@@ -0,0 +1,47 @@
<?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 the Privacy Subsystem (how very meta).
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* The privacy subsystem does not store any data of it's own.
* It merely serves as a conduit to allow other components to describe, export, and delete data.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}
+120
View File
@@ -0,0 +1,120 @@
<?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/>.
/**
* Testcase for providers implementing parts of the core_privacy subsystem.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\tests;
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Testcase for providers implementing parts of the core_privacy subsystem.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class provider_testcase extends \advanced_testcase {
/**
* Test tearDown.
*/
public function tearDown() {
\core_privacy\local\request\writer::reset();
}
/**
* Export all data for a component for the specified user.
*
* @param int $userid The userid of the user to fetch.
* @param string $component The component to get context data for.
* @return \core_privacy\local\request\contextlist
*/
public function get_contexts_for_userid(int $userid, string $component) {
$classname = $this->get_provider_classname($component);
return $classname::get_contexts_for_userid($userid);
}
/**
* Export all data for a component for the specified user.
*
* @param int $userid The userid of the user to fetch.
* @param string $component The component to get export data for.
*/
public function export_all_data_for_user(int $userid, string $component) {
$contextlist = $this->get_contexts_for_userid($userid, $component);
$approvedcontextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($userid),
$component,
$contextlist->get_contextids()
);
$classname = $this->get_provider_classname($component);
$classname::export_user_data($approvedcontextlist);
}
/**
* Export all daa within a context for a component for the specified user.
*
* @param int $userid The userid of the user to fetch.
* @param \context $context The context to export data for.
* @param string $component The component to get export data for.
*/
public function export_context_data_for_user(int $userid, \context $context, string $component) {
$contextlist = new \core_privacy\tests\request\approved_contextlist(
\core_user::get_user($userid),
$component,
[$context->id]
);
$classname = $this->get_provider_classname($component);
$classname::export_user_data($contextlist);
}
/**
* Determine the classname and ensure that it is a provider.
*
* @param string $component The classname.
* @return string
*/
protected function get_provider_classname($component) {
$classname = "\\${component}\\privacy\\provider";
if (!class_exists($classname)) {
throw new \coding_exception("{$component} does not implement any provider");
}
$rc = new \ReflectionClass($classname);
if (!$rc->implementsInterface(\core_privacy\local\metadata\provider::class)) {
throw new \coding_exception("{$component} does not implement metadata provider");
}
if (!$rc->implementsInterface(\core_privacy\local\request\core_user_data_provider::class)) {
throw new \coding_exception("{$component} does not declare that it provides any user data");
}
return $classname;
}
}
@@ -0,0 +1,79 @@
<?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/>.
/**
* Approved result set for unit testing.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\tests\request;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Fetch Result Set.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class approved_contextlist extends \core_privacy\local\request\approved_contextlist {
/**
* Add a single context to this approved_contextlist.
*
* @param \context $context The context to be added.
* @return $this
*/
public function add_context(\context $context) {
return $this->add_context_by_id($context->id);
}
/**
* Add a single context to this approved_contextlist by it's ID.
*
* @param int $contextid The context to be added.
* @return $this
*/
public function add_context_by_id($contextid) {
return $this->set_contextids(array_merge($this->get_contextids(), [$contextid]));
}
/**
* Add a set of contexts to this approved_contextlist.
*
* @param \context[] $contexts The contexts to be added.
* @return $this
*/
public function add_contexts(array $contexts) {
foreach ($contexts as $context) {
$this->add_context($context);
}
}
/**
* Add a set of contexts to this approved_contextlist by ID.
*
* @param int[] $contexts The contexts to be added.
* @return $this
*/
public function add_contexts_by_id(array $contexts) {
foreach ($contexts as $contextid) {
$this->add_context_by_id($contextid);
}
}
}
@@ -0,0 +1,484 @@
<?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/>.
/**
* This file contains the moodle format implementation of the content writer.
*
* @package core_privacy
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_privacy\tests\request;
defined('MOODLE_INTERNAL') || die();
/**
* An implementation of the content_writer for use in unit tests.
*
* This implementation does not export any data but instead stores it in
* structures within the instance which can be easily queried for use
* during unit tests.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class content_writer implements \core_privacy\local\request\content_writer {
/**
* @var \context The context currently being exported.
*/
protected $context = null;
/**
* @var array The collection of metadata which has been exported.
*/
protected $metadata = [];
/**
* @var array The data which has been exported.
*/
protected $data = [];
/**
* @var array The related data which has been exported.
*/
protected $relateddata = [];
/**
* @var array The list of stored files which have been exported.
*/
protected $files = [];
/**
* @var array The custom files which have been exported.
*/
protected $customfiles = [];
/**
* @var array The site-wide user preferences which have been exported.
*/
protected $userprefs = [];
/**
* Whether any data has been exported at all within the current context.
*/
public function has_any_data() {
$hasdata = !empty($this->data[$this->context->id]);
$hasrelateddata = !empty($this->relateddata[$this->context->id]);
$hasmetadata = !empty($this->metadata[$this->context->id]);
$hasfiles = !empty($this->files[$this->context->id]);
$hascustomfiles = !empty($this->customfiles[$this->context->id]);
$hasuserprefs = !empty($this->userprefs);
return $hasdata || $hasrelateddata || $hasmetadata || $hasfiles || $hascustomfiles || $hasuserprefs;
}
/**
* Constructor for the content writer.
*
* Note: The writer_factory must be passed.
* @param \core_privacy\local\request\writer $writer The writer factory.
*/
public function __construct(\core_privacy\local\request\writer $writer) {
}
/**
* Set the context for the current item being processed.
*
* @param \context $context The context to use
*/
public function set_context(\context $context) : \core_privacy\local\request\content_writer {
$this->context = $context;
if (empty($this->data[$this->context->id])) {
$this->data[$this->context->id] = [];
}
if (empty($this->relateddata[$this->context->id])) {
$this->relateddata[$this->context->id] = [];
}
if (empty($this->metadata[$this->context->id])) {
$this->metadata[$this->context->id] = [];
}
if (empty($this->files[$this->context->id])) {
$this->files[$this->context->id] = [];
}
if (empty($this->customfiles[$this->context->id])) {
$this->customfiles[$this->context->id] = [];
}
return $this;
}
/**
* Return the current context.
*
* @return \context
*/
public function get_current_context() : \context {
return $this->context;
}
/**
* Export the supplied data within the current context, at the supplied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stdClass $data The data to be exported
*/
public function export_data(array $subcontext, \stdClass $data) : \core_privacy\local\request\content_writer {
array_push($subcontext, 'data');
$finalcontent = $data;
while ($pathtail = array_pop($subcontext)) {
$finalcontent = [
$pathtail => $finalcontent,
];
}
$this->data[$this->context->id] = array_replace_recursive($this->data[$this->context->id], $finalcontent);
return $this;
}
/**
* Get all data within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @return array The metadata as a series of keys to value + descrition objects.
*/
public function get_data(array $subcontext = []) {
$basepath = $this->data[$this->context->id];
while ($subpath = array_shift($subcontext)) {
if (isset($basepath[$subpath])) {
$basepath = $basepath[$subpath];
} else {
return [];
}
}
if (isset($basepath['data'])) {
return $basepath['data'];
} else {
return [];
}
}
/**
* Export metadata about the supplied subcontext.
*
* Metadata consists of a key/value pair and a description of the value.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $key The metadata name.
* @param string $value The metadata value.
* @param string $description The description of the value.
* @return $this
*/
public function export_metadata(array $subcontext,
string $key,
$value,
string $description
) : \core_privacy\local\request\content_writer {
array_push($subcontext, 'metadata');
$finalcontent = [
$key => (object) [
'value' => $value,
'description' => $description,
],
];
while ($pathtail = array_pop($subcontext)) {
$finalcontent = [
$pathtail => $finalcontent,
];
}
$this->metadata[$this->context->id] = array_replace_recursive($this->metadata[$this->context->id], $finalcontent);
return $this;
}
/**
* Get all metadata within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @return array The metadata as a series of keys to value + descrition objects.
*/
public function get_all_metadata(array $subcontext = []) {
$basepath = $this->metadata[$this->context->id];
while ($subpath = array_shift($subcontext)) {
if (isset($basepath[$subpath])) {
$basepath = $basepath[$subpath];
}
}
if (isset($basepath['metadata'])) {
return $basepath['metadata'];
} else {
return [];
}
}
/**
* Get the specified metadata within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $key The metadata to be fetched within the context + subcontext.
* @param boolean $valueonly Whether to fetch only the value, rather than the value + description.
* @return array The metadata as a series of keys to value + descrition objects.
*/
public function get_metadata(array $subcontext = [], $key, $valueonly = true) {
$data = $this->get_all_metadata($subcontext);
if (!isset($data[$key])) {
return null;
}
$metadata = $data[$key];
if ($valueonly) {
return $metadata->value;
} else {
return $metadata;
}
}
/**
* Export a piece of related data.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $name The name of the file to be exported.
* @param \stdClass $data The related data to export.
*/
public function export_related_data(array $subcontext, $name, $data) : \core_privacy\local\request\content_writer {
array_push($subcontext, $name);
array_push($subcontext, 'data');
$finalcontent = $data;
while ($pathtail = array_pop($subcontext)) {
$finalcontent = [
$pathtail => $finalcontent,
];
}
$this->relateddata[$this->context->id] = array_replace_recursive($this->relateddata[$this->context->id], $finalcontent);
return $this;
}
/**
* Get all data within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the intended filename.
* @return array The metadata as a series of keys to value + descrition objects.
*/
public function get_related_data(array $subcontext = [], $filename) {
$basepath = $this->relateddata[$this->context->id];
$subcontext[] = $filename;
while ($subpath = array_shift($subcontext)) {
if (isset($basepath[$subpath])) {
$basepath = $basepath[$subpath];
} else {
return [];
}
}
if (isset($basepath['data'])) {
return $basepath['data'];
} else {
return [];
}
}
/**
* Export a piece of data in a custom format.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be exported.
* @param string $filecontent The content to be exported.
*/
public function export_custom_file(array $subcontext, $filename, $filecontent) : \core_privacy\local\request\content_writer {
$filename = clean_param($filename, PARAM_FILE);
$finalcontent = [
$filename => $filecontent,
];
while ($pathtail = array_pop($subcontext)) {
$finalcontent = [
$pathtail => $finalcontent,
];
}
$this->customfiles[$this->context->id] = array_replace_recursive($this->customfiles[$this->context->id], $finalcontent);
return $this;
}
/**
* Get the specified custom file within the subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $filename The name of the file to be fetched within the context + subcontext.
* @return string The content of the file.
*/
public function get_custom_file(array $subcontext = [], $filename = null) {
if (!empty($filename)) {
array_push($subcontext, $filename);
}
$basepath = $this->customfiles[$this->context->id];
while ($subpath = array_shift($subcontext)) {
if (isset($basepath[$subpath])) {
$basepath = $basepath[$subpath];
}
}
return $basepath;
}
/**
* Prepare a text area by processing pluginfile URLs within it.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
* @param string $text The text to be processed
* @return string The processed string
*/
public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text) : string {
return str_replace('@@PLUGINFILE@@/', 'files/', $text);
}
/**
* Export all files within the specified component, filearea, itemid combination.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param string $component The name of the component that the files belong to.
* @param string $filearea The filearea within that component.
* @param string $itemid Which item those files belong to.
*/
public function export_area_files(array $subcontext, $component, $filearea, $itemid) : \core_privacy\local\request\content_writer {
$fs = get_file_storage();
$files = $fs->get_area_files($this->context->id, $component, $filearea, $itemid);
foreach ($files as $file) {
$this->export_file($subcontext, $file);
}
return $this;
}
/**
* Export the specified file in the target location.
*
* @param array $subcontext The location within the current context that this data belongs.
* @param \stored_file $file The file to be exported.
*/
public function export_file(array $subcontext, \stored_file $file) : \core_privacy\local\request\content_writer {
if (!$file->is_directory()) {
$subcontextextra = [
'files',
$file->get_filepath(),
];
$newsubcontext = array_merge($subcontext, $subcontextextra);
$finalcontent = [
$file,
];
while ($pathtail = array_pop($subcontext)) {
$finalcontent = [
$pathtail => $finalcontent,
];
}
$this->customfiles[$this->context->id] = array_replace_recursive($this->customfiles[$this->context->id], $finalcontent);
}
return $this;
}
/**
* Get all files in the specfied subcontext.
*
* @param array $subcontext The location within the current context that this data belongs.
* @return \stored_file[] The list of stored_files in this context + subcontext.
*/
public function get_files(array $subcontext = []) {
$basepath = $this->files[$this->context->id];
while ($subpath = array_shift($subcontext)) {
if (isset($basepath[$subpath])) {
$basepath = $basepath[$subpath];
}
}
return $basepath;
}
/**
* Export the specified user preference.
*
* @param string $component The name of the component.
* @param string $key The name of th key to be exported.
* @param string $value The value of the preference
* @param string $description A description of the value
* @return \core_privacy\local\request\content_writer
*/
public function export_user_preference(
string $component,
string $key,
string $value,
string $description
) : \core_privacy\local\request\content_writer {
if (!isset($this->userprefs[$component])) {
$this->userprefs[$component] = (object) [];
}
$this->userprefs[$component]->$key = (object) [
'value' => $value,
'description' => $description,
];
return $this;
}
/**
* Get all user preferences for the specified component.
*
* @param string $component The name of the component.
* @return \stdClass
*/
public function get_user_preferences(string $component) {
if (isset($this->userprefs[$component])) {
return $this->userprefs[$component];
} else {
return (object) [];
}
}
/**
* Perform any required finalisation steps and return the location of the finalised export.
*
* @return string
*/
public function finalise_content() : string {
return 'mock_path';
}
}
@@ -0,0 +1,58 @@
<?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/>.
/**
* Unit Tests for the approved contextlist Class
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\approved_contextlist;
/**
* Tests for the \core_privacy API's approved contextlist functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class approved_contextlist_test extends advanced_testcase {
/**
* The approved contextlist should not be modifiable once set.
*/
public function test_default_values_set() {
$testuser = \core_user::get_user_by_username('admin');
$contextids = [3, 2, 1];
$component = 'core_privacy';
$uit = new approved_contextlist($testuser, $component, $contextids);
$this->assertEquals($testuser, $uit->get_user());
$this->assertEquals($component, $uit->get_component());
$result = $uit->get_contextids();
// Note: Array order is not guaranteed and should not matter.
foreach ($contextids as $contextid) {
$this->assertNotFalse(array_search($contextid, $result));
}
}
}
+211
View File
@@ -0,0 +1,211 @@
<?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/>.
/**
* Collection unit tests.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\metadata\collection;
use \core_privacy\local\metadata\types;
/**
* Tests for the \core_privacy API's collection functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_metadata_collection extends advanced_testcase {
/**
* Test that adding an unknown type causes the type to be added to the collection.
*/
public function test_add_type_generic_type() {
$collection = new collection('core_privacy');
// Mock a new types\type.
$mockedtype = $this->createMock(types\type::class);
$collection->add_type($mockedtype);
$items = $collection->get_collection();
$this->assertCount(1, $items);
$this->assertEquals($mockedtype, reset($items));
}
/**
* Test that adding a known type works as anticipated.
*/
public function test_add_type_known_type() {
$collection = new collection('core_privacy');
$linked = new types\subsystem_link('example', 'langstring');
$collection->add_type($linked);
$items = $collection->get_collection();
$this->assertCount(1, $items);
$this->assertEquals($linked, reset($items));
}
/**
* Test that adding multiple types returns them all.
*/
public function test_add_type_multiple() {
$collection = new collection('core_privacy');
$a = new types\subsystem_link('example', 'langstring');
$collection->add_type($a);
$b = new types\subsystem_link('example', 'langstring');
$collection->add_type($b);
$items = $collection->get_collection();
$this->assertCount(2, $items);
}
/**
* Test that the add_database_table function adds a database table.
*/
public function test_add_database_table() {
$collection = new collection('core_privacy');
$name = 'example';
$fields = ['field' => 'description'];
$summary = 'summarisation';
$collection->add_database_table($name, $fields, $summary);
$items = $collection->get_collection();
$this->assertCount(1, $items);
$item = reset($items);
$this->assertInstanceOf(types\database_table::class, $item);
$this->assertEquals($name, $item->get_name());
$this->assertEquals($fields, $item->get_privacy_fields());
$this->assertEquals($summary, $item->get_summary());
}
/**
* Test that the add_user_preference function adds a single user preference.
*/
public function test_add_user_preference() {
$collection = new collection('core_privacy');
$name = 'example';
$summary = 'summarisation';
$collection->add_user_preference($name, $summary);
$items = $collection->get_collection();
$this->assertCount(1, $items);
$item = reset($items);
$this->assertInstanceOf(types\user_preference::class, $item);
$this->assertEquals($name, $item->get_name());
$this->assertEquals($summary, $item->get_summary());
}
/**
* Test that the link_external_location function links an external location.
*/
public function test_link_external_location() {
$collection = new collection('core_privacy');
$name = 'example';
$fields = ['field' => 'description'];
$summary = 'summarisation';
$collection->link_external_location($name, $fields, $summary);
$items = $collection->get_collection();
$this->assertCount(1, $items);
$item = reset($items);
$this->assertInstanceOf(types\external_location::class, $item);
$this->assertEquals($name, $item->get_name());
$this->assertEquals($fields, $item->get_privacy_fields());
$this->assertEquals($summary, $item->get_summary());
}
/**
* Test that the link_subsystem function links the subsystem.
*/
public function test_link_subsystem() {
$collection = new collection('core_privacy');
$name = 'example';
$summary = 'summarisation';
$collection->link_subsystem($name, $summary);
$items = $collection->get_collection();
$this->assertCount(1, $items);
$item = reset($items);
$this->assertInstanceOf(types\subsystem_link::class, $item);
$this->assertEquals($name, $item->get_name());
$this->assertEquals($summary, $item->get_summary());
}
/**
* Test that the link_plugintype function links the plugin.
*/
public function test_link_plugintype() {
$collection = new collection('core_privacy');
$name = 'example';
$summary = 'summarisation';
$collection->link_plugintype($name, $summary);
$items = $collection->get_collection();
$this->assertCount(1, $items);
$item = reset($items);
$this->assertInstanceOf(types\plugintype_link::class, $item);
$this->assertEquals($name, $item->get_name());
$this->assertEquals($summary, $item->get_summary());
}
/**
* Data provider to supply a list of valid components.
*
* @return array
*/
public function component_list_provider() {
return [
['core_privacy'],
['mod_forum'],
];
}
/**
* Test that we can get the component correctly.
*
* The component will be used for string translations.
*
* @dataProvider component_list_provider
* @param string $component The component to test
*/
public function test_get_component($component) {
$collection = new collection($component);
$this->assertEquals($component, $collection->get_component());
}
}
+162
View File
@@ -0,0 +1,162 @@
<?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/>.
/**
* Unit Tests for the abstract contextlist Class
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\contextlist_base;
/**
* Tests for the \core_privacy API's contextlist base functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist_base_test extends advanced_testcase {
/**
* Ensure that get_contextids returns the list of unique contextids.
*
* @dataProvider get_contextids_provider
* @param array $input List of context IDs
* @param array $expected list of contextids
* @param int $count Expected count
*/
public function test_get_contextids($input, $expected, $count) {
$uit = new test_contextlist_base();
$uit->set_contextids($input);
$result = $uit->get_contextids();
$this->assertCount($count, $result);
// Note: Array order is not guaranteed and should not matter.
foreach ($expected as $contextid) {
$this->assertNotFalse(array_search($contextid, $result));
}
}
/**
* Provider for the list of contextids.
*
* @return array
*/
public function get_contextids_provider() {
return [
'basic' => [
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
5,
],
'duplicates' => [
[1, 1, 2, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
5,
],
'Mixed order with duplicates' => [
[5, 4, 2, 5, 4, 1, 3, 4, 1, 5, 5, 5, 2, 4, 1, 2],
[1, 2, 3, 4, 5],
5,
],
];
}
/**
* Ensure that get_contexts returns the correct list of contexts.
*/
public function test_get_contexts() {
global $DB;
$contexts = [];
$contexts[] = \context_system::instance();
$contexts[] = \context_user::instance(\core_user::get_user_by_username('admin')->id);
$ids = [];
foreach ($contexts as $context) {
$ids[] = $context->id;
}
$uit = new test_contextlist_base();
$uit->set_contextids($ids);
$result = $uit->get_contexts();
$this->assertCount(count($contexts), $result);
foreach ($contexts as $context) {
$this->assertNotFalse(array_search($context, $result));
}
}
/**
* Ensure that the contextlist_base is countable.
*
* @dataProvider get_contextids_provider
* @param array $input List of context IDs
* @param array $expected list of contextids
* @param int $count Expected count
*/
public function test_countable($input, $expected, $count) {
$uit = new test_contextlist_base();
$uit->set_contextids($input);
$this->assertCount($count, $uit);
}
/**
* Ensure that the contextlist_base iterates over the set of contexts.
*/
public function test_context_iteration() {
global $DB;
$allcontexts = $DB->get_records('context');
$contexts = [];
foreach ($allcontexts as $context) {
$contexts[] = \context::instance_by_id($context->id);
}
$uit = new test_contextlist_base();
$uit->set_contextids(array_keys($allcontexts));
foreach ($uit as $key => $context) {
$this->assertNotFalse(array_search($context, $contexts));
}
}
}
/**
* A test class extending the contextlist_base allowing setting of the
* contextids.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_contextlist_base extends contextlist_base {
/**
* Set the contextids for the test class.
*
* @param int[] $contexids The list of contextids to use.
*/
public function set_contextids(array $contextids) {
parent::set_contextids($contextids);
}
}
@@ -0,0 +1,175 @@
<?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/>.
/**
* Unit Tests for a the collection of contextlists class
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\contextlist_collection;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
/**
* Tests for the \core_privacy API's contextlist collection functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist_collection_test extends advanced_testcase {
/**
* A contextlist_collection should support the contextlist type.
*/
public function test_supports_contextlist() {
$uit = new contextlist_collection(1);
$contextlist = new contextlist();
$contextlist->set_component('core_privacy');
$uit->add_contextlist($contextlist);
$this->assertCount(1, $uit->get_contextlists());
}
/**
* A contextlist_collection should support the approved_contextlist type.
*/
public function test_supports_approved_contextlist() {
$uit = new contextlist_collection(1);
$testuser = \core_user::get_user_by_username('admin');
$contextids = [3, 2, 1];
$uit->add_contextlist(new approved_contextlist($testuser, 'core_privacy', $contextids));
$this->assertCount(1, $uit->get_contextlists());
}
/**
* Ensure that get_contextlist_for_component returns the correct contextlist.
*/
public function test_get_contextlist_for_component() {
$uit = new contextlist_collection(1);
$coretests = new contextlist();
$coretests->set_component('core_tests');
$uit->add_contextlist($coretests);
$coreprivacy = new contextlist();
$coreprivacy->set_component('core_privacy');
$uit->add_contextlist($coreprivacy);
// Note: This uses assertSame rather than assertEquals.
// The former checks the actual object, whilst assertEquals only checks that they look the same.
$this->assertSame($coretests, $uit->get_contextlist_for_component('core_tests'));
$this->assertSame($coreprivacy, $uit->get_contextlist_for_component('core_privacy'));
}
/**
* Ensure that get_contextlist_for_component does not die horribly when querying a non-existent component.
*/
public function test_get_contextlist_for_component_not_found() {
$uit = new contextlist_collection(1);
$this->assertNull($uit->get_contextlist_for_component('core_tests'));
}
/**
* Ensure that a duplicate contextlist in the collection throws an Exception.
*/
public function test_duplicate_addition_throws() {
$uit = new contextlist_collection(1);
$coretests = new contextlist();
$coretests->set_component('core_tests');
$uit->add_contextlist($coretests);
$this->expectException('moodle_exception');
$uit->add_contextlist($coretests);
}
/**
* Ensure that the contextlist_collection is countable.
*/
public function test_countable() {
$uit = new contextlist_collection(1);
$contextlist = new contextlist();
$contextlist->set_component('test_example');
$uit->add_contextlist($contextlist);
$contextlist = new contextlist();
$contextlist->set_component('test_another');
$uit->add_contextlist($contextlist);
$this->assertCount(2, $uit);
}
/**
* Ensure that the contextlist_collection iterates over the set of contextlists.
*/
public function test_iteration() {
$uit = new contextlist_collection(1);
$testdata = [];
$component = 'test_example';
$contextlist = new contextlist();
$contextlist->set_component($component);
$uit->add_contextlist($contextlist);
$testdata[$component] = $contextlist;
$component = 'test_another';
$contextlist = new contextlist();
$contextlist->set_component($component);
$uit->add_contextlist($contextlist);
$testdata[$component] = $contextlist;
$component = 'test_third';
$contextlist = new contextlist();
$contextlist->set_component($component);
$uit->add_contextlist($contextlist);
$testdata[$component] = $contextlist;
foreach ($uit as $component => $list) {
$this->assertEquals($testdata[$component], $list);
}
$this->assertCount(3, $uit);
}
/**
* Test that the userid is correctly returned.
*/
public function test_get_userid() {
$uit = new contextlist_collection(1);
$this->assertEquals(1, $uit->get_userid());
}
/**
* Test that an exception is thrown if a contextlist does not contain a component.
*/
public function test_add_without_component() {
$uit = new contextlist_collection(1);
$this->expectException(moodle_exception::class);
$uit->add_contextlist(new contextlist());
}
}
+55
View File
@@ -0,0 +1,55 @@
<?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/>.
/**
* Unit Tests for the approved contextlist Class
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\contextlist;
/**
* Tests for the \core_privacy API's approved contextlist functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class contextlist_test extends advanced_testcase {
/**
* Ensure that valid SQL results in the relevant contexts being added.
*/
public function test_add_from_sql() {
global $DB;
$sql = "SELECT c.id FROM {context} c";
$params = [];
$allcontexts = $DB->get_records_sql($sql, $params);
$uit = new contextlist();
$uit->add_from_sql($sql, $params);
$this->assertCount(count($allcontexts), $uit);
}
}
+56
View File
@@ -0,0 +1,56 @@
<?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/>.
/**
* Unit Tests for the request deletion criteria.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\deletion_criteria;
/**
* Tests for the \core_privacy API's request deletion criteria class.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class deletion_criteria_test extends advanced_testcase {
/**
* The get_context function should return the entered context.
*/
public function test_get_context() {
$context = \context_system::instance();
$uit = new deletion_criteria($context);
$this->assertSame($context, $uit->get_context());
}
/**
* The get_context function should return the entered context.
*/
public function test_get_context_user_context() {
$context = \context_user::instance(\core_user::get_user_by_username('admin')->id);
$uit = new deletion_criteria($context);
$this->assertSame($context, $uit->get_context());
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,54 @@
<?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/>.
/**
* Test provider using a fake plugin name.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_testcomponent4\privacy;
use core_privacy\local\request\writer;
/**
* Mock core_user_data_provider for unit tests.
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider, \core_privacy\local\request\user_preference_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'notimplemented';
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
writer::export_user_preference('mod_testcomponent4', 'mykey', 'myvalue', 'mydescription');
}
}
+42
View File
@@ -0,0 +1,42 @@
<?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/>.
/**
* Test null provider using a fake plugin name.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_testcomponent2\privacy;
/**
* Mock null_provider for unit tests.
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string the reason for being a null provider.
*/
public static function get_reason(): string {
return 'testcomponent2 null provider reason';
}
}
@@ -0,0 +1,46 @@
<?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/>.
/**
* Test provider using a fake plugin name.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_testcomponent3\privacy;
use core_privacy\local\metadata\collection;
/**
* Mock shared_data_provider for unit tests.
* @copyright 2018 Jake Dallimore <[email protected]>
* @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\plugin\subplugin_provider {
/**
* Returns meta data about this system.
*
* @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_metadata(collection $collection) : collection {
$collection = new collection('testcomponent3');
$collection->add_database_table('testtable', ['testfield1', 'testfield2'], 'testsummary');
return $collection;
}
}
+86
View File
@@ -0,0 +1,86 @@
<?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/>.
/**
* Test provider using a fake plugin name.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_testcomponent\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\deletion_criteria;
/**
* Mock core_user_data_provider for unit tests.
* @copyright 2018 Jake Dallimore <[email protected]>
* @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\plugin\provider {
/**
* @return array The array of metadata.
*/
public static function get_metadata(collection $collection) : collection {
$collection = new collection('testcomponent');
$collection->add_database_table('testtable', ['testfield1', 'testfield2'], 'testsummary');
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 {
$cl = new contextlist();
$cl->add_from_sql("SELECT c.id FROM {context} c WHERE c.id = :id", ['id' => \context_system::instance()->id]);
return $cl;
}
/**
* 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) {
// This does nothing. We only want to confirm this can be called via the \core_privacy\manager.
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param deletion_criteria $criteria An object containing specific deletion criteria to delete for.
*/
public static function delete_for_context(deletion_criteria $criteria) {
// This does nothing. We only want to confirm this can be called via the \core_privacy\manager.
}
/**
* 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_user_data(approved_contextlist $contextlist) {
// This does nothing. We only want to confirm this can be called via the \core_privacy\manager.
}
}
+268
View File
@@ -0,0 +1,268 @@
<?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/>.
/**
* Unit tests for the privacy legacy polyfill.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\deletion_criteria;
use \core_privacy\local\request\approved_contextlist;
/**
* Tests for the \core_privacy API's types\user_preference functionality.
* Unit tests for the Privacy API's legacy_polyfill.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_legacy_polyfill_test extends advanced_testcase {
/**
* Test that the null_provider polyfill works and that the static _get_reason can be
* successfully called.
*/
public function test_null_provider() {
$this->assertEquals('thisisareason', test_legacy_polyfill_null_provider::get_reason());
}
/**
* Test that the metdata\provider polyfill works and that the static _get_metadata can be
* successfully called.
*/
public function test_metadata_provider() {
$collection = new collection('core_privacy');
$this->assertSame($collection, test_legacy_polyfill_metadata_provider::get_metadata($collection));
}
/**
* Test that the local\request\user_preference_provider polyfill works and that the static
* _export_user_preferences can be successfully called.
*/
public function test_user_preference_provider() {
$userid = 417;
$mock = $this->createMock(test_legacy_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_export_user_preferences', [$userid])
->willReturn(null);
test_legacy_polyfill_user_preference_provider::$mock = $mock;
test_legacy_polyfill_user_preference_provider::export_user_preferences($userid);
}
/**
* Test that the local\request\core_user_preference_provider polyfill works and that the static
* _get_contexts_for_userid can be successfully called.
*/
public function test_get_contexts_for_userid() {
$userid = 417;
$contextlist = new contextlist('core_privacy');
$mock = $this->createMock(test_legacy_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_get_contexts_for_userid', [$userid])
->willReturn($contextlist);
test_legacy_polyfill_request_provider::$mock = $mock;
$result = test_legacy_polyfill_request_provider::get_contexts_for_userid($userid);
$this->assertSame($contextlist, $result);
}
/**
* Test that the local\request\core_user_preference_provider polyfill works and that the static
* _export_user_data can be successfully called.
*/
public function test_export_user_data() {
$contextlist = new approved_contextlist(\core_user::get_user_by_username('admin'), 'core_privacy', [98]);
$mock = $this->createMock(test_legacy_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_export_user_data', [$contextlist]);
test_legacy_polyfill_request_provider::$mock = $mock;
test_legacy_polyfill_request_provider::export_user_data($contextlist);
}
/**
* Test that the local\request\core_user_preference_provider polyfill works and that the static
* _delete_for_context can be successfully called.
*/
public function test_delete_for_context() {
$criteria = new deletion_criteria(\context_system::instance());
$mock = $this->createMock(test_legacy_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_delete_for_context', [$criteria]);
test_legacy_polyfill_request_provider::$mock = $mock;
test_legacy_polyfill_request_provider::delete_for_context($criteria);
}
/**
* Test that the local\request\core_user_preference_provider polyfill works and that the static
* _delete_user_data can be successfully called.
*/
public function test_delete_user_data() {
$contextlist = new approved_contextlist(\core_user::get_user_by_username('admin'), 'core_privacy', [98]);
$mock = $this->createMock(test_legacy_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_delete_user_data', [$contextlist]);
test_legacy_polyfill_request_provider::$mock = $mock;
test_legacy_polyfill_request_provider::delete_user_data($contextlist);
}
}
/**
* Legacy polyfill test for the null provider.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_legacy_polyfill_null_provider implements \core_privacy\local\metadata\null_provider {
use \core_privacy\local\legacy_polyfill;
/**
* Test for get_reason
*/
protected static function _get_reason() {
return 'thisisareason';
}
}
/**
* Legacy polyfill test for the metadata provider.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_legacy_polyfill_metadata_provider implements \core_privacy\local\metadata\provider {
use \core_privacy\local\legacy_polyfill;
/**
* Test for get_metadata.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
protected static function _get_metadata(collection $collection) {
return $collection;
}
}
/**
* Legacy polyfill test for the metadata provider.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_legacy_polyfill_user_preference_provider implements \core_privacy\local\request\user_preference_provider {
use \core_privacy\local\legacy_polyfill;
/**
* @var test_legacy_polyfill_request_provider $mock
*/
public static $mock = null;
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
protected static function _export_user_preferences($userid) {
return static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
}
/**
* Legacy polyfill test for the request provider.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_legacy_polyfill_request_provider implements \core_privacy\local\request\core_user_data_provider {
use \core_privacy\local\legacy_polyfill;
/**
* @var test_legacy_polyfill_request_provider $mock
*/
public static $mock = null;
/**
* Test for get_contexts_for_userid.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
protected static function _get_contexts_for_userid($userid) {
return static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
/**
* Test for export_user_data.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
protected static function _export_user_data(approved_contextlist $contextlist) {
return static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param deletion_criteria $criteria An object containing specific deletion criteria to delete for.
*/
public static function _delete_for_context(deletion_criteria $criteria) {
return static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
/**
* 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_user_data(approved_contextlist $contextlist) {
return static::$mock->get_return_value(__FUNCTION__, func_get_args());
}
}
class test_legacy_polyfill_mock_wrapper {
/**
* Get the return value for the specified item.
*/
public function get_return_value() {}
}
+193
View File
@@ -0,0 +1,193 @@
<?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 manager unit tests.
*
* @package core_privacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/privacy/tests/fixtures/mock_null_provider.php');
require_once($CFG->dirroot . '/privacy/tests/fixtures/mock_provider.php');
require_once($CFG->dirroot . '/privacy/tests/fixtures/mock_plugin_subplugin_provider.php');
require_once($CFG->dirroot . '/privacy/tests/fixtures/mock_mod_with_user_data_provider.php');
use \core_privacy\local\request\writer;
/**
* Privacy manager unit tests.
*
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class privacy_manager_testcase extends advanced_testcase {
/**
* Test tearDown.
*/
public function tearDown() {
\core_privacy\local\request\writer::reset();
}
/**
* Helper to spoof the results of the internal function get_components_list, allowing mock components to be tested.
*
* @param array $componentnames and array of component names to include as valid core components.
* @return PHPUnit_Framework_MockObject_MockObject
*/
protected function get_mock_manager_with_core_components($componentnames) {
$mock = $this->getMockBuilder(\core_privacy\manager::class)
->setMethods(['get_component_list'])
->getMock();
$mock->expects($this->any())
->method('get_component_list')
->will($this->returnValue($componentnames));
return $mock;
}
/**
* Test collection of metadata for components implementing a metadata provider.
*/
public function test_get_metadata_for_components() {
// Get a mock manager, in which the core components list is mocked to include all mock plugins.
// testcomponent is a core provider, testcomponent2 is a null provider, testcomponent3 is subplugin provider (non core).
$mockman = $this->get_mock_manager_with_core_components(['mod_testcomponent', 'mod_testcomponent2', 'mod_testcomponent3']);
// Core providers and shared providers both implement the metadata provider.
$collectionarray = $mockman->get_metadata_for_components();
$this->assertArrayHasKey('mod_testcomponent', $collectionarray);
$collection = $collectionarray['mod_testcomponent'];
$this->assertInstanceOf(\core_privacy\local\metadata\collection::class, $collection);
$this->assertArrayHasKey('mod_testcomponent3', $collectionarray);
$collection = $collectionarray['mod_testcomponent3'];
$this->assertInstanceOf(\core_privacy\local\metadata\collection::class, $collection);
// Component which implements just the local\metadata\null_provider. Metadata is not provided.
$this->assertArrayNotHasKey('mod_testcomponent2', $collectionarray);
}
/**
* Test that get_contexts_for_userid() only returns contextlist collections for core providers.
*/
public function test_get_contexts_for_userid() {
// Get a mock manager, in which the core components list is mocked to include all mock plugins.
// testcomponent is a core provider, testcomponent2 is a null provider, testcomponent3 is subplugin provider (non core).
$mockman = $this->get_mock_manager_with_core_components(['mod_testcomponent', 'mod_testcomponent2', 'mod_testcomponent3']);
// Get the contextlist_collection.
$contextlistcollection = $mockman->get_contexts_for_userid(10);
$this->assertInstanceOf(\core_privacy\local\request\contextlist_collection::class, $contextlistcollection);
ob_flush();
// Verify we have a contextlist for the component in the collection.
$this->assertInstanceOf(\core_privacy\local\request\contextlist::class,
$contextlistcollection->get_contextlist_for_component('mod_testcomponent'));
// Verify we don't have a contextlist for the shared provider in the collection.
$this->assertNull($contextlistcollection->get_contextlist_for_component('mod_testcomponent3'));
// Verify we don't have a contextlist for the component which does not store user data.
$this->assertEmpty($contextlistcollection->get_contextlist_for_component('mod_testcomponent2'));
}
/**
* Test verifying the output of component_is_compliant.
*/
public function test_component_is_compliant() {
// Get a mock manager, in which the core components list is mocked to include all mock plugins.
// testcomponent is a core provider, testcomponent2 is a null provider, testcomponent3 is subplugin provider (non core).
$mockman = $this->get_mock_manager_with_core_components(['mod_testcomponent', 'mod_testcomponent2', 'mod_testcomponent3']);
// A core_provider plugin implementing all required interfaces (local\metadata\provider, local\request\plugin_provider).
$this->assertTrue($mockman->component_is_compliant('mod_testcomponent'));
// A component implementing just the \core_privacy\local\metadata\null_provider is compliant.
$this->assertTrue($mockman->component_is_compliant('mod_testcomponent2'));
// A shared provider plugin implementing all required interfaces (local\metadata\provider, local\request\plugin\subplugin_provider)
// is compliant.
$this->assertTrue($mockman->component_is_compliant('mod_testcomponent3'));
// A component implementing none of the providers.
$this->assertFalse($mockman->component_is_compliant('tool_thisisnotarealtool123'));
}
/**
* Test verifying only approved contextlists can be used with the export_user_data method.
*/
public function test_export_user_data() {
// Get a mock manager, in which the core components list is mocked to include all mock plugins.
// testcomponent is a core provider, testcomponent2 is a null provider, testcomponent3 is subplugin provider (non core).
$mockman = $this->get_mock_manager_with_core_components(['mod_testcomponent', 'mod_testcomponent2', 'mod_testcomponent3', 'mod_testcomponent4']);
// Get the non-approved contextlists.
$contextlistcollection = $mockman->get_contexts_for_userid(10);
// Create an approved contextlist.
$approvedcontextlistcollection = new \core_privacy\local\request\contextlist_collection(10);
foreach ($contextlistcollection->get_contextlists() as $contextlist) {
$approvedcontextlist = new \core_privacy\local\request\approved_contextlist(new stdClass(), $contextlist->get_component(),
$contextlist->get_contextids());
$approvedcontextlistcollection->add_contextlist($approvedcontextlist);
}
// Verify the mocked return from the writer, meaning the manager method exited normally.
$this->assertEquals('mock_path', $mockman->export_user_data($approvedcontextlistcollection));
// Verify that a user preference was exported for 'mod_testcomponent4'.
$prefs = writer::with_context(\context_system::instance())->get_user_preferences('mod_testcomponent4');
$this->assertNotEmpty($prefs);
$this->assertNotEmpty($prefs->mykey);
$this->assertEquals('myvalue', $prefs->mykey->value);
$this->assertEquals('mydescription', $prefs->mykey->description);
// Verify an exception is thrown if trying to pass in a collection of non-approved_contextlist items.
$this->expectException(moodle_exception::class);
$mockman->export_user_data($contextlistcollection);
}
/**
* Test verifying only approved contextlists can be used with the delete_user_data method.
*/
public function test_delete_user_data() {
$this->resetAfterTest();
// Get a mock manager, in which the core components list is mocked to include all mock plugins.
// testcomponent is a core provider, testcomponent2 is a null provider, testcomponent3 is subplugin provider (non core).
$mockman = $this->get_mock_manager_with_core_components(['mod_testcomponent', 'mod_testcomponent2', 'mod_testcomponent3']);
// Get the non-approved contextlists.
$user = \core_user::get_user_by_username('admin');
$contextlistcollection = $mockman->get_contexts_for_userid($user->id);
// Create an approved contextlist.
$approvedcontextlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
foreach ($contextlistcollection->get_contextlists() as $contextlist) {
$approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user, $contextlist->get_component(),
$contextlist->get_contextids());
$approvedcontextlistcollection->add_contextlist($approvedcontextlist);
}
// Verify null, as the method has no return type and exits normally. Mainly checking we don't see any exception.
$this->assertNull($mockman->delete_user_data($approvedcontextlistcollection));
// Verify an exception is thrown if trying to pass in a collection of non-approved_contextlist items.
$this->expectException(moodle_exception::class);
$mockman->delete_user_data($contextlistcollection);
}
}
@@ -0,0 +1,747 @@
<?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/>.
/**
* Unit Tests for the Moodle Content Writer.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\writer;
use \core_privacy\local\request\moodle_content_writer;
/**
* Tests for the \core_privacy API's moodle_content_writer functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodle_content_writer_test extends advanced_testcase {
/**
* Test that exported data is saved correctly within the system context.
*
* @dataProvider export_data_provider
* @param \stdClass $data Data
*/
public function test_export_data($data) {
$context = \context_system::instance();
$subcontext = [];
$writer = $this->get_writer_instance()
->set_context($context)
->export_data($subcontext, $data);
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, $subcontext, 'data.json');
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertEquals($data, $expanded);
}
/**
* Test that exported data is saved correctly for context/subcontext.
*
* @dataProvider export_data_provider
* @param \stdClass $data Data
*/
public function test_export_data_different_context($data) {
$context = \context_user::instance(\core_user::get_user_by_username('admin')->id);
$subcontext = ['sub', 'context'];
$writer = $this->get_writer_instance()
->set_context($context)
->export_data($subcontext, $data);
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, $subcontext, 'data.json');
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertEquals($data, $expanded);
}
/**
* Test that exported is saved within the correct directory locations.
*/
public function test_export_data_writes_to_multiple_context() {
$subcontext = ['sub', 'context'];
$systemcontext = \context_system::instance();
$systemdata = (object) [
'belongsto' => 'system',
];
$usercontext = \context_user::instance(\core_user::get_user_by_username('admin')->id);
$userdata = (object) [
'belongsto' => 'user',
];
$writer = $this->get_writer_instance();
$writer
->set_context($systemcontext)
->export_data($subcontext, $systemdata);
$writer
->set_context($usercontext)
->export_data($subcontext, $userdata);
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($systemcontext, $subcontext, 'data.json');
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertEquals($systemdata, $expanded);
$contextpath = $this->get_context_path($usercontext, $subcontext, 'data.json');
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertEquals($userdata, $expanded);
}
/**
* Test that multiple writes to the same location cause the latest version to be written.
*/
public function test_export_data_multiple_writes_same_context() {
$subcontext = ['sub', 'context'];
$systemcontext = \context_system::instance();
$originaldata = (object) [
'belongsto' => 'system',
];
$newdata = (object) [
'abc' => 'def',
];
$writer = $this->get_writer_instance();
$writer
->set_context($systemcontext)
->export_data($subcontext, $originaldata);
$writer
->set_context($systemcontext)
->export_data($subcontext, $newdata);
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($systemcontext, $subcontext, 'data.json');
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertEquals($newdata, $expanded);
}
/**
* Data provider for exporting user data.
*/
public function export_data_provider() {
return [
'basic' => [
(object) [
'example' => (object) [
'key' => 'value',
],
],
],
];
}
/**
* Test that metadata can be set.
*
* @dataProvider export_metadata_provider
* @param string $key Key
* @param string $value Value
* @param string $description Description
*/
public function test_export_metadata($key, $value, $description) {
$context = \context_system::instance();
$subcontext = ['a', 'b', 'c'];
$writer = $this->get_writer_instance()
->set_context($context)
->export_metadata($subcontext, $key, $value, $description);
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, $subcontext, 'metadata.json');
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertTrue(isset($expanded->$key));
$this->assertEquals($value, $expanded->$key->value);
$this->assertEquals($description, $expanded->$key->description);
}
/**
* Test that metadata can be set additively.
*/
public function test_export_metadata_additive() {
$context = \context_system::instance();
$subcontext = [];
$writer = $this->get_writer_instance();
$writer
->set_context($context)
->export_metadata($subcontext, 'firstkey', 'firstvalue', 'firstdescription');
$writer
->set_context($context)
->export_metadata($subcontext, 'secondkey', 'secondvalue', 'seconddescription');
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, $subcontext, 'metadata.json');
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertTrue(isset($expanded->firstkey));
$this->assertEquals('firstvalue', $expanded->firstkey->value);
$this->assertEquals('firstdescription', $expanded->firstkey->description);
$this->assertTrue(isset($expanded->secondkey));
$this->assertEquals('secondvalue', $expanded->secondkey->value);
$this->assertEquals('seconddescription', $expanded->secondkey->description);
}
/**
* Test that metadata can be set additively.
*/
public function test_export_metadata_to_multiple_contexts() {
$systemcontext = \context_system::instance();
$usercontext = \context_user::instance(\core_user::get_user_by_username('admin')->id);
$subcontext = [];
$writer = $this->get_writer_instance();
$writer
->set_context($systemcontext)
->export_metadata($subcontext, 'firstkey', 'firstvalue', 'firstdescription')
->export_metadata($subcontext, 'secondkey', 'secondvalue', 'seconddescription');
$writer
->set_context($usercontext)
->export_metadata($subcontext, 'firstkey', 'alternativevalue', 'alternativedescription')
->export_metadata($subcontext, 'thirdkey', 'thirdvalue', 'thirddescription');
$fileroot = $this->fetch_exported_content($writer);
$systemcontextpath = $this->get_context_path($systemcontext, $subcontext, 'metadata.json');
$this->assertTrue($fileroot->hasChild($systemcontextpath));
$json = $fileroot->getChild($systemcontextpath)->getContent();
$expanded = json_decode($json);
$this->assertTrue(isset($expanded->firstkey));
$this->assertEquals('firstvalue', $expanded->firstkey->value);
$this->assertEquals('firstdescription', $expanded->firstkey->description);
$this->assertTrue(isset($expanded->secondkey));
$this->assertEquals('secondvalue', $expanded->secondkey->value);
$this->assertEquals('seconddescription', $expanded->secondkey->description);
$this->assertFalse(isset($expanded->thirdkey));
$usercontextpath = $this->get_context_path($usercontext, $subcontext, 'metadata.json');
$this->assertTrue($fileroot->hasChild($usercontextpath));
$json = $fileroot->getChild($usercontextpath)->getContent();
$expanded = json_decode($json);
$this->assertTrue(isset($expanded->firstkey));
$this->assertEquals('alternativevalue', $expanded->firstkey->value);
$this->assertEquals('alternativedescription', $expanded->firstkey->description);
$this->assertFalse(isset($expanded->secondkey));
$this->assertTrue(isset($expanded->thirdkey));
$this->assertEquals('thirdvalue', $expanded->thirdkey->value);
$this->assertEquals('thirddescription', $expanded->thirdkey->description);
}
/**
* Data provider for exporting user metadata.
*
* return array
*/
public function export_metadata_provider() {
return [
'basic' => [
'key',
'value',
'This is a description',
],
'valuewithspaces' => [
'key',
'value has mixed',
'This is a description',
],
'encodedvalue' => [
'key',
base64_encode('value has mixed'),
'This is a description',
],
];
}
/**
* Exporting a single stored_file should cause that file to be output in the files directory.
*/
public function test_export_area_files() {
$this->resetAfterTest();
$context = \context_system::instance();
$fs = get_file_storage();
// Add two files to core_privacy::tests::0.
$files = [];
$file = (object) [
'component' => 'core_privacy',
'filearea' => 'tests',
'itemid' => 0,
'path' => '/',
'name' => 'a.txt',
'content' => 'Test file 0',
];
$files[] = $file;
$file = (object) [
'component' => 'core_privacy',
'filearea' => 'tests',
'itemid' => 0,
'path' => '/',
'name' => 'b.txt',
'content' => 'Test file 1',
];
$files[] = $file;
// One with a different itemid.
$file = (object) [
'component' => 'core_privacy',
'filearea' => 'tests',
'itemid' => 1,
'path' => '/',
'name' => 'c.txt',
'content' => 'Other',
];
$files[] = $file;
// One with a different filearea.
$file = (object) [
'component' => 'core_privacy',
'filearea' => 'alternative',
'itemid' => 0,
'path' => '/',
'name' => 'd.txt',
'content' => 'Alternative',
];
$files[] = $file;
// One with a different component.
$file = (object) [
'component' => 'core',
'filearea' => 'tests',
'itemid' => 0,
'path' => '/',
'name' => 'e.txt',
'content' => 'Other tests',
];
$files[] = $file;
foreach ($files as $file) {
$record = [
'contextid' => $context->id,
'component' => $file->component,
'filearea' => $file->filearea,
'itemid' => $file->itemid,
'filepath' => $file->path,
'filename' => $file->name,
];
$file->namepath = $file->path . $file->name;
$file->storedfile = $fs->create_file_from_string($record, $file->content);
}
$writer = $this->get_writer_instance()
->set_context($context)
->export_area_files([], 'core_privacy', 'tests', 0);
$fileroot = $this->fetch_exported_content($writer);
$firstfiles = array_slice($files, 0, 2);
foreach ($firstfiles as $file) {
$contextpath = $this->get_context_path($context, [get_string('files')], $file->namepath);
$this->assertTrue($fileroot->hasChild($contextpath));
$this->assertEquals($file->content, $fileroot->getChild($contextpath)->getContent());
}
$otherfiles = array_slice($files, 2);
foreach ($otherfiles as $file) {
$contextpath = $this->get_context_path($context, [get_string('files')], $file->namepath);
$this->assertFalse($fileroot->hasChild($contextpath));
}
}
/**
* Exporting a single stored_file should cause that file to be output in the files directory.
*
* @dataProvider export_file_provider
* @param string $filepath File path
* @param string $filename File name
* @param string $content Content
*/
public function test_export_file($filepath, $filename, $content) {
$this->resetAfterTest();
$context = \context_system::instance();
$filenamepath = $filepath . $filename;
$filerecord = array(
'contextid' => $context->id,
'component' => 'core_privacy',
'filearea' => 'tests',
'itemid' => 0,
'filepath' => $filepath,
'filename' => $filename,
);
$fs = get_file_storage();
$file = $fs->create_file_from_string($filerecord, $content);
$writer = $this->get_writer_instance()
->set_context($context)
->export_file([], $file);
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, [get_string('files')], $filenamepath);
$this->assertTrue($fileroot->hasChild($contextpath));
$this->assertEquals($content, $fileroot->getChild($contextpath)->getContent());
}
/**
* Data provider for the test_export_file function.
*
* @return array
*/
public function export_file_provider() {
return [
'basic' => [
'/',
'testfile.txt',
'An example file content',
],
'longpath' => [
'/path/within/a/path/within/a/path/',
'testfile.txt',
'An example file content',
],
'pathwithspaces' => [
'/path with/some spaces/',
'testfile.txt',
'An example file content',
],
'filewithspaces' => [
'/path with/some spaces/',
'test file.txt',
'An example file content',
],
'image' => [
'/',
'logo.png',
file_get_contents(__DIR__ . '/fixtures/logo.png'),
],
'UTF8' => [
'/Žluťoučký/',
'koníček.txt',
'koníček',
],
'EUC-JP' => [
'/言語設定/',
'言語設定.txt',
'言語設定',
],
];
}
/**
* User preferences can not be exported against the user context.
*/
public function test_export_user_preference_context_user() {
$admin = \core_user::get_user_by_username('admin');
$writer = $this->get_writer_instance();
$this->expectException('coding_exception');
$writer->set_context(\context_user::instance($admin->id))
->export_user_preference('core_privacy', 'validkey', 'value', 'description');
}
/**
* User preferences can not be exported against the coursecat context.
*/
public function test_export_user_preference_context_coursecat() {
global $DB;
$categories = $DB->get_records('course_categories');
$firstcategory = reset($categories);
$this->expectException('coding_exception');
$this->get_writer_instance()
->set_context(\context_coursecat::instance($firstcategory->id))
->export_user_preference('core_privacy', 'validkey', 'value', 'description');
}
/**
* User preferences can not be exported against the course context.
*/
public function test_export_user_preference_context_course() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$this->expectException('coding_exception');
$this->get_writer_instance()
->set_context(\context_course::instance($course->id))
->export_user_preference('core_privacy', 'validkey', 'value', 'description');
}
/**
* User preferences can not be exported against a module context.
*/
public function test_export_user_preference_context_module() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
$this->expectException('coding_exception');
$this->get_writer_instance()
->set_context(\context_module::instance($forum->cmid))
->export_user_preference('core_privacy', 'validkey', 'value', 'description');
}
/**
* User preferences can not be exported against a block context.
*/
public function test_export_user_preference_context_block() {
global $DB;
$blocks = $DB->get_records('block_instances');
$block = reset($blocks);
$this->expectException('coding_exception');
$this->get_writer_instance()
->set_context(\context_block::instance($block->id))
->export_user_preference('core_privacy', 'validkey', 'value', 'description');
}
/**
* User preferences can be exported against the system.
*
* @dataProvider export_user_preference_provider
* @param string $component Component
* @param string $key Key
* @param string $value Value
* @param string $desc Description
*/
public function test_export_user_preference_context_system($component, $key, $value, $desc) {
$context = \context_system::instance();
$writer = $this->get_writer_instance()
->set_context($context)
->export_user_preference($component, $key, $value, $desc);
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertTrue(isset($expanded->$key));
$data = $expanded->$key;
$this->assertEquals($value, $data->value);
$this->assertEquals($desc, $data->description);
}
/**
* User preferences can be exported against the system.
*/
public function test_export_multiple_user_preference_context_system() {
$context = \context_system::instance();
$writer = $this->get_writer_instance();
$component = 'core_privacy';
$writer
->set_context($context)
->export_user_preference($component, 'key1', 'val1', 'desc1')
->export_user_preference($component, 'key2', 'val2', 'desc2');
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertTrue(isset($expanded->key1));
$data = $expanded->key1;
$this->assertEquals('val1', $data->value);
$this->assertEquals('desc1', $data->description);
$this->assertTrue(isset($expanded->key2));
$data = $expanded->key2;
$this->assertEquals('val2', $data->value);
$this->assertEquals('desc2', $data->description);
}
/**
* User preferences can be exported against the system.
*/
public function test_export_user_preference_replace() {
$context = \context_system::instance();
$writer = $this->get_writer_instance();
$component = 'core_privacy';
$key = 'key';
$writer
->set_context($context)
->export_user_preference($component, $key, 'val1', 'desc1');
$writer
->set_context($context)
->export_user_preference($component, $key, 'val2', 'desc2');
$fileroot = $this->fetch_exported_content($writer);
$contextpath = $this->get_context_path($context, [get_string('userpreferences')], "{$component}.json");
$this->assertTrue($fileroot->hasChild($contextpath));
$json = $fileroot->getChild($contextpath)->getContent();
$expanded = json_decode($json);
$this->assertTrue(isset($expanded->$key));
$data = $expanded->$key;
$this->assertEquals('val2', $data->value);
$this->assertEquals('desc2', $data->description);
}
/**
* Provider for various user preferences.
*
* @return array
*/
public function export_user_preference_provider() {
return [
'basic' => [
'core_privacy',
'onekey',
'value',
'description',
],
'encodedvalue' => [
'core_privacy',
'donkey',
base64_encode('value'),
'description',
],
'long description' => [
'core_privacy',
'twokey',
'value',
'This is a much longer description which actually states what this is used for. Blah blah blah.',
],
];
}
/**
* Get a fresh content writer.
*
* @return moodle_content_writer
*/
public function get_writer_instance() {
$factory = $this->createMock(writer::class);
return new moodle_content_writer($factory);
}
/**
* Fetch the exported content for inspection.
*
* @param moodle_content_writer $writer
* @return \org\bovigo\vfs\vfsStreamDirectory
*/
protected function fetch_exported_content(moodle_content_writer $writer) {
$export = $writer
->set_context(\context_system::instance())
->finalise_content();
$fileroot = \org\bovigo\vfs\vfsStream::setup('root');
$target = \org\bovigo\vfs\vfsStream::url('root');
$fp = get_file_packer();
$fp->extract_to_pathname($export, $target);
return $fileroot;
}
/**
* Determine the path for the current context.
*
* Note: This is a wrapper around the real function.
*
* @param \context $context The context being written
* @param array $subcontext The subcontext path
* @param string $name THe name of the file target
* @return array The context path.
*/
protected function get_context_path($context, $subcontext = null, $name = '') {
$rc = new ReflectionClass(moodle_content_writer::class);
$writer = $this->get_writer_instance();
$writer->set_context($context);
if (null === $subcontext) {
$rcm = $rc->getMethod('get_context_path');
$rcm->setAccessible(true);
return $rcm->invoke($writer);
} else {
$rcm = $rc->getMethod('get_path');
$rcm->setAccessible(true);
return $rcm->invoke($writer, $subcontext, $name);
}
}
}
+206
View File
@@ -0,0 +1,206 @@
<?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/>.
/**
* Unit Tests for the request helper.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\helper;
use \core_privacy\local\request\writer;
/**
* Tests for the \core_privacy API's request helper functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class request_helper_test extends advanced_testcase {
/**
* Test that basic module data is returned.
*/
public function test_get_context_data_context_module() {
$this->resetAfterTest();
// Setup.
$course = $this->getDataGenerator()->create_course();
$user = \core_user::get_user_by_username('admin');
$forum = $this->getDataGenerator()->create_module('forum', [
'course' => $course->id,
]);
$context = context_module::instance($forum->cmid);
$modinfo = get_fast_modinfo($course->id);
$cm = $modinfo->cms[$context->instanceid];
// Fetch the data.
$result = helper::get_context_data($context, $user);
$this->assertInstanceOf('stdClass', $result);
// Check that the name matches.
$this->assertEquals($cm->get_formatted_name(), $result->name);
// This plugin supports the intro. Check that it is included and correct.
$formattedintro = format_text($forum->intro, $forum->introformat, [
'noclean' => true,
'para' => false,
'context' => $context,
'overflowdiv' => true,
]);
$this->assertEquals($formattedintro, $result->intro);
// This function should only fetch data. It does not export it.
$this->assertFalse(writer::with_context($context)->has_any_data());
}
/**
* Test that basic block data is returned.
*/
public function test_get_context_data_context_block() {
$this->resetAfterTest();
// Setup.
$block = $this->getDataGenerator()->create_block('online_users');
$context = context_block::instance($block->id);
$user = \core_user::get_user_by_username('admin');
// Fetch the data.
$data = helper::get_context_data($context, $user);
$this->assertEquals(get_string('pluginname', 'block_online_users'), $data->blocktype);
// This function should only fetch data. It does not export it.
$this->assertFalse(writer::with_context($context)->has_any_data());
}
/**
* Test that a course moudle with completion tracking enabled has the completion data returned.
*/
public function test_get_context_data_context_module_completion() {
$this->resetAfterTest();
// Create a module and set completion.
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'completion' => 1]);
$context = context_module::instance($assign->cmid);
$cm = get_coursemodule_from_id('assign', $assign->cmid);
// Fetch context data.
$contextdata = helper::get_context_data($context, $user);
// Completion state is zero.
// Check non completion for a user.
$this->assertEquals(0, $contextdata->completion->state);
// Complete the activity as a user.
$completioninfo = new completion_info($course);
$completioninfo->update_state($cm, COMPLETION_COMPLETE, $user->id);
// Check that completion is now exported.
$contextdata = helper::get_context_data($context, $user);
$this->assertEquals(1, $contextdata->completion->state);
// This function should only fetch data. It does not export it.
$this->assertFalse(writer::with_context($context)->has_any_data());
}
/**
* Test that when there are no files to export for a course module context, nothing is exported.
*/
public function test_export_context_files_context_module_no_files() {
$this->resetAfterTest();
// Setup.
$course = $this->getDataGenerator()->create_course();
$user = \core_user::get_user_by_username('admin');
$forum = $this->getDataGenerator()->create_module('forum', [
'course' => $course->id,
]);
$context = context_module::instance($forum->cmid);
$modinfo = get_fast_modinfo($course->id);
$cm = $modinfo->cms[$context->instanceid];
// Fetch the data.
helper::export_context_files($context, $user);
// This function should only fetch data. It does not export it.
$this->assertFalse(writer::with_context($context)->has_any_data());
}
/**
* Test that when there are no files to export for a course context, nothing is exported.
*/
public function test_export_context_files_context_course_no_files() {
$this->resetAfterTest();
// Setup.
$course = $this->getDataGenerator()->create_course();
$user = \core_user::get_user_by_username('admin');
$context = context_course::instance($course->id);
// Fetch the data.
helper::export_context_files($context, $user);
// This function should only fetch data. It does not export it.
$this->assertFalse(writer::with_context($context)->has_any_data());
}
/**
* Test that when there are files to export for a course context, the files are exported.
*/
public function test_export_context_files_context_course_intro_files() {
$this->resetAfterTest();
// Setup.
$course = $this->getDataGenerator()->create_course();
$user = \core_user::get_user_by_username('admin');
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
$context = context_module::instance($assign->cmid);
// File details.
$filerecord = array(
'contextid' => $context->id,
'component' => 'mod_assign',
'filearea' => 'intro',
'itemid' => 0,
'filepath' => '/',
'filename' => 'logo.png',
);
$content = file_get_contents(__DIR__ . '/fixtures/logo.png');
// Store the file.
$fs = get_file_storage();
$file = $fs->create_file_from_string($filerecord, $content);
// Fetch the data.
helper::export_context_files($context, $user);
// This should have resulted in the file being exported.
$this->assertTrue(writer::with_context($context)->has_any_data());
}
}
+110
View File
@@ -0,0 +1,110 @@
<?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/>.
/**
* Unit Tests for the request transform helper.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\transform;
/**
* Tests for the \core_privacy API's request transform helper functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class request_transform_test extends advanced_testcase {
/**
* Test that user translation currently does nothing.
*
* We have not determined if we will do this or not, but we provide the functionality and encourgae people to use
* it so that it can be retrospectively fitted if required.
*/
public function test_user() {
// Note: This test currently sucks, but there's no point creating users just to test this.
for ($i = 0; $i < 10; $i++) {
$this->assertEquals($i, transform::user($i));
}
}
/**
* Test that the datetime is translated into a string.
*/
public function test_datetime() {
$this->assertInternalType('string', transform::datetime(1));
}
/**
* Test that the date is translated into a string.
*/
public function test_date() {
$this->assertInternalType('string', transform::date(1));
}
/**
* Ensure that the yesno function translates correctly.
*
* @dataProvider yesno_provider
* @param mixed $input The input to test
* @param string $expected The expected value
*/
public function test_yesno($input, $expected) {
$this->assertEquals($expected, transform::yesno($input));
}
/**
* Data provider for tests of the yesno transformation.
*
* @return array
*/
public function yesno_provider() {
return [
'Bool False' => [
false,
get_string('no'),
],
'Bool true' => [
true,
get_string('yes'),
],
'Int 0' => [
0,
get_string('no'),
],
'Int 1' => [
1,
get_string('yes'),
],
'String 0' => [
'0',
get_string('no'),
],
'String 1' => [
'1',
get_string('yes'),
],
];
}
}
+144
View File
@@ -0,0 +1,144 @@
<?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/>.
/**
* Type unit tests for the Database Table.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\metadata\types\database_table;
/**
* Tests for the \core_privacy API's types\database_table functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_metadata_types_database_table extends advanced_testcase {
/**
* Ensure that warnings are thrown if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param array $fields List of fields
* @param string $summary Summary
*/
public function test_invalid_configs($name, $fields, $summary) {
$record = new database_table($name, $fields, $summary);
$this->assertDebuggingCalled();
}
/**
* Ensure that warnings are not thrown if debugging is not enabled, even if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param array $fields List of fields
* @param string $summary Summary
*/
public function test_invalid_configs_debug_normal($name, $fields, $summary) {
global $CFG;
$this->resetAfterTest();
$CFG->debug = DEBUG_NORMAL;
$record = new database_table($name, $fields, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Ensure that no warnings are shown for valid combinations.
*
* @dataProvider valid_string_provider
* @param string $name Name
* @param array $fields List of fields
* @param string $summary Summary
*/
public function test_valid_configs($name, $fields, $summary) {
$record = new database_table($name, $fields, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Data provider with a list of invalid string identifiers.
*
* @return array
*/
public function invalid_string_provider() {
return [
'Space in summary' => [
'example',
[
'field' => 'privacy:valid',
],
'This table is used for purposes.',
],
'Comma in summary' => [
'example',
[
'field' => 'privacy:valid',
],
'privacy,foo',
],
'Space in field name' => [
'example',
[
'field' => 'This field is used for purposes.',
],
'privacy:valid',
],
'Comma in field name' => [
'example',
[
'field' => 'invalid,name',
],
'privacy:valid',
],
'No fields specified' => [
'example',
[],
'privacy:example:valid',
],
];
}
/**
* Data provider with a list of valid string identifiers.
*
* @return array
*/
public function valid_string_provider() {
return [
'Valid combination' => [
'example',
[
'field' => 'privacy:example:valid:field',
'field2' => 'privacy:example:valid:field2',
],
'privacy:example:valid',
],
];
}
}
@@ -0,0 +1,144 @@
<?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/>.
/**
* Type unit tests for the External Location.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\metadata\types\external_location;
/**
* Tests for the \core_privacy API's types\external_location functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_metadata_types_external_location extends advanced_testcase {
/**
* Ensure that warnings are thrown if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param array $fields List of fields
* @param string $summary Summary
*/
public function test_invalid_configs($name, $fields, $summary) {
$record = new external_location($name, $fields, $summary);
$this->assertDebuggingCalled();
}
/**
* Ensure that warnings are not thrown if debugging is not enabled, even if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param array $fields List of fields
* @param string $summary Summary
*/
public function test_invalid_configs_debug_normal($name, $fields, $summary) {
global $CFG;
$this->resetAfterTest();
$CFG->debug = DEBUG_NORMAL;
$record = new external_location($name, $fields, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Ensure that no warnings are shown for valid combinations.
*
* @dataProvider valid_string_provider
* @param string $name Name
* @param array $fields List of fields
* @param string $summary Summary
*/
public function test_valid_configs($name, $fields, $summary) {
$record = new external_location($name, $fields, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Data provider with a list of invalid string identifiers.
*
* @return array
*/
public function invalid_string_provider() {
return [
'Space in summary' => [
'example',
[
'field' => 'privacy:valid',
],
'This table is used for purposes.',
],
'Comma in summary' => [
'example',
[
'field' => 'privacy:valid',
],
'privacy,foo',
],
'Space in field name' => [
'example',
[
'field' => 'This field is used for purposes.',
],
'privacy:valid',
],
'Comma in field name' => [
'example',
[
'field' => 'invalid,name',
],
'privacy:valid',
],
'No fields specified' => [
'example',
[],
'privacy:example:valid',
],
];
}
/**
* Data provider with a list of valid string identifiers.
*
* @return array
*/
public function valid_string_provider() {
return [
'Valid combination' => [
'example',
[
'field' => 'privacy:example:valid:field',
'field2' => 'privacy:example:valid:field2',
],
'privacy:example:valid',
],
];
}
}
@@ -0,0 +1,111 @@
<?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/>.
/**
* Types unit tests for the Plugintype Link.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\metadata\types\plugintype_link;
/**
* Tests for the \core_privacy API's types\plugintype_link functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_metadata_types_plugintype_link extends advanced_testcase {
/**
* Ensure that warnings are thrown if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_invalid_configs($name, $summary) {
$record = new plugintype_link($name, $summary);
$this->assertDebuggingCalled();
}
/**
* Ensure that warnings are not thrown if debugging is not enabled, even if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_invalid_configs_debug_normal($name, $summary) {
global $CFG;
$this->resetAfterTest();
$CFG->debug = DEBUG_NORMAL;
$record = new plugintype_link($name, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Ensure that no warnings are shown for valid combinations.
*
* @dataProvider valid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_valid_configs($name, $summary) {
$record = new plugintype_link($name, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Data provider with a list of invalid string identifiers.
*
* @return array
*/
public function invalid_string_provider() {
return [
'Space in summary' => [
'example',
'This table is used for purposes.',
],
'Comma in summary' => [
'example',
'privacy,foo',
],
];
}
/**
* Data provider with a list of valid string identifiers.
*
* @return array
*/
public function valid_string_provider() {
return [
'Valid combination' => [
'example',
'privacy:example:valid',
],
];
}
}
+111
View File
@@ -0,0 +1,111 @@
<?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/>.
/**
* Types unit tests for the Subsystem Link.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\metadata\types\subsystem_link;
/**
* Tests for the \core_privacy API's types\subsystem_link functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_metadata_types_subsystem_link extends advanced_testcase {
/**
* Ensure that warnings are thrown if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_invalid_configs($name, $summary) {
$record = new subsystem_link($name, $summary);
$this->assertDebuggingCalled();
}
/**
* Ensure that warnings are not thrown if debugging is not enabled, even if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_invalid_configs_debug_normal($name, $summary) {
global $CFG;
$this->resetAfterTest();
$CFG->debug = DEBUG_NORMAL;
$record = new subsystem_link($name, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Ensure that no warnings are shown for valid combinations.
*
* @dataProvider valid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_valid_configs($name, $summary) {
$record = new subsystem_link($name, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Data provider with a list of invalid string identifiers.
*
* @return array
*/
public function invalid_string_provider() {
return [
'Space in summary' => [
'example',
'This table is used for purposes.',
],
'Comma in summary' => [
'example',
'privacy,foo',
],
];
}
/**
* Data provider with a list of valid string identifiers.
*
* @return array
*/
public function valid_string_provider() {
return [
'Valid combination' => [
'example',
'privacy:example:valid',
],
];
}
}
@@ -0,0 +1,111 @@
<?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/>.
/**
* Types unit tests for the Subsystem Link.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\metadata\types\user_preference;
/**
* Tests for the \core_privacy API's types\user_preference functionality.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_privacy_metadata_types_user_preference extends advanced_testcase {
/**
* Ensure that warnings are thrown if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_invalid_configs($name, $summary) {
$record = new user_preference($name, $summary);
$this->assertDebuggingCalled();
}
/**
* Ensure that warnings are not thrown if debugging is not enabled, even if string identifiers contain invalid characters.
*
* @dataProvider invalid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_invalid_configs_debug_normal($name, $summary) {
global $CFG;
$this->resetAfterTest();
$CFG->debug = DEBUG_NORMAL;
$record = new user_preference($name, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Ensure that no warnings are shown for valid combinations.
*
* @dataProvider valid_string_provider
* @param string $name Name
* @param string $summary Summary
*/
public function test_valid_configs($name, $summary) {
$record = new user_preference($name, $summary);
$this->assertDebuggingNotCalled();
}
/**
* Data provider with a list of invalid string identifiers.
*
* @return array
*/
public function invalid_string_provider() {
return [
'Space in summary' => [
'example',
'This table is used for purposes.',
],
'Comma in summary' => [
'example',
'privacy,foo',
],
];
}
/**
* Data provider with a list of valid string identifiers.
*
* @return array
*/
public function valid_string_provider() {
return [
'Valid combination' => [
'example',
'privacy:example:valid',
],
];
}
}
+81
View File
@@ -0,0 +1,81 @@
<?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/>.
/**
* Unit Tests for the Moodle Content Writer.
*
* @package core_privacy
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
use \core_privacy\local\request\writer;
/**
* Tests for the \core_privacy API's moodle_content_writer functionality.
*
* Note: The \core_privacy\tests\request\content_writer will be used for these tests.
* This content writer has additional sugar methods for fetching infromation which are not part of the standard
* content_writer interface.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class writer_test extends advanced_testcase {
/**
* Test that calling with_context multiple times will return the same write instance.
*/
public function test_with_context() {
$writer = writer::with_context(\context_system::instance());
$this->assertSame($writer, writer::with_context(\context_system::instance()));
}
/**
* Test that calling with_context multiple times will return the same write instance.
*/
public function test_with_context_different_context_same_instance() {
$writer = writer::with_context(\context_system::instance());
$this->assertSame($writer, writer::with_context(\context_user::instance(\core_user::get_user_by_username('admin')->id)));
}
/**
* Test that calling writer::reset() causes a new copy of the writer to be returned.
*/
public function test_reset() {
$writer = writer::with_context(\context_system::instance());
writer::reset();
$this->assertNotSame($writer, writer::with_context(\context_system::instance()));
}
/**
* Test that the export_user_preference calls the writer against the system context.
*/
public function test_export_user_preference_sets_system_context() {
$writer = writer::with_context(\context_user::instance(\core_user::get_user_by_username('admin')->id));
writer::export_user_preference('core_test', 'key', 'value', 'description');
$this->assertSame(\context_system::instance(), $writer->get_current_context());
}
}
+135
View File
@@ -0,0 +1,135 @@
<?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/>.
/**
* Helpers for the core_rating subsystem implementation of privacy.
*
* @package core_rating
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_rating\phpunit;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\tests\request\content_writer;
global $CFG;
/**
* Helpers for the core_rating subsystem implementation of privacy.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait privacy_helper {
/**
* Fetch all ratings on a subcontext.
*
* @param \context $context The context being stored.
* @param array $subcontext The subcontext path to check.
* @return array
*/
protected function get_ratings_on_subcontext(\context $context, array $subcontext) {
$writer = \core_privacy\local\request\writer::with_context($context);
return $writer->get_related_data($subcontext, 'rating');
}
/**
* Check that all included ratings belong to the specified user.
*
* @param int $userid The ID of the user being stored.
* @param \context $context The context being stored.
* @param array $subcontext The subcontext path to check.
* @param string $component The component being stored.
* @param string $ratingarea The rating area to store results for.
* @param int $itemid The itemid to store.
*/
protected function assert_all_own_ratings_on_context(
int $userid,
\context $context,
array $subcontext,
$component,
$ratingarea,
$itemid
) {
$writer = \core_privacy\local\request\writer::with_context($context);
$rm = new \rating_manager();
$dbratings = $rm->get_all_ratings_for_item((object) [
'context' => $context,
'component' => $component,
'ratingarea' => $ratingarea,
'itemid' => $itemid,
]);
$exportedratings = $this->get_ratings_on_subcontext($context, $subcontext);
foreach ($exportedratings as $ratingid => $rating) {
$this->assertTrue(isset($dbratings[$ratingid]));
$this->assertEquals($userid, $rating->author);
$this->assert_rating_matches($dbratings[$ratingid], $rating);
}
foreach ($dbratings as $rating) {
if ($rating->userid == $userid) {
$this->assertEquals($rating->id, $ratingid);
}
}
}
/**
* Check that all included ratings are valid. They may belong to any user.
*
* @param \context $context The context being stored.
* @param array $subcontext The subcontext path to check.
* @param string $component The component being stored.
* @param string $ratingarea The rating area to store results for.
* @param int $itemid The itemid to store.
*/
protected function assert_all_ratings_on_context(\context $context, array $subcontext, $component, $ratingarea, $itemid) {
$writer = \core_privacy\local\request\writer::with_context($context);
$rm = new \rating_manager();
$dbratings = $rm->get_all_ratings_for_item((object) [
'context' => $context,
'component' => $component,
'ratingarea' => $ratingarea,
'itemid' => $itemid,
]);
$exportedratings = $this->get_ratings_on_subcontext($context, $subcontext);
foreach ($exportedratings as $ratingid => $rating) {
$this->assertTrue(isset($dbratings[$ratingid]));
$this->assert_rating_matches($dbratings[$ratingid], $rating);
}
foreach ($dbratings as $rating) {
$this->assertTrue(isset($exportedratings[$rating->id]));
}
}
/**
* Assert that the rating matches.
*
* @param \stdClass $expected The expected rating structure
* @param \stdClass $stored The actual rating structure
*/
protected function assert_rating_matches($expected, $stored) {
$this->assertEquals($expected->rating, $stored->rating);
$this->assertEquals($expected->userid, $stored->author);
}
}
+162
View File
@@ -0,0 +1,162 @@
<?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_ratings.
*
* @package core_rating
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_rating\privacy;
use \core_privacy\local\metadata\collection;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/rating/lib.php');
/**
* Privacy Subsystem implementation for core_ratings.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// The ratings subsystem contains data.
\core_privacy\local\metadata\provider,
// The ratings subsystem is only ever used to store data for other components.
// It does not store any data of its own and does not need to implement the \core_privacy\local\request\subsystem\provider
// as a result.
// The ratings subsystem provides a data service to other components.
\core_privacy\local\request\subsystem\plugin_provider {
/**
* Returns metadata about the ratings subsystem.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through the subsystem.
*/
public static function get_metadata(collection $collection) : collection {
// The table 'rating' cotains data that a user has entered.
// It stores the user-entered rating alongside a mapping to describe what was mapped.
$collection->add_database_table('rating', [
'rating' => 'privacy:metadata:rating:rating',
'userid' => 'privacy:metadata:rating:userid',
'timecreated' => 'privacy:metadata:rating:timecreated',
'timemodified' => 'privacy:metadata:rating:timemodified',
], 'privacy:metadata:rating');
return $collection;
}
/**
* Export all ratings which match the specified component, areaid, and itemid.
*
* If requesting ratings for a users own content, and you wish to include all ratings of that content, specify
* $onlyuser as false.
*
* When requesting ratings for another users content, you should only export the ratings that the specified user
* made themselves.
*
* @param int $userid The user whose information is to be exported
* @param \context $context The context being stored.
* @param array $subcontext The subcontext within the context to export this information
* @param string $component The component to fetch data from
* @param string $ratingarea The ratingarea that the data was stored in within the component
* @param int $itemid The itemid within that ratingarea
* @param bool $onlyuser Whether to only export ratings that the current user has made, or all ratings
*/
public static function export_area_ratings(
int $userid,
\context $context,
array $subcontext,
string $component,
string $ratingarea,
int $itemid,
bool $onlyuser = true
) {
global $DB;
$rm = new \rating_manager();
$ratings = $rm->get_all_ratings_for_item((object) [
'context' => $context,
'component' => $component,
'ratingarea' => $ratingarea,
'itemid' => $itemid,
]);
if ($onlyuser) {
$ratings = array_filter($ratings, function($rating) use ($userid){
return ($rating->userid == $userid);
});
}
if (empty($ratings)) {
return;
}
$toexport = array_map(function($rating) {
return (object) [
'rating' => $rating->rating,
'author' => $rating->userid,
];
}, $ratings);
$writer = \core_privacy\local\request\writer::with_context($context)
->export_related_data($subcontext, 'rating', $toexport);
}
/**
* Get the SQL required to find all submission items where this user has had any involvements.
*
* @param string $alias The name of the table alias to use.
* @param string $component The na eof the component to fetch ratings for.
* @param string $ratingarea The rating area to fetch results for.
* @param string $itemidjoin The right-hand-side of the JOIN ON clause.
* @param int $userid The ID of the user being stored.
* @return \stdClass
*/
public static function get_sql_join($alias, $component, $ratingarea, $itemidjoin, $userid) {
static $count = 0;
$count++;
// Join the rating table with the specified alias and the relevant join params.
$join = "LEFT JOIN {rating} {$alias} ON ";
$join .= "{$alias}.component = :ratingcomponent{$count} AND ";
$join .= "{$alias}.ratingarea = :ratingarea{$count} AND ";
$join .= "{$alias}.itemid = {$itemidjoin}";
// Match against the specified user.
$userwhere = "{$alias}.userid = :ratinguserid{$count}";
$params = [
'ratingcomponent' . $count => $component,
'ratingarea' . $count => $ratingarea,
'ratinguserid' . $count => $userid,
];
$return = (object) [
'join' => $join,
'params' => $params,
'userwhere' => $userwhere,
];
return $return;
}
}
+266
View File
@@ -0,0 +1,266 @@
<?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/>.
/**
* Unit tests for the core_rating implementation of the Privacy API.
*
* @package core_rating
* @category test
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/rating/lib.php');
use \core_rating\privacy\provider;
use \core_privacy\local\request\writer;
/**
* Unit tests for the core_rating implementation of the Privacy API.
*
* @copyright 2018 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_rating_privacy_testcase extends \core_privacy\tests\provider_testcase {
/**
* Rate something as a user.
*
* @param int $userid
* @param string $component
* @param string $ratingarea
* @param int $itemid
* @param \context $context
* @param string $score
*/
protected function rate_as_user($userid, $component, $ratingarea, $itemid, $context, $score) {
// Rate the courses.
$rm = new rating_manager();
$ratingoptions = (object) [
'component' => $component,
'ratingarea' => $ratingarea,
'scaleid' => 100,
];
// Rate all courses as u1, and the course category too..
$ratingoptions->itemid = $itemid;
$ratingoptions->userid = $userid;
$ratingoptions->context = $context;
$rating = new \rating($ratingoptions);
$rating->update_rating($score);
}
/**
* Ensure that the get_sql_join function returns valid SQL which returns the correct list of rated itemids.
*/
public function test_get_sql_join() {
global $DB;
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
// Rate the courses.
$rm = new rating_manager();
$ratingoptions = (object) [
'component' => 'core_course',
'ratingarea' => 'course',
'scaleid' => 100,
];
// Rate all courses as u1, and something else in the same context.
$this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
$this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
$this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
$this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
// Rate course2 as u2, and something else in a different context/component..
$this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
$this->rate_as_user($u2->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 10);
// Return any course which the u1 has rated.
// u1 rated all three courses.
$ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u1->id);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(3, $courses);
$this->assertTrue(isset($courses[$course1->id]));
$this->assertTrue(isset($courses[$course2->id]));
$this->assertTrue(isset($courses[$course3->id]));
// User u1 rated files in course 3 only.
$ratingquery = provider::get_sql_join('r', 'core_course', 'files', 'c.id', $u1->id);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(1, $courses);
$this->assertFalse(isset($courses[$course1->id]));
$this->assertFalse(isset($courses[$course2->id]));
$this->assertTrue(isset($courses[$course3->id]));
// Return any course which the u2 has rated.
// User u2 rated only course 2.
$ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u2->id);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(1, $courses);
$this->assertFalse(isset($courses[$course1->id]));
$this->assertTrue(isset($courses[$course2->id]));
$this->assertFalse(isset($courses[$course3->id]));
// User u2 rated u3.
$ratingquery = provider::get_sql_join('r', 'user', 'user', 'u.id', $u2->id);
$sql = "SELECT u.id FROM {user} u {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$users = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(1, $users);
$this->assertFalse(isset($users[$u1->id]));
$this->assertFalse(isset($users[$u2->id]));
$this->assertTrue(isset($users[$u3->id]));
// Return any course which the u3 has rated.
// User u3 did not rate anything.
$ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u3->id);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(0, $courses);
$this->assertFalse(isset($courses[$course1->id]));
$this->assertFalse(isset($courses[$course2->id]));
$this->assertFalse(isset($courses[$course3->id]));
}
/**
* Ensure that export_area_ratings exports all ratings that a user has made, and all ratings for a users own content.
*/
public function test_export_area_ratings() {
global $DB;
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
// Rate the courses.
$rm = new rating_manager();
$ratingoptions = (object) [
'component' => 'core_course',
'ratingarea' => 'course',
'scaleid' => 100,
];
// Rate all courses as u1, and something else in the same context.
$this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
$this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
$this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
$this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
$this->rate_as_user($u1->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 10);
// Rate course2 as u2, and something else in a different context/component..
$this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
$this->rate_as_user($u2->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 20);
// Test exports.
// User 1 rated all three courses, and the core_course, and user 3.
// User 1::course1 is stored in [] subcontext.
$context = \context_course::instance($course1->id);
$subcontext = [];
provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'course', $course1->id, true);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$rating = $writer->get_related_data($subcontext, 'rating');
$this->assert_has_rating($u1, 25, $rating);
// User 1::course2 is stored in ['foo'] subcontext.
$context = \context_course::instance($course2->id);
$subcontext = ['foo'];
provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'course', $course2->id, true);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$result = $writer->get_related_data($subcontext, 'rating');
$this->assertCount(1, $result);
$this->assert_has_rating($u1, 50, $result);
// User 1::course3 is stored in ['foo'] subcontext.
$context = \context_course::instance($course3->id);
$subcontext = ['foo'];
provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'course', $course3->id, true);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$result = $writer->get_related_data($subcontext, 'rating');
$this->assertCount(1, $result);
$this->assert_has_rating($u1, 75, $result);
// User 1::course3::files is stored in ['foo', 'files'] subcontext.
$context = \context_course::instance($course3->id);
$subcontext = ['foo', 'files'];
provider::export_area_ratings($u1->id, $context, $subcontext, 'core_course', 'files', $course3->id, true);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$result = $writer->get_related_data($subcontext, 'rating');
$this->assertCount(1, $result);
$this->assert_has_rating($u1, 99, $result);
// Both users 1 and 2 rated user 3.
// Exporting the data for user 3 should include both of those ratings.
$context = \context_user::instance($u3->id);
$subcontext = ['user'];
provider::export_area_ratings($u3->id, $context, $subcontext, 'user', 'user', $u3->id, false);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$result = $writer->get_related_data($subcontext, 'rating');
$this->assertCount(2, $result);
$this->assert_has_rating($u1, 10, $result);
$this->assert_has_rating($u2, 20, $result);
}
/**
* Assert that a user has the correct rating.
*
* @param \stdClass $author The user with the rating
* @param int $score The rating that was given
* @param \stdClass[] The ratings which were found
*/
protected function assert_has_rating($author, $score, $actual) {
$found = false;
foreach ($actual as $rating) {
if ($author->id == $rating->author) {
$found = true;
$this->assertEquals($score, $rating->rating);
}
}
$this->assertTrue($found);
}
}
+149
View File
@@ -0,0 +1,149 @@
<?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_tag.
*
* @package core_tag
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_tag\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
/**
* Privacy Subsystem implementation for core_tag.
*
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// Tags store user data.
\core_privacy\local\metadata\provider,
// The tag subsystem provides data to other components.
\core_privacy\local\request\subsystem\plugin_provider {
/**
* Returns meta data about this system.
*
* @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_metadata(collection $collection) : collection {
// The table 'tag' contains data that a user has entered.
// It is currently linked with a userid, but this field will hopefulyl go away.
// Note: The userid is not necessarily 100% accurate. See MDL-61555.
$collection->add_database_table('tag', [
'name' => 'privacy:metadata:tag:name',
'rawname' => 'privacy:metadata:tag:rawname',
'description' => 'privacy:metadata:tag:description',
'flag' => 'privacy:metadata:tag:flag',
'timemodified' => 'privacy:metadata:tag:timemodified',
'userid' => 'privacy:metadata:tag:userid',
], 'privacy:metadata:tag');
// The table 'tag_instance' contains user data.
// It links the user of a specific tag, to the item which is tagged.
// In some cases the userid who 'owns' the tag is also stored.
$collection->add_database_table('tag_instance', [
'tagid' => 'privacy:metadata:taginstance:tagid',
'ordering' => 'privacy:metadata:taginstance:ordering',
'timecreated' => 'privacy:metadata:taginstance:timecreated',
'timemodified' => 'privacy:metadata:taginstance:timemodified',
'tiuserid' => 'privacy:metadata:taginstance:tiuserid',
], 'privacy:metadata:taginstance');
// The table 'tag_area' does not contain any specific user data.
// It links components and item types to collections and describes how they can be associated.
// The table 'tag_coll' does not contain any specific user data.
// It describes a list of tag collections configured by the administrator.
// The table 'tag_correlation' does not contain any user data.
// It is a cache for other data already stored.
return $collection;
}
/**
* Store all tags which match the specified component, itemtype, and itemid.
*
* In most situations you will want to specify $onlyuser as false.
* This will fetch only tags where the user themselves set the tag, or where tags are a shared resource.
*
* If you specify $onlyuser as true, only the tags created by that user will be included.
*
* @param int $userid The user whose information is to be exported
* @param \context $context The context to export for
* @param array $subcontext The subcontext within the context to export this information
* @param string $component The component to fetch data from
* @param string $itemtype The itemtype that the data was exported in within the component
* @param int $itemid The itemid within that tag
* @param bool $onlyuser Whether to only export ratings that the current user has made, or all tags
*/
public static function export_item_tags(
int $userid,
\context $context,
array $subcontext,
string $component,
string $itemtype,
int $itemid,
bool $onlyuser = false
) {
global $DB;
// Do not include the mdl_tag userid data because of bug with re-using existing tags by other users.
$sql = "SELECT
t.id,
t.tagcollid,
t.name,
t.rawname,
t.isstandard,
t.description,
t.descriptionformat,
t.flag,
t.timemodified
FROM {tag} t
INNER JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.component = :component
AND ti.itemtype = :itemtype
AND ti.itemid = :itemid
";
if ($onlyuser) {
$sql .= "AND ti.tiuserid = :userid";
} else {
$sql .= "AND (ti.tiuserid = 0 OR ti.tiuserid = :userid)";
}
$params = [
'component' => $component,
'itemtype' => $itemtype,
'itemid' => $itemid,
'userid' => $userid,
];
if ($tags = $DB->get_records_sql($sql, $params)) {
$writer = \core_privacy\local\request\writer::with_context($context)
->export_related_data($subcontext, 'tags', $tags);
}
}
}
+89
View File
@@ -0,0 +1,89 @@
<?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 tests for core_tag.
*
* @package core_comment
* @category test
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/tag/lib.php');
use \core_privacy\tests\provider_testcase;
use \core_privacy\local\request\writer;
use \core_tag\privacy\provider;
/**
* Unit tests for tag/classes/privacy/policy
*
* @copyright 2018 Zig Tan <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_tag_privacy_testcase extends provider_testcase {
/**
* Check the exporting of tags for a user id in a context.
*/
public function test_export_tags() {
global $DB;
$this->resetAfterTest(true);
// Create a user to perform tagging.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// Create a course to tag.
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
$subcontext = [];
// Create three dummy tags and tag instances.
$dummytags = [ 'Tag 1', 'Tag 2', 'Tag 3' ];
core_tag_tag::set_item_tags('core_course', 'course', $course->id, context_course::instance($course->id),
$dummytags, $user->id);
// Get the tag instances that should have been created.
$taginstances = $DB->get_records('tag_instance', array('itemtype' => 'course', 'itemid' => $course->id));
$this->assertCount(count($dummytags), $taginstances);
// Check tag instances match the component and context.
foreach ($taginstances as $taginstance) {
$this->assertEquals('core_course', $taginstance->component);
$this->assertEquals(context_course::instance($course->id)->id, $taginstance->contextid);
}
// Retrieve tags only for this user.
provider::export_item_tags($user->id, $context, $subcontext, 'core_course', 'course', $course->id, true);
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$exportedtags = $writer->get_related_data($subcontext, 'tags');
$this->assertCount(count($dummytags), $exportedtags);
// Check the exported tag's rawname is found in the initial dummy tags.
foreach ($exportedtags as $exportedtag) {
$this->assertContains($exportedtag->rawname, $dummytags);
}
}
}