From ce1ec9b4d96f0a6930bcd88c0e6701a88181bdaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Massart?= Date: Wed, 4 Apr 2018 13:16:44 +0800 Subject: [PATCH] MDL-61836 core_blog: Implement privacy API --- blog/classes/privacy/provider.php | 469 ++++++++++++++++++++ blog/tests/privacy_test.php | 694 ++++++++++++++++++++++++++++++ lang/en/blog.php | 23 + 3 files changed, 1186 insertions(+) create mode 100644 blog/classes/privacy/provider.php create mode 100644 blog/tests/privacy_test.php diff --git a/blog/classes/privacy/provider.php b/blog/classes/privacy/provider.php new file mode 100644 index 00000000000..7ec3696ea56 --- /dev/null +++ b/blog/classes/privacy/provider.php @@ -0,0 +1,469 @@ +. + +/** + * Data provider. + * + * @package core_blog + * @copyright 2018 Frédéric Massart + * @author Frédéric Massart + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_blog\privacy; +defined('MOODLE_INTERNAL') || die(); + +use blog_entry; +use context; +use context_helper; +use context_user; +use context_system; +use core_tag_tag; +use core_privacy\local\metadata\collection; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; + +require_once($CFG->dirroot . '/blog/locallib.php'); + +/** + * Data provider class. + * + * @package core_blog + * @copyright 2018 Frédéric Massart + * @author Frédéric Massart + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\subsystem\provider { + + /** + * Returns metadata. + * + * @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('post', [ + 'userid' => 'privacy:metadata:post:userid', + 'subject' => 'privacy:metadata:post:subject', + 'summary' => 'privacy:metadata:post:summary', + 'uniquehash' => 'privacy:metadata:post:uniquehash', + 'publishstate' => 'privacy:metadata:post:publishstate', + 'created' => 'privacy:metadata:post:created', + 'lastmodified' => 'privacy:metadata:post:lastmodified', + + // The following columns are unused: + // coursemoduleid, courseid, moduleid, groupid, rating, usermodified. + ], 'privacy:metadata:post'); + + $collection->link_subsystem('core_comments', 'privacy:metadata:core_comments'); + $collection->link_subsystem('core_files', 'privacy:metadata:core_files'); + $collection->link_subsystem('core_tag', 'privacy:metadata:core_tag'); + + $collection->add_database_table('blog_external', [ + 'userid' => 'privacy:metadata:external:userid', + 'name' => 'privacy:metadata:external:name', + 'description' => 'privacy:metadata:external:description', + 'url' => 'privacy:metadata:external:url', + 'filtertags' => 'privacy:metadata:external:filtertags', + 'timemodified' => 'privacy:metadata:external:timemodified', + 'timefetched' => 'privacy:metadata:external:timefetched', + ], 'privacy:metadata:external'); + + // We do not report on blog_association because this is just context-related data. + + 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) : \core_privacy\local\request\contextlist { + global $DB; + $contextlist = new \core_privacy\local\request\contextlist(); + + // There are at least one blog post. + if ($DB->record_exists_select('post', 'userid = :userid AND module IN (:blog, :blogext)', [ + 'userid' => $userid, 'blog' => 'blog', 'blogext' => 'blog_external'])) { + $sql = " + SELECT ctx.id + FROM {context} ctx + WHERE ctx.contextlevel = :ctxlevel + AND ctx.instanceid = :ctxuserid"; + $params = [ + 'ctxlevel' => CONTEXT_USER, + 'ctxuserid' => $userid, + ]; + $contextlist->add_from_sql($sql, $params); + + // Add the associated context of the blog posts. + $sql = " + SELECT DISTINCT ctx.id + FROM {post} p + JOIN {blog_association} ba + ON ba.blogid = p.id + JOIN {context} ctx + ON ctx.id = ba.contextid + WHERE p.userid = :userid"; + $params = [ + 'userid' => $userid, + ]; + $contextlist->add_from_sql($sql, $params); + } + + // If there is at least one external blog, we add the user context. This is done this + // way because we can't directly add context to a contextlist. + if ($DB->record_exists('blog_external', ['userid' => $userid])) { + $sql = " + SELECT ctx.id + FROM {context} + WHERE ctx.contextlevel = :ctxlevel + AND ctx.instanceid = :ctxuserid"; + $params = [ + 'ctxlevel' => CONTEXT_USER, + 'ctxuserid' => $userid, + ]; + $contextlist->add_from_sql($sql, $params); + } + + // Include the user contexts in which the user comments. + $sql = " + SELECT DISTINCT ctx.id + FROM {context} ctx + JOIN {comments} c + ON c.contextid = ctx.id + WHERE c.component = :component + AND c.commentarea = :commentarea + AND c.userid = :userid"; + $params = [ + 'component' => 'blog', + 'commentarea' => 'format_blog', + 'userid' => $userid + ]; + $contextlist->add_from_sql($sql, $params); + + return $contextlist; + } + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + global $DB; + + $sysctx = context_system::instance(); + $fs = get_file_storage(); + $userid = $contextlist->get_user()->id; + $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); + $rootpath = [get_string('blog', 'core_blog')]; + $associations = []; + + foreach ($contextlist as $context) { + switch ($context->contextlevel) { + case CONTEXT_USER: + $contextuserid = $context->instanceid; + $insql = ' > 0'; + $inparams = []; + + if ($contextuserid != $userid) { + // We will only be exporting comments, so fetch the IDs of the relevant entries. + $entryids = $DB->get_fieldset_sql(" + SELECT DISTINCT c.itemid + FROM {comments} c + WHERE c.contextid = :contextid + AND c.userid = :userid + AND c.component = :component + AND c.commentarea = :commentarea", [ + 'contextid' => $context->id, + 'userid' => $userid, + 'component' => 'blog', + 'commentarea' => 'format_blog' + ]); + + if (empty($entryids)) { + // This should not happen, as the user context should not have been reported then. + continue; + } + + list($insql, $inparams) = $DB->get_in_or_equal($entryids, SQL_PARAMS_NAMED); + } + + // Loop over each blog entry in context. + $sql = "userid = :userid AND module IN (:blog, :blogext) AND id $insql"; + $params = array_merge($inparams, ['userid' => $contextuserid, 'blog' => 'blog', 'blogext' => 'blog_external']); + $recordset = $DB->get_recordset_select('post', $sql, $params); + foreach ($recordset as $record) { + + $subject = format_string($record->subject); + $path = array_merge($rootpath, [get_string('blogentries', 'core_blog'), $subject . " ({$record->id})"]); + + // If the context is not mine, then we ONLY export the comments made by the exporting user. + if ($contextuserid != $userid) { + \core_comment\privacy\provider::export_comments($context, 'blog', 'format_blog', + $record->id, $path, true); + continue; + } + + // Manually export the files as they reside in the system context so we can't use + // the write's helper methods. The same happens for attachments. + foreach ($fs->get_area_files($sysctx->id, 'blog', 'post', $record->id) as $f) { + writer::with_context($context)->export_file($path, $f); + } + foreach ($fs->get_area_files($sysctx->id, 'blog', 'attachment', $record->id) as $f) { + writer::with_context($context)->export_file($path, $f); + } + + // Rewrite the summary files. + $summary = writer::with_context($context)->rewrite_pluginfile_urls($path, 'blog', 'post', + $record->id, $record->summary); + + // Fetch associations. + $assocs = []; + $sql = "SELECT ba.contextid, $ctxfields + FROM {blog_association} ba + JOIN {context} ctx + ON ba.contextid = ctx.id + WHERE ba.blogid = :blogid"; + $assocset = $DB->get_recordset_sql($sql, ['blogid' => $record->id]); + foreach ($assocset as $assocrec) { + context_helper::preload_from_record($assocrec); + $assocctx = context::instance_by_id($assocrec->contextid); + $assocs[] = $assocctx->get_context_name(); + } + $assocset->close(); + + // Export associated tags. + \core_tag\privacy\provider::export_item_tags($userid, $context, $path, 'core', 'post', $record->id); + + // Export all comments made on my post. + \core_comment\privacy\provider::export_comments($context, 'blog', 'format_blog', + $record->id, $path, false); + + // Add blog entry data. + $entry = (object) [ + 'subject' => $subject, + 'summary' => format_text($summary, $record->summaryformat), + 'uniquehash' => $record->uniquehash, + 'publishstate' => static::transform_publishstate($record->publishstate), + 'created' => transform::datetime($record->created), + 'lastmodified' => transform::datetime($record->lastmodified), + 'associations' => $assocs + ]; + + writer::with_context($context)->export_data($path, $entry); + } + $recordset->close(); + + // Export external blogs. + $recordset = $DB->get_recordset('blog_external', ['userid' => $userid]); + foreach ($recordset as $record) { + + $path = array_merge($rootpath, [get_string('externalblogs', 'core_blog'), + $record->name . " ({$record->id})"]); + + // Export associated tags. + \core_tag\privacy\provider::export_item_tags($userid, $context, $path, 'core', + 'blog_external', $record->id); + + // Add data. + $external = (object) [ + 'name' => $record->name, + 'description' => $record->description, + 'url' => $record->url, + 'filtertags' => $record->filtertags, + 'modified' => transform::datetime($record->timemodified), + 'lastfetched' => transform::datetime($record->timefetched), + ]; + + writer::with_context($context)->export_data($path, $external); + } + $recordset->close(); + break; + + case CONTEXT_COURSE: + case CONTEXT_MODULE: + $associations[] = $context->id; + break; + } + } + + // Export associations. + if (!empty($associations)) { + list($insql, $inparams) = $DB->get_in_or_equal($associations, SQL_PARAMS_NAMED); + $sql = " + SELECT ba.contextid, p.subject, $ctxfields + FROM {post} p + JOIN {blog_association} ba + ON ba.blogid = p.id + JOIN {context} ctx + ON ctx.id = ba.contextid + WHERE ba.contextid $insql + AND p.userid = :userid + ORDER BY ba.contextid ASC"; + $params = array_merge($inparams, ['userid' => $userid]); + + $path = [get_string('privacy:path:blogassociations', 'core_blog')]; + + $flushassocs = function($context, $assocs) use ($path) { + writer::with_context($context)->export_data($path, (object) [ + 'associations' => $assocs + ]); + }; + + $lastcontextid = null; + $assocs = []; + $recordset = $DB->get_recordset_sql($sql, $params); + foreach ($recordset as $record) { + context_helper::preload_from_record($record); + + if ($lastcontextid && $record->contextid != $lastcontextid) { + $flushassocs(context::instance_by_id($lastcontextid), $assocs); + $assocs = []; + } + $assocs[] = format_string($record->subject); + $lastcontextid = $record->contextid; + } + + if ($lastcontextid) { + $flushassocs(context::instance_by_id($lastcontextid), $assocs); + } + + $recordset->close(); + } + } + + /** + * Delete all data for all users in the specified context. + * + * @param context $context The specific context to delete data for. + */ + public static function delete_data_for_all_users_in_context(context $context) { + global $DB; + switch ($context->contextlevel) { + case CONTEXT_USER: + static::delete_all_user_data($context); + break; + + case CONTEXT_COURSE: + case CONTEXT_MODULE: + // We only delete associations here. + $DB->delete_records('blog_association', ['contextid' => $context->id]); + break; + } + + // Delete all the comments. + \core_comment\privacy\provider::delete_comments_for_all_users_in_context($context); + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + global $DB; + + $userid = $contextlist->get_user()->id; + $associationcontextids = []; + + foreach ($contextlist as $context) { + if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) { + static::delete_all_user_data($context); + + } else if ($context->contextlevel == CONTEXT_COURSE) { + // Only delete the course associations. + $associationcontextids[] = $context->id; + + } else if ($context->contextlevel == CONTEXT_MODULE) { + // Only delete the module associations. + $associationcontextids[] = $context->id; + } + } + + // Delete the associations. + if (!empty($associationcontextids)) { + list($insql, $inparams) = $DB->get_in_or_equal($associationcontextids, SQL_PARAMS_NAMED); + $sql = "SELECT ba.id + FROM {blog_association} ba + JOIN {post} p + ON p.id = ba.blogid + WHERE ba.contextid $insql + AND p.userid = :userid"; + $params = array_merge($inparams, ['userid' => $userid]); + $associds = $DB->get_fieldset_sql($sql, $params); + + list($insql, $inparams) = $DB->get_in_or_equal($associds, SQL_PARAMS_NAMED, 'param', true); + $DB->delete_records_select('blog_association', "id $insql", $inparams); + } + + // Delete the comments. + \core_comment\privacy\provider::delete_comments_for_user($contextlist); + } + + /** + * Helper method to delete all user data. + * + * @param context_user $usercontext The user context. + * @return void + */ + protected static function delete_all_user_data(context_user $usercontext) { + global $DB; + $userid = $usercontext->instanceid; + + // Delete all blog posts. + $recordset = $DB->get_recordset_select('post', 'userid = :userid AND module IN (:blog, :blogext)', [ + 'userid' => $userid, 'blog' => 'blog', 'blogext' => 'blog_external']); + foreach ($recordset as $record) { + $entry = new blog_entry(null, $record); + $entry->delete(); // Takes care of files and associations. + } + $recordset->close(); + + // Delete all external blogs, and their associated tags. + $DB->delete_records('blog_external', ['userid' => $userid]); + core_tag_tag::delete_instances('core', 'blog_external', $usercontext->id); + + // Delete all comments made in this context. + $DB->delete_records('comments', ['contextid' => $usercontext->id, 'component' => 'blog', 'commentarea' => 'format_blog']); + } + + /** + * Transform a publish state. + * + * @param string $publishstate The publish state. + * @return string + */ + public static function transform_publishstate($publishstate) { + switch ($publishstate) { + case 'draft': + return get_string('publishtonoone', 'core_blog'); + case 'site': + return get_string('publishtosite', 'core_blog'); + case 'public': + return get_string('publishtoworld', 'core_blog'); + default: + } + return get_string('privacy:unknown', 'core_blog'); + } +} diff --git a/blog/tests/privacy_test.php b/blog/tests/privacy_test.php new file mode 100644 index 00000000000..65f772ad362 --- /dev/null +++ b/blog/tests/privacy_test.php @@ -0,0 +1,694 @@ +. + +/** + * Data provider tests. + * + * @package core_blog + * @category test + * @copyright 2018 Frédéric Massart + * @author Frédéric Massart + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); +global $CFG; + +use core_privacy\tests\provider_testcase; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\transform; +use core_privacy\local\request\writer; +use core_blog\privacy\provider; + +require_once($CFG->dirroot . '/blog/locallib.php'); +require_once($CFG->dirroot . '/comment/lib.php'); + +/** + * Data provider testcase class. + * + * @package core_blog + * @category test + * @copyright 2018 Frédéric Massart + * @author Frédéric Massart + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_blog_privacy_testcase extends provider_testcase { + + public function setUp() { + $this->resetAfterTest(); + } + + public function test_get_contexts_for_userid() { + $dg = $this->getDataGenerator(); + $c1 = $dg->create_course(); + $c2 = $dg->create_course(); + $c3 = $dg->create_course(); + $cm1a = $dg->create_module('page', ['course' => $c1]); + $cm1b = $dg->create_module('page', ['course' => $c1]); + $cm2a = $dg->create_module('page', ['course' => $c2]); + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + $u1ctx = context_user::instance($u1->id); + + // Blog share a table with notes, so throw data in there and make sure it doesn't get reported. + $dg->get_plugin_generator('core_notes')->create_instance(['userid' => $u1->id, 'courseid' => $c3->id]); + + $this->assertEmpty(provider::get_contexts_for_userid($u1->id)->get_contextids()); + $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); + + // Gradually create blog posts for user 1. First system one. + $this->create_post(['userid' => $u1->id]); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(1, $contextids); + $this->assertEquals($u1ctx->id, $contextids[0]); + $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); + + // Create a blog post associated with c1. + $post = $this->create_post(['userid' => $u1->id, 'courseid' => $c1->id]); + $entry = new blog_entry($post->id); + $entry->add_association(context_course::instance($c1->id)->id); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(2, $contextids); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + $this->assertTrue(in_array(context_course::instance($c1->id)->id, $contextids)); + $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); + + // Create a blog post associated with cm2a. + $post = $this->create_post(['userid' => $u1->id, 'courseid' => $c2->id]); + $entry = new blog_entry($post->id); + $entry->add_association(context_module::instance($cm2a->cmid)->id); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(3, $contextids); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + $this->assertTrue(in_array(context_course::instance($c1->id)->id, $contextids)); + $this->assertTrue(in_array(context_module::instance($cm2a->cmid)->id, $contextids)); + $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); + + // User 2 comments on u1's post. + $comment = $this->get_comment_object($u1ctx, $post->id); + $this->setUser($u2); + $comment->add('Hello, it\'s me!'); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(3, $contextids); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + $this->assertTrue(in_array(context_course::instance($c1->id)->id, $contextids)); + $this->assertTrue(in_array(context_module::instance($cm2a->cmid)->id, $contextids)); + $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); + $this->assertCount(1, $contextids); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + } + + public function test_get_contexts_for_userid_with_one_associated_post_only() { + $dg = $this->getDataGenerator(); + $c1 = $dg->create_course(); + $u1 = $dg->create_user(); + $u1ctx = context_user::instance($u1->id); + + $this->assertEmpty(provider::get_contexts_for_userid($u1->id)->get_contextids()); + + // Create a blog post associated with c1. It should always return both the course and user context. + $post = $this->create_post(['userid' => $u1->id, 'courseid' => $c1->id]); + $entry = new blog_entry($post->id); + $entry->add_association(context_course::instance($c1->id)->id); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(2, $contextids); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + $this->assertTrue(in_array(context_course::instance($c1->id)->id, $contextids)); + } + + public function test_delete_data_for_user() { + global $DB; + + $dg = $this->getDataGenerator(); + $c1 = $dg->create_course(); + $c2 = $dg->create_course(); + $cm1a = $dg->create_module('page', ['course' => $c1]); + $cm1b = $dg->create_module('page', ['course' => $c1]); + $cm2a = $dg->create_module('page', ['course' => $c2]); + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + $u3 = $dg->create_user(); + + $c1ctx = context_course::instance($c1->id); + $c2ctx = context_course::instance($c2->id); + $cm1actx = context_module::instance($cm1a->cmid); + $cm1bctx = context_module::instance($cm1b->cmid); + $cm2actx = context_module::instance($cm2a->cmid); + $u1ctx = context_user::instance($u1->id); + $u2ctx = context_user::instance($u2->id); + + // Blog share a table with notes, so throw data in there and make sure it doesn't get deleted. + $this->assertFalse($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + $dg->get_plugin_generator('core_notes')->create_instance(['userid' => $u1->id, 'courseid' => $c1->id]); + $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + + // Create two external blogs. + $extu1 = $this->create_external_blog(['userid' => $u1->id]); + $extu2 = $this->create_external_blog(['userid' => $u2->id]); + + // Create a set of posts. + $entry = new blog_entry($this->create_post(['userid' => $u1->id])->id); + $commentedon = $entry; + $entry = new blog_entry($this->create_post(['userid' => $u2->id])->id); + + // Two course associations for u1. + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($c1ctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($c1ctx->id); + + // Two module associations with cm1a, and 1 with cm1b for u1. + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1actx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1actx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1bctx->id); + + // One association for u2 in c1, cm1a and cm2a. + $entry = new blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); + $entry->add_association($c1ctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1actx->id); + $entry = new blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c2->id])->id); + $entry->add_association($cm2actx->id); + + // One association for u1 in c2 and cm2a. + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c2->id])->id); + $entry->add_association($c2ctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c2->id])->id); + $entry->add_association($cm2actx->id); + + // Add comments. + $comment = $this->get_comment_object($u1ctx, $commentedon->id); + $this->setUser($u1); + $comment->add('Hello, it\'s me!'); + $comment->add('I was wondering...'); + $this->setUser($u2); + $comment->add('If after all these years'); + $this->setUser($u3); + $comment->add('You\'d like to meet'); + + // Assert current setup. + $this->assertCount(6, provider::get_contexts_for_userid($u1->id)->get_contextids()); + $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + $this->assertCount(2, $DB->get_records('comments', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('comments', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('comments', ['userid' => $u3->id])); + + // Delete for u1 in cm1a. + $appctxs = new approved_contextlist($u1, 'core_blog', [$cm1actx->id]); + provider::delete_data_for_user($appctxs); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(5, $contextids); + $this->assertFalse(in_array($cm1actx->id, $contextids)); + $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + + // Delete for u1 in c1. + $appctxs = new approved_contextlist($u1, 'core_blog', [$c1ctx->id]); + provider::delete_data_for_user($appctxs); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(4, $contextids); + $this->assertFalse(in_array($c1ctx->id, $contextids)); + $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + + // Delete for u1 in c2. + $appctxs = new approved_contextlist($u1, 'core_blog', [$c2ctx->id]); + provider::delete_data_for_user($appctxs); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(3, $contextids); + $this->assertFalse(in_array($c2ctx->id, $contextids)); + $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + + // Delete for u1 in another user's context, shouldn't do anything. + provider::delete_data_for_user(new approved_contextlist($u1, 'core_blog', [$u2ctx->id])); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(3, $contextids); + $this->assertFalse(in_array($c2ctx->id, $contextids)); + $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + $this->assertCount(2, $DB->get_records('comments', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('comments', ['userid' => $u2->id])); + + // Delete for u2 in u1 context. + provider::delete_data_for_user(new approved_contextlist($u2, 'core_blog', [$u1ctx->id])); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(3, $contextids); + $this->assertFalse(in_array($c2ctx->id, $contextids)); + $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(4, provider::get_contexts_for_userid($u2->id)->get_contextids()); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + $this->assertCount(2, $DB->get_records('comments', ['userid' => $u1->id])); + $this->assertCount(0, $DB->get_records('comments', ['userid' => $u2->id])); + $this->assertCount(1, $DB->get_records('comments', ['userid' => $u3->id])); + + // Delete for u1 in their context. + $appctxs = new approved_contextlist($u1, 'core_blog', [$u1ctx->id]); + provider::delete_data_for_user($appctxs); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(0, $contextids); + $this->assertCount(1, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(4, provider::get_contexts_for_userid($u2->id)->get_contextids()); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(0, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + $this->assertCount(0, $DB->get_records('comments', ['userid' => $u1->id])); + $this->assertCount(0, $DB->get_records('comments', ['userid' => $u2->id])); + $this->assertCount(0, $DB->get_records('comments', ['userid' => $u3->id])); + $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); + } + + public function test_delete_data_for_all_users_in_context() { + global $DB; + + $dg = $this->getDataGenerator(); + $c1 = $dg->create_course(); + $c2 = $dg->create_course(); + $cm1a = $dg->create_module('page', ['course' => $c1]); + $cm1b = $dg->create_module('page', ['course' => $c1]); + $cm2a = $dg->create_module('page', ['course' => $c2]); + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + + $c1ctx = context_course::instance($c1->id); + $c2ctx = context_course::instance($c2->id); + $cm1actx = context_module::instance($cm1a->cmid); + $cm1bctx = context_module::instance($cm1b->cmid); + $cm2actx = context_module::instance($cm2a->cmid); + $u1ctx = context_user::instance($u1->id); + + // Create two external blogs. + $extu1 = $this->create_external_blog(['userid' => $u1->id]); + $extu2 = $this->create_external_blog(['userid' => $u2->id]); + + // Create a set of posts. + $entry = new blog_entry($this->create_post(['userid' => $u1->id])->id); + $entry = new blog_entry($this->create_post(['userid' => $u2->id])->id); + + // Course associations for u1 and u2. + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($c1ctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($c1ctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); + $entry->add_association($c1ctx->id); + + // Module associations for u1 and u2. + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1actx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1actx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1bctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); + $entry->add_association($cm1actx->id); + + // Foreign associations for u1, u2. + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c2->id])->id); + $entry->add_association($c2ctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c2->id])->id); + $entry->add_association($c2ctx->id); + $entry = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $cm2a->id])->id); + $entry->add_association($cm2actx->id); + + // Validate what we've got. + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(8, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(6, $contextids); + $this->assertTrue(in_array($c1ctx->id, $contextids)); + $this->assertTrue(in_array($c2ctx->id, $contextids)); + $this->assertTrue(in_array($cm1actx->id, $contextids)); + $this->assertTrue(in_array($cm1bctx->id, $contextids)); + $this->assertTrue(in_array($cm2actx->id, $contextids)); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(4, $contextids); + $this->assertTrue(in_array($c1ctx->id, $contextids)); + $this->assertTrue(in_array($c2ctx->id, $contextids)); + $this->assertTrue(in_array($cm1actx->id, $contextids)); + + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + + // Delete cm1a context. + provider::delete_data_for_all_users_in_context($cm1actx); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(8, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(5, $contextids); + $this->assertTrue(in_array($c1ctx->id, $contextids)); + $this->assertTrue(in_array($c2ctx->id, $contextids)); + $this->assertFalse(in_array($cm1actx->id, $contextids)); + $this->assertTrue(in_array($cm1bctx->id, $contextids)); + $this->assertTrue(in_array($cm2actx->id, $contextids)); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(3, $contextids); + $this->assertTrue(in_array($c1ctx->id, $contextids)); + $this->assertTrue(in_array($c2ctx->id, $contextids)); + $this->assertFalse(in_array($cm1actx->id, $contextids)); + + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + + // Delete c1 context. + provider::delete_data_for_all_users_in_context($c1ctx); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(8, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(4, $contextids); + $this->assertFalse(in_array($c1ctx->id, $contextids)); + $this->assertTrue(in_array($c2ctx->id, $contextids)); + $this->assertFalse(in_array($cm1actx->id, $contextids)); + $this->assertTrue(in_array($cm1bctx->id, $contextids)); + $this->assertTrue(in_array($cm2actx->id, $contextids)); + $this->assertTrue(in_array($u1ctx->id, $contextids)); + $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(2, $contextids); + $this->assertFalse(in_array($c1ctx->id, $contextids)); + $this->assertTrue(in_array($c2ctx->id, $contextids)); + $this->assertFalse(in_array($cm1actx->id, $contextids)); + + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + + // Delete u1 context. + provider::delete_data_for_all_users_in_context($u1ctx); + $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); + $this->assertCount(0, $DB->get_records('post', ['userid' => $u1->id])); + $this->assertCount(0, $contextids); + $this->assertFalse(in_array($c1ctx->id, $contextids)); + $this->assertFalse(in_array($c2ctx->id, $contextids)); + $this->assertFalse(in_array($cm1actx->id, $contextids)); + $this->assertFalse(in_array($cm1bctx->id, $contextids)); + $this->assertFalse(in_array($cm2actx->id, $contextids)); + $this->assertFalse(in_array($u1ctx->id, $contextids)); + $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); + $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); + $this->assertCount(2, $contextids); + $this->assertFalse(in_array($c1ctx->id, $contextids)); + $this->assertTrue(in_array($c2ctx->id, $contextids)); + $this->assertFalse(in_array($cm1actx->id, $contextids)); + + $this->assertCount(0, $DB->get_records('blog_external', ['userid' => $u1->id])); + $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); + } + + public function test_export_data_for_user() { + global $DB; + $dg = $this->getDataGenerator(); + + $c1 = $dg->create_course(); + $cm1a = $dg->create_module('page', ['course' => $c1]); + $cm1b = $dg->create_module('page', ['course' => $c1]); + $u1 = $dg->create_user(); + $u2 = $dg->create_user(); + $c1ctx = context_course::instance($c1->id); + $cm1actx = context_module::instance($cm1a->cmid); + $cm1bctx = context_module::instance($cm1b->cmid); + $u1ctx = context_user::instance($u1->id); + $u2ctx = context_user::instance($u2->id); + + // System entries. + $e1 = new blog_entry($this->create_post(['userid' => $u1->id, 'subject' => 'Hello world!', + 'publishstate' => 'public'])->id); + $e2 = new blog_entry($this->create_post(['userid' => $u1->id, 'subject' => 'Hi planet!', + 'publishstate' => 'draft'])->id); + $e3 = new blog_entry($this->create_post(['userid' => $u2->id, 'subject' => 'Ignore me'])->id); + + // Create a blog entry associated with contexts. + $e4 = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'Course assoc'])->id); + $e4->add_association($c1ctx->id); + $e4b = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'Course assoc 2'])->id); + $e4b->add_association($c1ctx->id); + $e5 = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'Module assoc', + 'publishstate' => 'public'])->id); + $e5->add_association($cm1actx->id); + $e5b = new blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'C/CM assoc'])->id); + $e5b->add_association($c1ctx->id); + $e5b->add_association($cm1actx->id); + $e6 = new blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id, 'subject' => 'Module assoc'])->id); + $e6->add_association($cm1actx->id); + + // External blogs. + $ex1 = $this->create_external_blog(['userid' => $u1->id, 'url' => 'https://moodle.org', 'name' => 'Moodle RSS']); + $ex2 = $this->create_external_blog(['userid' => $u1->id, 'url' => 'https://example.com', 'name' => 'Example']); + $ex3 = $this->create_external_blog(['userid' => $u2->id, 'url' => 'https://example.com', 'name' => 'Ignore me']); + + // Attach tags. + core_tag_tag::set_item_tags('core', 'post', $e1->id, $u1ctx, ['Beer', 'Golf']); + core_tag_tag::set_item_tags('core', 'blog_external', $ex1->id, $u1ctx, ['Car', 'Golf']); + core_tag_tag::set_item_tags('core', 'post', $e3->id, $u2ctx, ['ITG']); + core_tag_tag::set_item_tags('core', 'blog_external', $ex3->id, $u2ctx, ['DDR']); + core_tag_tag::set_item_tags('core', 'dontfindme', $e1->id, $u1ctx, ['Lone tag']); + + // Attach comments. + $comment = $this->get_comment_object($u1ctx, $e1->id); + $this->setUser($u1); + $comment->add('Hello, it\'s me!'); + $this->setUser($u2); + $comment->add('I was wondering if after'); + $this->setUser($u1); + $comment = $this->get_comment_object($u2ctx, $e3->id); + $comment->add('All these years'); + + // Blog share a table with notes, so throw some data in there, it should not be exported. + $note = $dg->get_plugin_generator('core_notes')->create_instance(['userid' => $u1->id, 'courseid' => $c1->id, + 'subject' => 'ABC']); + + // Validate module associations. + $contextlist = new approved_contextlist($u1, 'core_blog', [$cm1actx->id]); + provider::export_user_data($contextlist); + $writer = writer::with_context($cm1actx); + $assocs = $writer->get_data([get_string('privacy:path:blogassociations', 'core_blog')]); + $this->assertCount(2, $assocs->associations); + $this->assertTrue(in_array('Module assoc', $assocs->associations)); + $this->assertTrue(in_array('C/CM assoc', $assocs->associations)); + + // Validate course associations. + $contextlist = new approved_contextlist($u1, 'core_blog', [$c1ctx->id]); + provider::export_user_data($contextlist); + $writer = writer::with_context($c1ctx); + $assocs = $writer->get_data([get_string('privacy:path:blogassociations', 'core_blog')]); + $this->assertCount(3, $assocs->associations); + $this->assertTrue(in_array('Course assoc', $assocs->associations)); + $this->assertTrue(in_array('Course assoc 2', $assocs->associations)); + $this->assertTrue(in_array('C/CM assoc', $assocs->associations)); + + // Confirm we're not exporting for another user. + $contextlist = new approved_contextlist($u2, 'core_blog', [$u2ctx->id]); + $writer = writer::with_context($u1ctx); + $this->assertFalse($writer->has_any_data()); + + // Now export user context for u2. + $this->setUser($u2); + $contextlist = new approved_contextlist($u2, 'core_blog', [$u1ctx->id]); + provider::export_user_data($contextlist); + $writer = writer::with_context($u1ctx); + $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('externalblogs', 'core_blog'), + $ex1->name . " ({$ex1->id})"]); + $this->assertEmpty($data); + $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), + $e2->subject . " ({$e2->id})"]); + $this->assertEmpty($data); + $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), + $e1->subject . " ({$e1->id})"]); + $this->assertEmpty($data); + $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), + $e1->subject . " ({$e1->id})", get_string('commentsubcontext', 'core_comment')]); + $this->assertNotEmpty($data); + $this->assertCount(1, $data->comments); + $comment = array_shift($data->comments); + $this->assertEquals('I was wondering if after', strip_tags($comment->content)); + + // Now export user context data. + $this->setUser($u1); + $contextlist = new approved_contextlist($u1, 'core_blog', [$u1ctx->id]); + writer::reset(); + provider::export_user_data($contextlist); + $writer = writer::with_context($u1ctx); + + // Check external blogs. + $externals = [$ex1, $ex2]; + foreach ($externals as $ex) { + $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('externalblogs', 'core_blog'), + $ex->name . " ({$ex->id})"]); + $this->assertNotEmpty($data); + $this->assertEquals($data->name, $ex->name); + $this->assertEquals($data->description, $ex->description); + $this->assertEquals($data->url, $ex->url); + $this->assertEquals($data->filtertags, $ex->filtertags); + $this->assertEquals($data->modified, transform::datetime($ex->timemodified)); + $this->assertEquals($data->lastfetched, transform::datetime($ex->timefetched)); + } + + // Check entries. + $entries = [$e1, $e2, $e4, $e4b, $e5, $e5b]; + $associations = [ + $e1->id => null, + $e2->id => null, + $e4->id => $c1ctx->get_context_name(), + $e4b->id => $c1ctx->get_context_name(), + $e5->id => $cm1actx->get_context_name(), + $e5b->id => [$c1ctx->get_context_name(), $cm1actx->get_context_name()], + ]; + foreach ($entries as $e) { + $path = [get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), $e->subject . " ({$e->id})"]; + $data = $writer->get_data($path); + $this->assertNotEmpty($data); + $this->assertEquals($data->subject, $e->subject); + $this->assertEquals($data->summary, $e->summary); + $this->assertEquals($data->publishstate, provider::transform_publishstate($e->publishstate)); + $this->assertEquals($data->created, transform::datetime($e->created)); + $this->assertEquals($data->lastmodified, transform::datetime($e->lastmodified)); + + // We attached comments and tags to this entry. + $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); + + $comments = $writer->get_data($commentpath); + $this->assertCount(2, $comments->comments); + $this->assertEquals('Hello, it\'s me!', strip_tags($comments->comments[0]->content)); + $this->assertEquals('I was wondering if after', strip_tags($comments->comments[1]->content)); + + } else { + $tagdata = $writer->get_related_data($path, 'tags'); + $this->assertEmpty($tagdata); + $comments = $writer->get_data($commentpath); + $this->assertEmpty($comments); + } + + if (isset($associations[$e->id])) { + $assocs = $associations[$e->id]; + if (is_array($assocs)) { + $this->assertCount(count($assocs), $data->associations); + foreach ($assocs as $v) { + $this->assertTrue(in_array($v, $data->associations)); + } + } else { + $this->assertCount(1, $data->associations); + $this->assertTrue(in_array($assocs, $data->associations)); + } + } + } + + // The note was not exported. + $path = [get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), "ABC ($note->id)"]; + $this->assertEmpty($writer->get_data($path)); + + } + + /** + * Create a blog post. + * + * @param array $params The params. + * @return stdClass + */ + protected function create_post(array $params) { + global $DB; + $post = new stdClass(); + $post->module = 'blog'; + $post->courseid = 0; + $post->subject = 'the test post'; + $post->summary = 'test post summary text'; + $post->summaryformat = FORMAT_PLAIN; + $post->publishstate = 'site'; + $post->created = time() - HOURSECS; + $post->lastmodified = time(); + foreach ($params as $key => $value) { + $post->{$key} = $value; + } + + $post->id = $DB->insert_record('post', $post); + return $post; + } + + /** + * Create an extenral blog. + * + * @param array $params The params. + * @return stdClass + */ + protected function create_external_blog(array $params) { + global $DB; + $post = new stdClass(); + $post->name = 'test external'; + $post->description = 'the description'; + $post->url = 'http://example.com'; + $post->filtertags = 'a, c, b'; + $post->timefetched = time() - HOURSECS; + $post->timemodified = time(); + foreach ($params as $key => $value) { + $post->{$key} = $value; + } + $post->id = $DB->insert_record('blog_external', $post); + return $post; + } + + /** + * Get the comment area. + * + * @param context $context The context. + * @param int $itemid The item ID. + * @param string $component The component. + * @param string $area The area. + * @return comment + */ + protected function get_comment_object(context $context, $itemid) { + $args = new stdClass(); + $args->context = $context; + $args->course = get_course(SITEID); + $args->area = 'format_blog'; + $args->itemid = $itemid; + $args->component = 'blog'; + $comment = new comment($args); + $comment->set_post_permission(true); + return $comment; + } +} diff --git a/lang/en/blog.php b/lang/en/blog.php index ba4aebd71ff..3b0053f1de7 100644 --- a/lang/en/blog.php +++ b/lang/en/blog.php @@ -128,6 +128,29 @@ $string['pagesize'] = 'Blog entries per page'; $string['permalink'] = 'Permalink'; $string['personalblogs'] = 'Users can only see their own blog'; $string['preferences'] = 'Blog preferences'; +$string['privacy:metadata:core_comments'] = 'Comments associated with blog entries'; +$string['privacy:metadata:core_files'] = 'Files attached to blog entries'; +$string['privacy:metadata:core_tag'] = 'Tags associated with blog entries'; +$string['privacy:metadata:external'] = 'A link to an external RSS feed'; +$string['privacy:metadata:external:userid'] = 'The user ID'; +$string['privacy:metadata:external:name'] = 'The name of the feed'; +$string['privacy:metadata:external:description'] = 'The description of the feed'; +$string['privacy:metadata:external:url'] = 'The URL of the feed'; +$string['privacy:metadata:external:filtertags'] = 'The list of tags to filter the entries with'; +$string['privacy:metadata:external:timemodified'] = 'Date at which the association was last modified'; +$string['privacy:metadata:external:timefetched'] = 'Date at which the feed was last fetched'; +$string['privacy:metadata:post'] = 'The information related to blog entries'; +$string['privacy:metadata:post:userid'] = 'The user ID'; +$string['privacy:metadata:post:subject'] = 'The subject of the entry'; +$string['privacy:metadata:post:summary'] = 'The summary of the entry'; +$string['privacy:metadata:post:content'] = 'The content of an external entry'; +$string['privacy:metadata:post:uniquehash'] = 'A unique identifier for an external entry, typically a URL.'; +$string['privacy:metadata:post:publishstate'] = 'Whether the entry is visible to others or not'; +$string['privacy:metadata:post:created'] = 'Date at which the entry was created'; +$string['privacy:metadata:post:lastmodified'] = 'Date at which the entry was last modified'; +$string['privacy:metadata:post:usermodified'] = 'The user who last modified the entry'; +$string['privacy:path:blogassociations'] = 'Associated blog posts'; +$string['privacy:unknown'] = 'Unknown'; $string['publishto'] = 'Publish to'; $string['publishto_help'] = 'There are 3 options: