Skip to content

Commit

Permalink
MDL-62068 core_tag: implement privacy API
Browse files Browse the repository at this point in the history
  • Loading branch information
marinaglancy committed May 8, 2018
1 parent 630a72f commit 2207b0f
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 18 deletions.
6 changes: 1 addition & 5 deletions blog/tests/privacy_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,7 @@ public function test_export_data_for_user() {
$commentpath = array_merge($path, [get_string('commentsubcontext', 'core_comment')]);
if ($e->id == $e1->id) {
$tagdata = $writer->get_related_data($path, 'tags');
$this->assertCount(2, $tagdata);
$tag = array_shift($tagdata);
$this->assertEquals('Beer', $tag->rawname);
$tag = array_shift($tagdata);
$this->assertEquals('Golf', $tag->rawname);
$this->assertEquals(['Beer', 'Golf'], $tagdata, '', 0, 10, true);

$comments = $writer->get_data($commentpath);
$this->assertCount(2, $comments->comments);
Expand Down
110 changes: 98 additions & 12 deletions tag/classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
defined('MOODLE_INTERNAL') || die();

use \core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;

/**
* Privacy Subsystem implementation for core_tag.
Expand All @@ -39,7 +43,10 @@ class provider implements
\core_privacy\local\metadata\provider,

// The tag subsystem provides data to other components.
\core_privacy\local\request\subsystem\plugin_provider {
\core_privacy\local\request\subsystem\plugin_provider,

// The tag subsystem may have data that belongs to this user.
\core_privacy\local\request\plugin\provider {

/**
* Returns meta data about this system.
Expand Down Expand Up @@ -110,17 +117,9 @@ public static function export_item_tags(
) {
global $DB;

// Do not include the mdl_tag userid data because of bug with re-using existing tags by other users.
// Ignore mdl_tag.userid here because it only reflects the user who originally created the tag.
$sql = "SELECT
t.id,
t.tagcollid,
t.name,
t.rawname,
t.isstandard,
t.description,
t.descriptionformat,
t.flag,
t.timemodified
t.rawname
FROM {tag} t
INNER JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.component = :component
Expand All @@ -141,7 +140,7 @@ public static function export_item_tags(
'userid' => $userid,
];

if ($tags = $DB->get_records_sql($sql, $params)) {
if ($tags = $DB->get_fieldset_sql($sql, $params)) {
$writer = \core_privacy\local\request\writer::with_context($context)
->export_related_data($subcontext, 'tags', $tags);
}
Expand Down Expand Up @@ -194,4 +193,91 @@ public static function delete_item_tags_select(\context $context, $component, $i
'contextid = :contextid AND component = :component AND itemtype = :itemtype AND itemid ' . $itemidstest,
$params);
}

/**
* 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 {
$contextlist = new contextlist();
$contextlist->add_from_sql("SELECT c.id
FROM {context} c
JOIN {tag} t ON t.userid = :userid
WHERE contextlevel = :contextlevel",
['userid' => $userid, 'contextlevel' => CONTEXT_SYSTEM]);
return $contextlist;
}

/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
$context = \context_system::instance();
if (!$contextlist->count() || !in_array($context->id, $contextlist->get_contextids())) {
return;
}

$user = $contextlist->get_user();
$sql = "SELECT id, userid, tagcollid, name, rawname, isstandard, description, descriptionformat, flag, timemodified
FROM {tag} WHERE userid = ?";
$rs = $DB->get_recordset_sql($sql, [$user->id]);
foreach ($rs as $record) {
$subcontext = [get_string('tags', 'tag'), $record->id];
$tag = (object)[
'id' => $record->id,
'userid' => transform::user($record->userid),
'name' => $record->name,
'rawname' => $record->rawname,
'isstandard' => transform::yesno($record->isstandard),
'description' => writer::with_context($context)->rewrite_pluginfile_urls($subcontext,
'tag', 'description', $record->id, strval($record->description)),
'descriptionformat' => $record->descriptionformat,
'flag' => $record->flag,
'timemodified' => transform::datetime($record->timemodified),

];
writer::with_context($context)->export_data($subcontext, $tag);
writer::with_context($context)->export_area_files($subcontext, 'tag', 'description', $record->id);
}
$rs->close();
}

/**
* Delete all data for all users in the specified context.
*
* We do not delete tag instances in this method - this should be done by the components that define tagareas.
* We only delete tags themselves in case of system context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
// Tags can only be defined in system context.
if ($context->id == \context_system::instance()->id) {
$DB->delete_records('tag_instance');
$DB->delete_records('tag', []);
}
}

/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
$context = \context_system::instance();
if (!$contextlist->count() || !in_array($context->id, $contextlist->get_contextids())) {
return;
}

// Do not delete tags themselves in case they are used by somebody else.
// If the user is the only one using the tag, it will be automatically deleted anyway during the next cron cleanup.
$DB->set_field_select('tag', 'userid', 0, 'userid = ?', [$contextlist->get_user()->id]);
}
}
93 changes: 92 additions & 1 deletion tag/tests/privacy_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function test_export_tags() {

// Check the exported tag's rawname is found in the initial dummy tags.
foreach ($exportedtags as $exportedtag) {
$this->assertContains($exportedtag->rawname, $dummytags);
$this->assertContains($exportedtag, $dummytags);
}
}

Expand Down Expand Up @@ -151,4 +151,95 @@ public function test_delete_item_tags_select() {
$expectedtagcount -= 2;
$this->assertEquals($expectedtagcount, $DB->count_records('tag_instance'));
}

protected function set_up_tags() {
global $CFG;
require_once($CFG->dirroot.'/user/editlib.php');

$this->resetAfterTest(true);

$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();

$this->setUser($user1);
useredit_update_interests($user1, ['Birdwatching', 'Computers']);

$this->setUser($user2);
useredit_update_interests($user2, ['computers']);

$this->setAdminUser();

$tag = core_tag_tag::get_by_name(0, 'computers', '*');
$tag->update(['description' => '<img src="@@PLUGINFILE@@/computer.jpg">']);
get_file_storage()->create_file_from_string([
'contextid' => context_system::instance()->id,
'component' => 'tag',
'filearea' => 'description',
'itemid' => $tag->id,
'filepath' => '/',
'filename' => 'computer.jpg'
], "jpg:image");

return [$user1, $user2];
}

public function test_export_item_tags() {
list($user1, $user2) = $this->set_up_tags();
$this->assertEquals([context_system::instance()->id],
provider::get_contexts_for_userid($user1->id)->get_contextids());
$this->assertEmpty(provider::get_contexts_for_userid($user2->id)->get_contextids());
}

public function test_delete_data_for_user() {
global $DB;
list($user1, $user2) = $this->set_up_tags();
$context = context_system::instance();
$this->assertEquals(2, $DB->count_records('tag', []));
$this->assertEquals(0, $DB->count_records('tag', ['userid' => 0]));
provider::delete_data_for_user(new \core_privacy\local\request\approved_contextlist($user2, 'core_tag', [$context->id]));
$this->assertEquals(2, $DB->count_records('tag', []));
$this->assertEquals(0, $DB->count_records('tag', ['userid' => 0]));
provider::delete_data_for_user(new \core_privacy\local\request\approved_contextlist($user1, 'core_tag', [$context->id]));
$this->assertEquals(2, $DB->count_records('tag', []));
$this->assertEquals(2, $DB->count_records('tag', ['userid' => 0]));
}

public function test_delete_data_for_all_users_in_context() {
global $DB;
$course = $this->getDataGenerator()->create_course();
list($user1, $user2) = $this->set_up_tags();
$this->assertEquals(2, $DB->count_records('tag', []));
$this->assertEquals(3, $DB->count_records('tag_instance', []));
provider::delete_data_for_all_users_in_context(context_course::instance($course->id));
$this->assertEquals(2, $DB->count_records('tag', []));
$this->assertEquals(3, $DB->count_records('tag_instance', []));
provider::delete_data_for_all_users_in_context(context_system::instance());
$this->assertEquals(0, $DB->count_records('tag', []));
$this->assertEquals(0, $DB->count_records('tag_instance', []));
}

public function test_export_data_for_user() {
global $DB;
list($user1, $user2) = $this->set_up_tags();
$context = context_system::instance();
provider::export_user_data(new \core_privacy\local\request\approved_contextlist($user2, 'core_tag', [$context->id]));
$this->assertFalse(writer::with_context($context)->has_any_data());

$tagids = array_values(array_map(function($tag) {
return $tag->id;
}, core_tag_tag::get_by_name_bulk(core_tag_collection::get_default(), ['Birdwatching', 'Computers'])));

provider::export_user_data(new \core_privacy\local\request\approved_contextlist($user1, 'core_tag', [$context->id]));
$writer = writer::with_context($context);

$data = $writer->get_data(['Tags', $tagids[0]]);
$files = $writer->get_files(['Tags', $tagids[0]]);
$this->assertEquals('Birdwatching', $data->rawname);
$this->assertEmpty($files);

$data = $writer->get_data(['Tags', $tagids[1]]);
$files = $writer->get_files(['Tags', $tagids[1]]);
$this->assertEquals('Computers', $data->rawname);
$this->assertEquals(['computer.jpg'], array_keys($files));
}
}

0 comments on commit 2207b0f

Please sign in to comment.