1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Privacy class for requesting user data. 19 * 20 * @package core_portfolio 21 * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace core_portfolio\privacy; 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 use core_privacy\local\metadata\collection; 29 use core_privacy\local\request\context; 30 use core_privacy\local\request\contextlist; 31 use core_privacy\local\request\approved_contextlist; 32 use core_privacy\local\request\transform; 33 use core_privacy\local\request\userlist; 34 use core_privacy\local\request\approved_userlist; 35 36 /** 37 * Provider for the portfolio API. 38 * 39 * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com> 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class provider implements 43 // The core portfolio system stores preferences related to the other portfolio subsystems. 44 \core_privacy\local\metadata\provider, 45 \core_privacy\local\request\plugin\provider, 46 \core_privacy\local\request\core_userlist_provider, 47 // The portfolio subsystem will be called by other components. 48 \core_privacy\local\request\subsystem\plugin_provider, 49 50 \core_privacy\local\request\shared_userlist_provider { 51 52 /** 53 * Returns meta data about this system. 54 * 55 * @param collection $collection The initialised collection to add items to. 56 * @return collection A listing of user data stored through this system. 57 */ 58 public static function get_metadata(collection $collection) : collection { 59 $collection->add_database_table('portfolio_instance_user', [ 60 'instance' => 'privacy:metadata:instance', 61 'userid' => 'privacy:metadata:userid', 62 'name' => 'privacy:metadata:name', 63 'value' => 'privacy:metadata:value' 64 ], 'privacy:metadata:instancesummary'); 65 66 $collection->add_database_table('portfolio_log', [ 67 'userid' => 'privacy:metadata:portfolio_log:userid', 68 'time' => 'privacy:metadata:portfolio_log:time', 69 'caller_class' => 'privacy:metadata:portfolio_log:caller_class', 70 'caller_component' => 'privacy:metadata:portfolio_log:caller_component', 71 ], 'privacy:metadata:portfolio_log'); 72 73 // Temporary data is not exported/deleted in privacy API. It is cleaned by cron. 74 $collection->add_database_table('portfolio_tempdata', [ 75 'data' => 'privacy:metadata:portfolio_tempdata:data', 76 'expirytime' => 'privacy:metadata:portfolio_tempdata:expirytime', 77 'userid' => 'privacy:metadata:portfolio_tempdata:userid', 78 'instance' => 'privacy:metadata:portfolio_tempdata:instance', 79 ], 'privacy:metadata:portfolio_tempdata'); 80 81 $collection->add_plugintype_link('portfolio', [], 'privacy:metadata'); 82 return $collection; 83 } 84 85 /** 86 * Get the list of contexts that contain user information for the specified user. 87 * 88 * @param int $userid The user to search. 89 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 90 */ 91 public static function get_contexts_for_userid(int $userid) : contextlist { 92 $sql = "SELECT ctx.id 93 FROM {context} ctx 94 WHERE ctx.instanceid = :userid AND ctx.contextlevel = :usercontext 95 AND (EXISTS (SELECT 1 FROM {portfolio_instance_user} WHERE userid = :userid1) OR 96 EXISTS (SELECT 1 FROM {portfolio_log} WHERE userid = :userid2)) 97 "; 98 $params = ['userid' => $userid, 'usercontext' => CONTEXT_USER, 'userid1' => $userid, 'userid2' => $userid]; 99 $contextlist = new contextlist(); 100 $contextlist->add_from_sql($sql, $params); 101 return $contextlist; 102 } 103 104 /** 105 * Get the list of users within a specific context. 106 * 107 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 108 */ 109 public static function get_users_in_context(userlist $userlist) { 110 $context = $userlist->get_context(); 111 112 if (!$context instanceof \context_user) { 113 return; 114 } 115 116 $params = [$context->instanceid]; 117 118 $sql = "SELECT userid 119 FROM {portfolio_instance_user} 120 WHERE userid = ?"; 121 $userlist->add_from_sql('userid', $sql, $params); 122 123 $sql = "SELECT userid 124 FROM {portfolio_log} 125 WHERE userid = ?"; 126 $userlist->add_from_sql('userid', $sql, $params); 127 } 128 129 /** 130 * Export all user data for the specified user, in the specified contexts. 131 * 132 * @param approved_contextlist $contextlist The approved contexts to export information for. 133 */ 134 public static function export_user_data(approved_contextlist $contextlist) { 135 global $DB; 136 137 if ($contextlist->get_component() != 'core_portfolio') { 138 return; 139 } 140 141 $correctusercontext = array_filter($contextlist->get_contexts(), function($context) use ($contextlist) { 142 if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $contextlist->get_user()->id) { 143 return $context; 144 } 145 }); 146 147 if (empty($correctusercontext)) { 148 return; 149 } 150 151 $usercontext = array_shift($correctusercontext); 152 153 $sql = "SELECT pi.id AS instanceid, pi.name, 154 piu.id AS preferenceid, piu.name AS preference, piu.value, 155 pl.id AS logid, pl.time AS logtime, pl.caller_class, pl.caller_file, 156 pl.caller_component, pl.returnurl, pl.continueurl 157 FROM {portfolio_instance} pi 158 LEFT JOIN {portfolio_instance_user} piu ON piu.instance = pi.id AND piu.userid = :userid1 159 LEFT JOIN {portfolio_log} pl ON pl.portfolio = pi.id AND pl.userid = :userid2 160 WHERE piu.id IS NOT NULL OR pl.id IS NOT NULL"; 161 $params = ['userid1' => $usercontext->instanceid, 'userid2' => $usercontext->instanceid]; 162 $instances = []; 163 $rs = $DB->get_recordset_sql($sql, $params); 164 foreach ($rs as $record) { 165 $instances += [$record->name => 166 (object)[ 167 'name' => $record->name, 168 'preferences' => [], 169 'logs' => [], 170 ] 171 ]; 172 if ($record->preferenceid) { 173 $instances[$record->name]->preferences[$record->preferenceid] = (object)[ 174 'name' => $record->preference, 175 'value' => $record->value, 176 ]; 177 } 178 if ($record->logid) { 179 $instances[$record->name]->logs[$record->logid] = (object)[ 180 'time' => transform::datetime($record->logtime), 181 'caller_class' => $record->caller_class, 182 'caller_file' => $record->caller_file, 183 'caller_component' => $record->caller_component, 184 'returnurl' => $record->returnurl, 185 'continueurl' => $record->continueurl 186 ]; 187 } 188 } 189 $rs->close(); 190 191 if (!empty($instances)) { 192 foreach ($instances as &$instance) { 193 if (!empty($instance->preferences)) { 194 $instance->preferences = array_values($instance->preferences); 195 } else { 196 unset($instance->preferences); 197 } 198 if (!empty($instance->logs)) { 199 $instance->logs = array_values($instance->logs); 200 } else { 201 unset($instance->logs); 202 } 203 } 204 \core_privacy\local\request\writer::with_context($contextlist->current())->export_data( 205 [get_string('privacy:path', 'portfolio')], (object) $instances); 206 } 207 } 208 209 /** 210 * Delete all data for all users in the specified context. 211 * 212 * @param context $context The specific context to delete data for. 213 */ 214 public static function delete_data_for_all_users_in_context(\context $context) { 215 global $DB; 216 // Context could be anything, BEWARE! 217 if ($context->contextlevel == CONTEXT_USER) { 218 $DB->delete_records('portfolio_instance_user', ['userid' => $context->instanceid]); 219 $DB->delete_records('portfolio_tempdata', ['userid' => $context->instanceid]); 220 $DB->delete_records('portfolio_log', ['userid' => $context->instanceid]); 221 } 222 } 223 224 /** 225 * Delete multiple users within a single context. 226 * 227 * @param approved_userlist $userlist The approved context and user information to delete information for. 228 */ 229 public static function delete_data_for_users(approved_userlist $userlist) { 230 global $DB; 231 232 $context = $userlist->get_context(); 233 234 if ($context instanceof \context_user) { 235 $DB->delete_records('portfolio_instance_user', ['userid' => $context->instanceid]); 236 $DB->delete_records('portfolio_tempdata', ['userid' => $context->instanceid]); 237 $DB->delete_records('portfolio_log', ['userid' => $context->instanceid]); 238 } 239 } 240 241 /** 242 * Delete all user data for the specified user, in the specified contexts. 243 * 244 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 245 */ 246 public static function delete_data_for_user(approved_contextlist $contextlist) { 247 global $DB; 248 249 if ($contextlist->get_component() != 'core_portfolio') { 250 return; 251 } 252 253 $correctusercontext = array_filter($contextlist->get_contexts(), function($context) use ($contextlist) { 254 if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $contextlist->get_user()->id) { 255 return $context; 256 } 257 }); 258 259 if (empty($correctusercontext)) { 260 return; 261 } 262 263 $usercontext = array_shift($correctusercontext); 264 265 $DB->delete_records('portfolio_instance_user', ['userid' => $usercontext->instanceid]); 266 $DB->delete_records('portfolio_tempdata', ['userid' => $usercontext->instanceid]); 267 $DB->delete_records('portfolio_log', ['userid' => $usercontext->instanceid]); 268 } 269 270 /** 271 * Export all portfolio data from each portfolio plugin for the specified userid and context. 272 * 273 * @param int $userid The user to export. 274 * @param \context $context The context to export. 275 * @param array $subcontext The subcontext within the context to export this information to. 276 * @param array $linkarray The weird and wonderful link array used to display information for a specific item 277 */ 278 public static function export_portfolio_user_data(int $userid, \context $context, array $subcontext, array $linkarray) { 279 static::call_plugin_method('export_portfolio_user_data', [$userid, $context, $subcontext, $linkarray]); 280 } 281 282 /** 283 * Deletes all user content for a context in all portfolio plugins. 284 * 285 * @param \context $context The context to delete user data for. 286 */ 287 public static function delete_portfolio_for_context(\context $context) { 288 static::call_plugin_method('delete_portfolio_for_context', [$context]); 289 } 290 291 /** 292 * Deletes all user content for a user in a context in all portfolio plugins. 293 * 294 * @param int $userid The user to delete 295 * @param \context $context The context to refine the deletion. 296 */ 297 public static function delete_portfolio_for_user(int $userid, \context $context) { 298 static::call_plugin_method('delete_portfolio_for_user', [$userid, $context]); 299 } 300 301 /** 302 * Internal method for looping through all of the portfolio plugins and calling a method. 303 * 304 * @param string $methodname Name of the method to call on the plugins. 305 * @param array $params The parameters that go with the method being called. 306 */ 307 protected static function call_plugin_method($methodname, $params) { 308 // Note: Even if portfolio is _now_ disabled, there may be legacy data to export. 309 \core_privacy\manager::plugintype_class_callback('portfolio', portfolio_provider::class, $methodname, $params); 310 } 311 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body