MDL-62289 tool_dataprivacy: Ensure all user data deleted.
[moodle.git] / admin / tool / dataprivacy / classes / expired_user_contexts.php
CommitLineData
5efc1f9e
DM
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 * Expired contexts manager for CONTEXT_USER.
19 *
20 * @package tool_dataprivacy
21 * @copyright 2018 David Monllao
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace tool_dataprivacy;
25
26use tool_dataprivacy\purpose;
27use tool_dataprivacy\context_instance;
28
29defined('MOODLE_INTERNAL') || die();
30
31/**
32 * Expired contexts manager for CONTEXT_USER.
33 *
34 * @copyright 2018 David Monllao
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
38
e60058ff
DM
39 /**
40 * Only user level.
41 *
42 * @return int[]
43 */
44 protected function get_context_levels() {
45 return [CONTEXT_USER];
46 }
47
5efc1f9e
DM
48 /**
49 * Returns the user context instances that are expired.
50 *
51 * @return \stdClass[]
52 */
53 protected function get_expired_contexts() {
54 global $DB;
55
56 // Including context info + last login timestamp.
57 $fields = 'ctx.id AS id, ' . \context_helper::get_preload_record_columns_sql('ctx');
58
59 $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
60
61 // Calculate what is considered expired according to the context level effective purpose (= now + retention period).
62 $expiredtime = new \DateTime();
63 $retention = new \DateInterval($purpose->get('retentionperiod'));
64 $expiredtime->sub($retention);
65
66 $sql = "SELECT $fields FROM {context} ctx
67 JOIN {user} u ON ctx.contextlevel = ? AND ctx.instanceid = u.id
68 LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx ON ctx.id = expiredctx.contextid
69 WHERE u.lastaccess <= ? AND u.lastaccess > 0 AND expiredctx.id IS NULL
70 ORDER BY ctx.path, ctx.contextlevel ASC";
71 $possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_USER, $expiredtime->getTimestamp()]);
72
73 $expiredcontexts = [];
74 foreach ($possiblyexpired as $record) {
75
76 \context_helper::preload_from_record($record);
77
78 // No strict checking as the context may already be deleted (e.g. we just deleted a course,
79 // module contexts below it will not exist).
80 $context = \context::instance_by_id($record->id, false);
81 if (!$context) {
82 continue;
83 }
84
85 if (is_siteadmin($context->instanceid)) {
86 continue;
87 }
88
89 $courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
90 foreach ($courses as $course) {
91 if (!$course->enddate) {
92 // We can not know it what is going on here, so we prefer to be conservative.
93 continue 2;
94 }
95
96 if ($course->enddate >= time()) {
97 // Future or ongoing course.
98 continue 2;
99 }
100 }
101
102 $expiredcontexts[$context->id] = $context;
103 }
104
105 return $expiredcontexts;
106 }
e60058ff
DM
107
108 /**
109 * Deletes user data from the provided context.
110 *
111 * Overwritten to delete the user.
112 *
113 * @param \core_privacy\manager $privacymanager
114 * @param \tool_dataprivacy\expired_context $expiredctx
115 * @return \context|false
116 */
117 protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
c8e49221
AG
118 $context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
119 if (!$context) {
120 api::delete_expired_context($expiredctx->get('contextid'));
e60058ff
DM
121 return false;
122 }
123
c8e49221
AG
124 if (!PHPUNIT_TEST) {
125 mtrace('Deleting context ' . $context->id . ' - ' .
126 shorten_text($context->get_context_name(true, true)));
127 }
128
129 // To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
130 // contexts for the user that may still exist and call delete_data_for_user().
e60058ff 131 $user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
c8e49221
AG
132 $approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
133 $contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
134
135 foreach ($contextlistcollection as $contextlist) {
136 $approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
137 $user,
138 $contextlist->get_component(),
139 $contextlist->get_contextids()
140 ));
141 }
142
143 $privacymanager->delete_data_for_user($approvedlistcollection);
144 api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
145
146 // Delete the user.
e60058ff
DM
147 delete_user($user);
148
149 return $context;
150 }
5efc1f9e 151}