Autoría | Ultima modificación | Ver Log |
<?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/>./*** Data provider.** @package core_blog* @copyright 2018 Frédéric Massart* @author Frédéric Massart <fred@branchup.tech>* @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 <fred@branchup.tech>* @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,\core_privacy\local\request\core_userlist_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_comment', '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.idFROM {context} ctxWHERE ctx.contextlevel = :ctxlevelAND 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.idFROM {post} pJOIN {blog_association} baON ba.blogid = p.idJOIN {context} ctxON ctx.id = ba.contextidWHERE 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.idFROM {context} ctxWHERE ctx.contextlevel = :ctxlevelAND 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.idFROM {context} ctxJOIN {comments} cON c.contextid = ctx.idWHERE c.component = :componentAND c.commentarea = :commentareaAND c.userid = :userid";$params = ['component' => 'blog','commentarea' => 'format_blog','userid' => $userid];$contextlist->add_from_sql($sql, $params);return $contextlist;}/*** Get the list of users who have data within a context.** @param \core_privacy\local\request\userlist $userlist The userlist containing the list of users who have* data in this context/plugin combination.*/public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {global $DB;$context = $userlist->get_context();if ($context->contextlevel == CONTEXT_COURSE || $context->contextlevel == CONTEXT_MODULE) {$params = ['contextid' => $context->id];$sql = "SELECT p.id, p.useridFROM {post} pJOIN {blog_association} ba ON ba.blogid = p.id AND ba.contextid = :contextid";$posts = $DB->get_records_sql($sql, $params);$userids = array_map(function($post) {return $post->userid;}, $posts);$userlist->add_users($userids);if (!empty($posts)) {// Add any user's who posted on the blog.list($insql, $inparams) = $DB->get_in_or_equal(array_keys($posts), SQL_PARAMS_NAMED);\core_comment\privacy\provider::get_users_in_context_from_sql($userlist, 'c', 'blog', 'format_blog', null, $insql,$inparams);}} else if ($context->contextlevel == CONTEXT_USER) {$params = ['userid' => $context->instanceid];$sql = "SELECT useridFROM {blog_external}WHERE userid = :userid";$userlist->add_from_sql('userid', $sql, $params);$sql = "SELECT useridFROM {post}WHERE userid = :userid";$userlist->add_from_sql('userid', $sql, $params);// Add any user's who posted on the blog.\core_comment\privacy\provider::get_users_in_context_from_sql($userlist, 'c', 'blog', 'format_blog', $context->id);}}/*** 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.itemidFROM {comments} cWHERE c.contextid = :contextidAND c.userid = :useridAND c.component = :componentAND 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 2;}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, 'id');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, $ctxfieldsFROM {blog_association} baJOIN {context} ctxON ba.contextid = ctx.idWHERE 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, $ctxfieldsFROM {post} pJOIN {blog_association} baON ba.blogid = p.idJOIN {context} ctxON ctx.id = ba.contextidWHERE ba.contextid $insqlAND p.userid = :useridORDER 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($context, 'blog', 'format_blog');}/*** 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);\core_comment\privacy\provider::delete_comments_for_all_users($context, 'blog', 'format_blog');} 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;} else {\core_comment\privacy\provider::delete_comments_for_user($contextlist, 'blog', 'format_blog');}}// Delete the associations.if (!empty($associationcontextids)) {list($insql, $inparams) = $DB->get_in_or_equal($associationcontextids, SQL_PARAMS_NAMED);$sql = "SELECT ba.idFROM {blog_association} baJOIN {post} pON p.id = ba.blogidWHERE ba.contextid $insqlAND p.userid = :userid";$params = array_merge($inparams, ['userid' => $userid]);$associds = $DB->get_fieldset_sql($sql, $params);$DB->delete_records_list('blog_association', 'id', $associds);}}/*** Delete multiple users within a single context.** @param approved_userlist $userlist The approved context and user information to delete information for.*/public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {global $DB;$context = $userlist->get_context();$userids = $userlist->get_userids();if ($context->contextlevel == CONTEXT_USER) {// If one of the listed users matches this context then delete the blog, associations, and comments.if (array_search($context->instanceid, $userids) !== false) {self::delete_all_user_data($context);\core_comment\privacy\provider::delete_comments_for_all_users($context, 'blog', 'format_blog');return;}\core_comment\privacy\provider::delete_comments_for_users($userlist, 'blog', 'format_blog');} else {list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);$sql = "SELECT ba.idFROM {blog_association} baJOIN {post} p ON p.id = ba.blogidWHERE ba.contextid = :contextidAND p.userid $insql";$inparams['contextid'] = $context->id;$associds = $DB->get_fieldset_sql($sql, $inparams);if (!empty($associds)) {list($insql, $inparams) = $DB->get_in_or_equal($associds, SQL_PARAMS_NAMED, 'param', true);$DB->delete_records_select('blog_association', "id $insql", $inparams);}}}/*** 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);}/*** 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');}}