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 Subsystem implementation for mod_choice. 19 * 20 * @package mod_choice 21 * @category privacy 22 * @copyright 2018 Jun Pataleta 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace mod_choice\privacy; 27 28 use core_privacy\local\metadata\collection; 29 use core_privacy\local\request\approved_contextlist; 30 use core_privacy\local\request\approved_userlist; 31 use core_privacy\local\request\contextlist; 32 use core_privacy\local\request\deletion_criteria; 33 use core_privacy\local\request\helper; 34 use core_privacy\local\request\userlist; 35 use core_privacy\local\request\writer; 36 37 defined('MOODLE_INTERNAL') || die(); 38 39 /** 40 * Implementation of the privacy subsystem plugin provider for the choice activity module. 41 * 42 * @copyright 2018 Jun Pataleta 43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 44 */ 45 class provider implements 46 // This plugin stores personal data. 47 \core_privacy\local\metadata\provider, 48 49 // This plugin is a core_user_data_provider. 50 \core_privacy\local\request\plugin\provider, 51 52 // This plugin is capable of determining which users have data within it. 53 \core_privacy\local\request\core_userlist_provider { 54 /** 55 * Return the fields which contain personal data. 56 * 57 * @param collection $items a reference to the collection to use to store the metadata. 58 * @return collection the updated collection of metadata items. 59 */ 60 public static function get_metadata(collection $items) : collection { 61 $items->add_database_table( 62 'choice_answers', 63 [ 64 'choiceid' => 'privacy:metadata:choice_answers:choiceid', 65 'optionid' => 'privacy:metadata:choice_answers:optionid', 66 'userid' => 'privacy:metadata:choice_answers:userid', 67 'timemodified' => 'privacy:metadata:choice_answers:timemodified', 68 ], 69 'privacy:metadata:choice_answers' 70 ); 71 72 return $items; 73 } 74 75 /** 76 * Get the list of contexts that contain user information for the specified user. 77 * 78 * @param int $userid the userid. 79 * @return contextlist the list of contexts containing user info for the user. 80 */ 81 public static function get_contexts_for_userid(int $userid) : contextlist { 82 // Fetch all choice answers. 83 $sql = "SELECT c.id 84 FROM {context} c 85 INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 86 INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname 87 INNER JOIN {choice} ch ON ch.id = cm.instance 88 INNER JOIN {choice_options} co ON co.choiceid = ch.id 89 INNER JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id 90 WHERE ca.userid = :userid"; 91 92 $params = [ 93 'modname' => 'choice', 94 'contextlevel' => CONTEXT_MODULE, 95 'userid' => $userid, 96 ]; 97 $contextlist = new contextlist(); 98 $contextlist->add_from_sql($sql, $params); 99 100 return $contextlist; 101 } 102 103 /** 104 * Get the list of users who have data within a context. 105 * 106 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 107 */ 108 public static function get_users_in_context(userlist $userlist) { 109 $context = $userlist->get_context(); 110 111 if (!$context instanceof \context_module) { 112 return; 113 } 114 115 // Fetch all choice answers. 116 $sql = "SELECT ca.userid 117 FROM {course_modules} cm 118 JOIN {modules} m ON m.id = cm.module AND m.name = :modname 119 JOIN {choice} ch ON ch.id = cm.instance 120 JOIN {choice_options} co ON co.choiceid = ch.id 121 JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id 122 WHERE cm.id = :cmid"; 123 124 $params = [ 125 'cmid' => $context->instanceid, 126 'modname' => 'choice', 127 ]; 128 129 $userlist->add_from_sql('userid', $sql, $params); 130 } 131 132 /** 133 * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist. 134 * 135 * @param approved_contextlist $contextlist a list of contexts approved for export. 136 */ 137 public static function export_user_data(approved_contextlist $contextlist) { 138 global $DB; 139 140 if (empty($contextlist->count())) { 141 return; 142 } 143 144 $user = $contextlist->get_user(); 145 146 list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED); 147 148 $sql = "SELECT cm.id AS cmid, 149 co.text as answer, 150 ca.timemodified 151 FROM {context} c 152 INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel 153 INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname 154 INNER JOIN {choice} ch ON ch.id = cm.instance 155 INNER JOIN {choice_options} co ON co.choiceid = ch.id 156 INNER JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id 157 WHERE c.id {$contextsql} 158 AND ca.userid = :userid 159 ORDER BY cm.id"; 160 161 $params = ['modname' => 'choice', 'contextlevel' => CONTEXT_MODULE, 'userid' => $user->id] + $contextparams; 162 163 // Reference to the choice activity seen in the last iteration of the loop. By comparing this with the current record, and 164 // because we know the results are ordered, we know when we've moved to the answers for a new choice activity and therefore 165 // when we can export the complete data for the last activity. 166 $lastcmid = null; 167 168 $choiceanswers = $DB->get_recordset_sql($sql, $params); 169 foreach ($choiceanswers as $choiceanswer) { 170 // If we've moved to a new choice, then write the last choice data and reinit the choice data array. 171 if ($lastcmid != $choiceanswer->cmid) { 172 if (!empty($choicedata)) { 173 $context = \context_module::instance($lastcmid); 174 self::export_choice_data_for_user($choicedata, $context, $user); 175 } 176 $choicedata = [ 177 'answer' => [], 178 'timemodified' => \core_privacy\local\request\transform::datetime($choiceanswer->timemodified), 179 ]; 180 } 181 $choicedata['answer'][] = $choiceanswer->answer; 182 $lastcmid = $choiceanswer->cmid; 183 } 184 $choiceanswers->close(); 185 186 // The data for the last activity won't have been written yet, so make sure to write it now! 187 if (!empty($choicedata)) { 188 $context = \context_module::instance($lastcmid); 189 self::export_choice_data_for_user($choicedata, $context, $user); 190 } 191 } 192 193 /** 194 * Export the supplied personal data for a single choice activity, along with any generic data or area files. 195 * 196 * @param array $choicedata the personal data to export for the choice. 197 * @param \context_module $context the context of the choice. 198 * @param \stdClass $user the user record 199 */ 200 protected static function export_choice_data_for_user(array $choicedata, \context_module $context, \stdClass $user) { 201 // Fetch the generic module data for the choice. 202 $contextdata = helper::get_context_data($context, $user); 203 204 // Merge with choice data and write it. 205 $contextdata = (object)array_merge((array)$contextdata, $choicedata); 206 writer::with_context($context)->export_data([], $contextdata); 207 208 // Write generic module intro files. 209 helper::export_context_files($context, $user); 210 } 211 212 /** 213 * Delete all data for all users in the specified context. 214 * 215 * @param \context $context the context to delete in. 216 */ 217 public static function delete_data_for_all_users_in_context(\context $context) { 218 global $DB; 219 220 if (!$context instanceof \context_module) { 221 return; 222 } 223 224 if ($cm = get_coursemodule_from_id('choice', $context->instanceid)) { 225 $DB->delete_records('choice_answers', ['choiceid' => $cm->instance]); 226 } 227 } 228 229 /** 230 * Delete all user data for the specified user, in the specified contexts. 231 * 232 * @param approved_contextlist $contextlist a list of contexts approved for deletion. 233 */ 234 public static function delete_data_for_user(approved_contextlist $contextlist) { 235 global $DB; 236 237 if (empty($contextlist->count())) { 238 return; 239 } 240 241 $userid = $contextlist->get_user()->id; 242 foreach ($contextlist->get_contexts() as $context) { 243 244 if (!$context instanceof \context_module) { 245 continue; 246 } 247 $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid]); 248 if (!$instanceid) { 249 continue; 250 } 251 $DB->delete_records('choice_answers', ['choiceid' => $instanceid, 'userid' => $userid]); 252 } 253 } 254 255 /** 256 * Delete multiple users within a single context. 257 * 258 * @param approved_userlist $userlist The approved context and user information to delete information for. 259 */ 260 public static function delete_data_for_users(approved_userlist $userlist) { 261 global $DB; 262 263 $context = $userlist->get_context(); 264 265 if (!$context instanceof \context_module) { 266 return; 267 } 268 269 $cm = get_coursemodule_from_id('choice', $context->instanceid); 270 271 if (!$cm) { 272 // Only choice module will be handled. 273 return; 274 } 275 276 $userids = $userlist->get_userids(); 277 list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 278 279 $select = "choiceid = :choiceid AND userid $usersql"; 280 $params = ['choiceid' => $cm->instance] + $userparams; 281 $DB->delete_records_select('choice_answers', $select, $params); 282 } 283 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body