MDL-66752 tool_dataprivacy: Add automatic data request approval feature
[moodle.git] / admin / tool / dataprivacy / classes / data_request.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 * Class for loading/storing data requests from the DB.
19 *
20 * @package tool_dataprivacy
21 * @copyright 2018 Jun Pataleta
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
b4ecfa38 24
5efc1f9e 25namespace tool_dataprivacy;
b4ecfa38 26
5efc1f9e
DM
27defined('MOODLE_INTERNAL') || die();
28
29use core\persistent;
30
31/**
32 * Class for loading/storing competencies from the DB.
33 *
34 * @copyright 2018 Jun Pataleta
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class data_request extends persistent {
38
39 /** The table name this persistent object maps to. */
40 const TABLE = 'tool_dataprivacy_request';
41
c13c4569
MG
42 /** Data request created manually. */
43 const DATAREQUEST_CREATION_MANUAL = 0;
44
45 /** Data request created automatically. */
46 const DATAREQUEST_CREATION_AUTO = 1;
47
5efc1f9e
DM
48 /**
49 * Return the definition of the properties of this model.
50 *
51 * @return array
52 */
53 protected static function define_properties() {
54 return [
55 'type' => [
56 'choices' => [
57 api::DATAREQUEST_TYPE_EXPORT,
58 api::DATAREQUEST_TYPE_DELETE,
59 api::DATAREQUEST_TYPE_OTHERS,
60 ],
61 'type' => PARAM_INT
62 ],
63 'comments' => [
64 'type' => PARAM_TEXT,
65 'default' => ''
66 ],
ba5b59c0
JP
67 'commentsformat' => [
68 'choices' => [
69 FORMAT_HTML,
70 FORMAT_MOODLE,
71 FORMAT_PLAIN,
72 FORMAT_MARKDOWN
73 ],
74 'type' => PARAM_INT,
75 'default' => FORMAT_PLAIN
76 ],
5efc1f9e
DM
77 'userid' => [
78 'default' => 0,
79 'type' => PARAM_INT
80 ],
81 'requestedby' => [
82 'default' => 0,
83 'type' => PARAM_INT
84 ],
85 'status' => [
b838d85c 86 'default' => api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
5efc1f9e
DM
87 'choices' => [
88 api::DATAREQUEST_STATUS_PENDING,
5efc1f9e
DM
89 api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
90 api::DATAREQUEST_STATUS_APPROVED,
91 api::DATAREQUEST_STATUS_PROCESSING,
92 api::DATAREQUEST_STATUS_COMPLETE,
93 api::DATAREQUEST_STATUS_CANCELLED,
94 api::DATAREQUEST_STATUS_REJECTED,
693f690c
MH
95 api::DATAREQUEST_STATUS_DOWNLOAD_READY,
96 api::DATAREQUEST_STATUS_EXPIRED,
97 api::DATAREQUEST_STATUS_DELETED,
5efc1f9e
DM
98 ],
99 'type' => PARAM_INT
100 ],
101 'dpo' => [
102 'default' => 0,
103 'type' => PARAM_INT,
104 'null' => NULL_ALLOWED
105 ],
106 'dpocomment' => [
107 'default' => '',
108 'type' => PARAM_TEXT,
109 'null' => NULL_ALLOWED
110 ],
ba5b59c0
JP
111 'dpocommentformat' => [
112 'choices' => [
113 FORMAT_HTML,
114 FORMAT_MOODLE,
115 FORMAT_PLAIN,
116 FORMAT_MARKDOWN
117 ],
118 'type' => PARAM_INT,
119 'default' => FORMAT_PLAIN
120 ],
12c1e8b2
JP
121 'systemapproved' => [
122 'default' => false,
123 'type' => PARAM_BOOL,
124 ],
c13c4569
MG
125 'creationmethod' => [
126 'default' => self::DATAREQUEST_CREATION_MANUAL,
127 'choices' => [
128 self::DATAREQUEST_CREATION_MANUAL,
129 self::DATAREQUEST_CREATION_AUTO
130 ],
131 'type' => PARAM_INT
132 ],
5efc1f9e
DM
133 ];
134 }
693f690c
MH
135
136 /**
137 * Determines whether a completed data export request has expired.
138 * The response will be valid regardless of the expiry scheduled task having run.
139 *
140 * @param data_request $request the data request object whose expiry will be checked.
141 * @return bool true if the request has expired.
142 */
143 public static function is_expired(data_request $request) {
144 $result = false;
145
146 // Only export requests expire.
147 if ($request->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
148 switch ($request->get('status')) {
149 // Expired requests are obviously expired.
150 case api::DATAREQUEST_STATUS_EXPIRED:
151 $result = true;
152 break;
153 // Complete requests are expired if the expiry time has elapsed.
154 case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
155 $expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
156 if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
157 $result = true;
158 }
159 break;
160 }
161 }
162
163 return $result;
164 }
165
693f690c
MH
166 /**
167 * Fetch completed data requests which are due to expire.
168 *
169 * @param int $userid Optional user ID to filter by.
170 *
171 * @return array Details of completed requests which are due to expire.
172 */
173 public static function get_expired_requests($userid = 0) {
174 global $DB;
175
176 $expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
177 $expirytime = strtotime("-{$expiryseconds} second");
237c85d3 178 $table = self::TABLE;
693f690c
MH
179 $sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
180 $params = array(
181 'export_type' => api::DATAREQUEST_TYPE_EXPORT,
182 'completestatus' => api::DATAREQUEST_STATUS_DOWNLOAD_READY,
183 'expirytime' => $expirytime,
184 );
185 $sort = 'id';
186 $fields = 'id, userid';
187
188 // Filter by user ID if specified.
189 if ($userid > 0) {
190 $sqlwhere .= ' AND (userid = :userid OR requestedby = :requestedby)';
191 $params['userid'] = $userid;
192 $params['requestedby'] = $userid;
193 }
194
195 return $DB->get_records_select_menu($table, $sqlwhere, $params, $sort, $fields, 0, 2000);
196 }
197
198 /**
199 * Expire a given set of data requests.
200 * Update request status and delete the files.
201 *
202 * @param array $expiredrequests [requestid => userid]
203 *
204 * @return void
205 */
206 public static function expire($expiredrequests) {
207 global $DB;
208
209 $ids = array_keys($expiredrequests);
210
211 if (count($ids) > 0) {
212 list($insql, $inparams) = $DB->get_in_or_equal($ids);
213 $initialparams = array(api::DATAREQUEST_STATUS_EXPIRED, time());
214 $params = array_merge($initialparams, $inparams);
215
237c85d3 216 $update = "UPDATE {" . self::TABLE . "}
693f690c
MH
217 SET status = ?, timemodified = ?
218 WHERE id $insql";
219
220 if ($DB->execute($update, $params)) {
221 $fs = get_file_storage();
222
223 foreach ($expiredrequests as $id => $userid) {
224 $usercontext = \context_user::instance($userid);
225 $fs->delete_area_files($usercontext->id, 'tool_dataprivacy', 'export', $id);
226 }
227 }
228 }
229 }
50208b5c
AN
230
231 /**
232 * Whether this request is in a state appropriate for reset/resubmission.
233 *
234 * Note: This does not check whether any other completed requests exist for this user.
235 *
236 * @return bool
237 */
238 public function is_resettable() : bool {
239 if (api::DATAREQUEST_TYPE_OTHERS == $this->get('type')) {
240 // It is not possible to reset 'other' reqeusts.
241 return false;
242 }
243
244 $resettable = [
245 api::DATAREQUEST_STATUS_APPROVED => true,
246 api::DATAREQUEST_STATUS_REJECTED => true,
247 ];
248
249 return isset($resettable[$this->get('status')]);
250 }
251
252 /**
253 * Whether this request is 'active'.
254 *
255 * @return bool
256 */
257 public function is_active() : bool {
258 $active = [
259 api::DATAREQUEST_STATUS_APPROVED => true,
260 ];
261
262 return isset($active[$this->get('status')]);
263 }
264
265 /**
266 * Reject this request and resubmit it as a fresh request.
267 *
268 * Note: This does not check whether any other completed requests exist for this user.
269 *
270 * @return self
271 */
272 public function resubmit_request() : data_request {
273 if ($this->is_active()) {
274 $this->set('status', api::DATAREQUEST_STATUS_REJECTED)->save();
275 }
276
277 if (!$this->is_resettable()) {
278 throw new \moodle_exception('cannotreset', 'tool_dataprivacy');
279 }
280
281 $currentdata = $this->to_record();
282 unset($currentdata->id);
283
03acfa40
AN
284 // Clone the original request, but do not notify.
285 $clone = api::create_data_request(
286 $this->get('userid'),
287 $this->get('type'),
288 $this->get('comments'),
289 $this->get('creationmethod'),
290 false
291 );
50208b5c
AN
292 $clone->set('comments', $this->get('comments'));
293 $clone->set('dpo', $this->get('dpo'));
294 $clone->set('requestedby', $this->get('requestedby'));
295 $clone->save();
296
297 return $clone;
298 }
5efc1f9e 299}