See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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 * Data provider. 19 * 20 * @package mod_survey 21 * @copyright 2018 Frédéric Massart 22 * @author Frédéric Massart <fred@branchup.tech> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 namespace mod_survey\privacy; 27 defined('MOODLE_INTERNAL') || die(); 28 29 use context; 30 use context_helper; 31 use context_module; 32 use core_privacy\local\metadata\collection; 33 use core_privacy\local\request\approved_contextlist; 34 use core_privacy\local\request\approved_userlist; 35 use core_privacy\local\request\helper; 36 use core_privacy\local\request\transform; 37 use core_privacy\local\request\userlist; 38 use core_privacy\local\request\writer; 39 40 require_once($CFG->dirroot . '/mod/survey/lib.php'); 41 42 /** 43 * Data provider class. 44 * 45 * @package mod_survey 46 * @copyright 2018 Frédéric Massart 47 * @author Frédéric Massart <fred@branchup.tech> 48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 49 */ 50 class provider implements 51 \core_privacy\local\metadata\provider, 52 \core_privacy\local\request\core_userlist_provider, 53 \core_privacy\local\request\plugin\provider { 54 55 /** 56 * Returns metadata. 57 * 58 * @param collection $collection The initialised collection to add items to. 59 * @return collection A listing of user data stored through this system. 60 */ 61 public static function get_metadata(collection $collection) : collection { 62 $collection->add_database_table('survey_answers', [ 63 'userid' => 'privacy:metadata:answers:userid', 64 'question' => 'privacy:metadata:answers:question', 65 'answer1' => 'privacy:metadata:answers:answer1', 66 'answer2' => 'privacy:metadata:answers:answer2', 67 'time' => 'privacy:metadata:answers:time', 68 ], 'privacy:metadata:answers'); 69 70 $collection->add_database_table('survey_analysis', [ 71 'userid' => 'privacy:metadata:analysis:userid', 72 'notes' => 'privacy:metadata:analysis:notes', 73 ], 'privacy:metadata:analysis'); 74 75 return $collection; 76 } 77 78 /** 79 * Get the list of contexts that contain user information for the specified user. 80 * 81 * @param int $userid The user to search. 82 * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. 83 */ 84 public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { 85 $contextlist = new \core_privacy\local\request\contextlist(); 86 87 // While we should not have an analysis without answers, it's safer to gather contexts by looking at both tables. 88 $sql = " 89 SELECT DISTINCT ctx.id 90 FROM {survey} s 91 JOIN {modules} m 92 ON m.name = :survey 93 JOIN {course_modules} cm 94 ON cm.instance = s.id 95 AND cm.module = m.id 96 JOIN {context} ctx 97 ON ctx.instanceid = cm.id 98 AND ctx.contextlevel = :modulelevel 99 LEFT JOIN {survey_answers} sa 100 ON sa.survey = s.id 101 AND sa.userid = :userid1 102 LEFT JOIN {survey_analysis} sy 103 ON sy.survey = s.id 104 AND sy.userid = :userid2 105 WHERE s.template <> 0 106 AND (sa.id IS NOT NULL 107 OR sy.id IS NOT NULL)"; 108 109 $contextlist->add_from_sql($sql, [ 110 'survey' => 'survey', 111 'modulelevel' => CONTEXT_MODULE, 112 'userid1' => $userid, 113 'userid2' => $userid, 114 ]); 115 116 return $contextlist; 117 } 118 119 /** 120 * Get the list of users who have data within a context. 121 * 122 * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. 123 */ 124 public static function get_users_in_context(userlist $userlist) { 125 $context = $userlist->get_context(); 126 127 if (!is_a($context, \context_module::class)) { 128 return; 129 } 130 131 $params = [ 132 'survey' => 'survey', 133 'modulelevel' => CONTEXT_MODULE, 134 'contextid' => $context->id, 135 ]; 136 137 $sql = " 138 SELECT sa.userid 139 FROM {survey} s 140 JOIN {modules} m 141 ON m.name = :survey 142 JOIN {course_modules} cm 143 ON cm.instance = s.id 144 AND cm.module = m.id 145 JOIN {context} ctx 146 ON ctx.instanceid = cm.id 147 AND ctx.contextlevel = :modulelevel 148 JOIN {survey_answers} sa 149 ON sa.survey = s.id 150 WHERE ctx.id = :contextid 151 AND s.template <> 0"; 152 153 $userlist->add_from_sql('userid', $sql, $params); 154 155 $sql = " 156 SELECT sy.userid 157 FROM {survey} s 158 JOIN {modules} m 159 ON m.name = :survey 160 JOIN {course_modules} cm 161 ON cm.instance = s.id 162 AND cm.module = m.id 163 JOIN {context} ctx 164 ON ctx.instanceid = cm.id 165 AND ctx.contextlevel = :modulelevel 166 JOIN {survey_analysis} sy 167 ON sy.survey = s.id 168 WHERE ctx.id = :contextid 169 AND s.template <> 0"; 170 171 $userlist->add_from_sql('userid', $sql, $params); 172 } 173 174 /** 175 * Export all user data for the specified user, in the specified contexts. 176 * 177 * @param approved_contextlist $contextlist The approved contexts to export information for. 178 */ 179 public static function export_user_data(approved_contextlist $contextlist) { 180 global $DB; 181 182 $user = $contextlist->get_user(); 183 $userid = $user->id; 184 $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { 185 if ($context->contextlevel == CONTEXT_MODULE) { 186 $carry[] = $context->instanceid; 187 } 188 return $carry; 189 }, []); 190 191 if (empty($cmids)) { 192 return; 193 } 194 195 // Export the answers. 196 list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED); 197 $sql = " 198 SELECT sa.*, 199 sq.id as qid, 200 sq.text as qtext, 201 sq.shorttext as qshorttext, 202 sq.intro as qintro, 203 sq.options as qoptions, 204 sq.type as qtype, 205 cm.id as cmid 206 FROM {survey_answers} sa 207 JOIN {survey_questions} sq 208 ON sq.id = sa.question 209 JOIN {survey} s 210 ON s.id = sa.survey 211 JOIN {modules} m 212 ON m.name = :survey 213 JOIN {course_modules} cm 214 ON cm.instance = s.id 215 AND cm.module = m.id 216 WHERE cm.id $insql 217 AND sa.userid = :userid 218 ORDER BY s.id, sq.id"; 219 $params = array_merge($inparams, ['survey' => 'survey', 'userid' => $userid]); 220 221 $recordset = $DB->get_recordset_sql($sql, $params); 222 static::recordset_loop_and_export($recordset, 'cmid', [], function($carry, $record) { 223 $q = survey_translate_question((object) [ 224 'text' => $record->qtext, 225 'shorttext' => $record->qshorttext, 226 'intro' => $record->qintro, 227 'options' => $record->qoptions 228 ]); 229 $qtype = $record->qtype; 230 $options = explode(',', $q->options ?? ''); 231 232 $carry[] = [ 233 'question' => array_merge((array) $q, [ 234 'options' => $qtype > 0 ? $options : '-' 235 ]), 236 'answer' => [ 237 'actual' => $qtype > 0 && !empty($record->answer1) ? $options[$record->answer1 - 1] : $record->answer1, 238 'preferred' => $qtype > 0 && !empty($record->answer2) ? $options[$record->answer2 - 1] : $record->answer2, 239 ], 240 'time' => transform::datetime($record->time), 241 ]; 242 return $carry; 243 244 }, function($cmid, $data) use ($user) { 245 $context = context_module::instance($cmid); 246 $contextdata = helper::get_context_data($context, $user); 247 $contextdata = (object) array_merge((array) $contextdata, ['answers' => $data]); 248 helper::export_context_files($context, $user); 249 writer::with_context($context)->export_data([], $contextdata); 250 }); 251 252 // Export the analysis. 253 $sql = " 254 SELECT sy.*, cm.id as cmid 255 FROM {survey_analysis} sy 256 JOIN {survey} s 257 ON s.id = sy.survey 258 JOIN {modules} m 259 ON m.name = :survey 260 JOIN {course_modules} cm 261 ON cm.instance = s.id 262 AND cm.module = m.id 263 WHERE cm.id $insql 264 AND sy.userid = :userid 265 ORDER BY s.id"; 266 $params = array_merge($inparams, ['survey' => 'survey', 'userid' => $userid]); 267 268 $recordset = $DB->get_recordset_sql($sql, $params); 269 static::recordset_loop_and_export($recordset, 'cmid', null, function($carry, $record) { 270 $carry = ['notes' => $record->notes]; 271 return $carry; 272 }, function($cmid, $data) { 273 $context = context_module::instance($cmid); 274 writer::with_context($context)->export_related_data([], 'survey_analysis', (object) $data); 275 }); 276 } 277 278 /** 279 * Delete all data for all users in the specified context. 280 * 281 * @param context $context The specific context to delete data for. 282 */ 283 public static function delete_data_for_all_users_in_context(context $context) { 284 global $DB; 285 286 if ($context->contextlevel != CONTEXT_MODULE) { 287 return; 288 } 289 290 if ($surveyid = static::get_survey_id_from_context($context)) { 291 $DB->delete_records('survey_answers', ['survey' => $surveyid]); 292 $DB->delete_records('survey_analysis', ['survey' => $surveyid]); 293 } 294 } 295 296 /** 297 * Delete all user data for the specified user, in the specified contexts. 298 * 299 * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. 300 */ 301 public static function delete_data_for_user(approved_contextlist $contextlist) { 302 global $DB; 303 304 $userid = $contextlist->get_user()->id; 305 $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) { 306 if ($context->contextlevel == CONTEXT_MODULE) { 307 $carry[] = $context->instanceid; 308 } 309 return $carry; 310 }, []); 311 if (empty($cmids)) { 312 return; 313 } 314 315 // Fetch the survey IDs. 316 list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED); 317 $sql = " 318 SELECT s.id 319 FROM {survey} s 320 JOIN {modules} m 321 ON m.name = :survey 322 JOIN {course_modules} cm 323 ON cm.instance = s.id 324 AND cm.module = m.id 325 WHERE cm.id $insql"; 326 $params = array_merge($inparams, ['survey' => 'survey']); 327 $surveyids = $DB->get_fieldset_sql($sql, $params); 328 329 // Delete all the things. 330 list($insql, $inparams) = $DB->get_in_or_equal($surveyids, SQL_PARAMS_NAMED); 331 $params = array_merge($inparams, ['userid' => $userid]); 332 $DB->delete_records_select('survey_answers', "survey $insql AND userid = :userid", $params); 333 $DB->delete_records_select('survey_analysis', "survey $insql AND userid = :userid", $params); 334 } 335 336 /** 337 * Delete multiple users within a single context. 338 * 339 * @param approved_userlist $userlist The approved context and user information to delete information for. 340 */ 341 public static function delete_data_for_users(approved_userlist $userlist) { 342 global $DB; 343 $context = $userlist->get_context(); 344 345 if ($context->contextlevel != CONTEXT_MODULE) { 346 return; 347 } 348 349 // Fetch the survey ID. 350 $sql = " 351 SELECT s.id 352 FROM {survey} s 353 JOIN {modules} m 354 ON m.name = :survey 355 JOIN {course_modules} cm 356 ON cm.instance = s.id 357 AND cm.module = m.id 358 WHERE cm.id = :cmid"; 359 $params = [ 360 'survey' => 'survey', 361 'cmid' => $context->instanceid, 362 ]; 363 $surveyid = $DB->get_field_sql($sql, $params); 364 $userids = $userlist->get_userids(); 365 366 // Delete all the things. 367 list($insql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); 368 $params['surveyid'] = $surveyid; 369 370 $DB->delete_records_select('survey_answers', "survey = :surveyid AND userid {$insql}", $params); 371 $DB->delete_records_select('survey_analysis', "survey = :surveyid AND userid {$insql}", $params); 372 } 373 374 /** 375 * Get a survey ID from its context. 376 * 377 * @param context_module $context The module context. 378 * @return int 379 */ 380 protected static function get_survey_id_from_context(context_module $context) { 381 $cm = get_coursemodule_from_id('survey', $context->instanceid); 382 return $cm ? (int) $cm->instance : 0; 383 } 384 /** 385 * Loop and export from a recordset. 386 * 387 * @param moodle_recordset $recordset The recordset. 388 * @param string $splitkey The record key to determine when to export. 389 * @param mixed $initial The initial data to reduce from. 390 * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. 391 * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. 392 * @return void 393 */ 394 protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial, 395 callable $reducer, callable $export) { 396 397 $data = $initial; 398 $lastid = null; 399 400 foreach ($recordset as $record) { 401 if ($lastid && $record->{$splitkey} != $lastid) { 402 $export($lastid, $data); 403 $data = $initial; 404 } 405 $data = $reducer($data, $record); 406 $lastid = $record->{$splitkey}; 407 } 408 $recordset->close(); 409 410 if (!empty($lastid)) { 411 $export($lastid, $data); 412 } 413 } 414 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body