Merge branch 'MDL-62589-master' of git://github.com/andrewnicols/moodle
[moodle.git] / admin / tool / dataprivacy / classes / data_request.php
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/>.
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  */
25 namespace tool_dataprivacy;
27 defined('MOODLE_INTERNAL') || die();
29 use core\persistent;
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  */
37 class data_request extends persistent {
39     /** The table name this persistent object maps to. */
40     const TABLE = 'tool_dataprivacy_request';
42     /** Data request created manually. */
43     const DATAREQUEST_CREATION_MANUAL = 0;
45     /** Data request created automatically. */
46     const DATAREQUEST_CREATION_AUTO = 1;
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             ],
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             ],
77             'userid' => [
78                 'default' => 0,
79                 'type' => PARAM_INT
80             ],
81             'requestedby' => [
82                 'default' => 0,
83                 'type' => PARAM_INT
84             ],
85             'status' => [
86                 'default' => api::DATAREQUEST_STATUS_PENDING,
87                 'choices' => [
88                     api::DATAREQUEST_STATUS_PENDING,
89                     api::DATAREQUEST_STATUS_PREPROCESSING,
90                     api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
91                     api::DATAREQUEST_STATUS_APPROVED,
92                     api::DATAREQUEST_STATUS_PROCESSING,
93                     api::DATAREQUEST_STATUS_COMPLETE,
94                     api::DATAREQUEST_STATUS_CANCELLED,
95                     api::DATAREQUEST_STATUS_REJECTED,
96                     api::DATAREQUEST_STATUS_DOWNLOAD_READY,
97                     api::DATAREQUEST_STATUS_EXPIRED,
98                     api::DATAREQUEST_STATUS_DELETED,
99                 ],
100                 'type' => PARAM_INT
101             ],
102             'dpo' => [
103                 'default' => 0,
104                 'type' => PARAM_INT,
105                 'null' => NULL_ALLOWED
106             ],
107             'dpocomment' => [
108                 'default' => '',
109                 'type' => PARAM_TEXT,
110                 'null' => NULL_ALLOWED
111             ],
112             'dpocommentformat' => [
113                 'choices' => [
114                     FORMAT_HTML,
115                     FORMAT_MOODLE,
116                     FORMAT_PLAIN,
117                     FORMAT_MARKDOWN
118                 ],
119                 'type' => PARAM_INT,
120                 'default' => FORMAT_PLAIN
121             ],
122             'creationmethod' => [
123                 'default' => self::DATAREQUEST_CREATION_MANUAL,
124                 'choices' => [
125                     self::DATAREQUEST_CREATION_MANUAL,
126                     self::DATAREQUEST_CREATION_AUTO
127                 ],
128                 'type' => PARAM_INT
129             ],
130         ];
131     }
133     /**
134      * Determines whether a completed data export request has expired.
135      * The response will be valid regardless of the expiry scheduled task having run.
136      *
137      * @param data_request $request the data request object whose expiry will be checked.
138      * @return bool true if the request has expired.
139      */
140     public static function is_expired(data_request $request) {
141         $result = false;
143         // Only export requests expire.
144         if ($request->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
145             switch ($request->get('status')) {
146                 // Expired requests are obviously expired.
147                 case api::DATAREQUEST_STATUS_EXPIRED:
148                     $result = true;
149                     break;
150                 // Complete requests are expired if the expiry time has elapsed.
151                 case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
152                     $expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
153                     if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
154                         $result = true;
155                     }
156                     break;
157             }
158         }
160         return $result;
161     }
163     /**
164      * Fetch completed data requests which are due to expire.
165      *
166      * @param int $userid Optional user ID to filter by.
167      *
168      * @return array Details of completed requests which are due to expire.
169      */
170     public static function get_expired_requests($userid = 0) {
171         global $DB;
173         $expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
174         $expirytime = strtotime("-{$expiryseconds} second");
175         $table = self::TABLE;
176         $sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
177         $params = array(
178             'export_type' => api::DATAREQUEST_TYPE_EXPORT,
179             'completestatus' => api::DATAREQUEST_STATUS_DOWNLOAD_READY,
180             'expirytime' => $expirytime,
181         );
182         $sort = 'id';
183         $fields = 'id, userid';
185         // Filter by user ID if specified.
186         if ($userid > 0) {
187             $sqlwhere .= ' AND (userid = :userid OR requestedby = :requestedby)';
188             $params['userid'] = $userid;
189             $params['requestedby'] = $userid;
190         }
192         return $DB->get_records_select_menu($table, $sqlwhere, $params, $sort, $fields, 0, 2000);
193     }
195     /**
196      * Expire a given set of data requests.
197      * Update request status and delete the files.
198      *
199      * @param array $expiredrequests [requestid => userid]
200      *
201      * @return void
202      */
203     public static function expire($expiredrequests) {
204         global $DB;
206         $ids = array_keys($expiredrequests);
208         if (count($ids) > 0) {
209             list($insql, $inparams) = $DB->get_in_or_equal($ids);
210             $initialparams = array(api::DATAREQUEST_STATUS_EXPIRED, time());
211             $params = array_merge($initialparams, $inparams);
213             $update = "UPDATE {" . self::TABLE . "}
214                           SET status = ?, timemodified = ?
215                         WHERE id $insql";
217             if ($DB->execute($update, $params)) {
218                 $fs = get_file_storage();
220                 foreach ($expiredrequests as $id => $userid) {
221                     $usercontext = \context_user::instance($userid);
222                     $fs->delete_area_files($usercontext->id, 'tool_dataprivacy', 'export', $id);
223                 }
224             }
225         }
226     }
228     /**
229      * Whether this request is in a state appropriate for reset/resubmission.
230      *
231      * Note: This does not check whether any other completed requests exist for this user.
232      *
233      * @return  bool
234      */
235     public function is_resettable() : bool {
236         if (api::DATAREQUEST_TYPE_OTHERS == $this->get('type')) {
237             // It is not possible to reset 'other' reqeusts.
238             return false;
239         }
241         $resettable = [
242             api::DATAREQUEST_STATUS_APPROVED => true,
243             api::DATAREQUEST_STATUS_REJECTED => true,
244         ];
246         return isset($resettable[$this->get('status')]);
247     }
249     /**
250      * Whether this request is 'active'.
251      *
252      * @return  bool
253      */
254     public function is_active() : bool {
255         $active = [
256             api::DATAREQUEST_STATUS_APPROVED => true,
257         ];
259         return isset($active[$this->get('status')]);
260     }
262     /**
263      * Reject this request and resubmit it as a fresh request.
264      *
265      * Note: This does not check whether any other completed requests exist for this user.
266      *
267      * @return  self
268      */
269     public function resubmit_request() : data_request {
270         if ($this->is_active()) {
271             $this->set('status', api::DATAREQUEST_STATUS_REJECTED)->save();
272         }
274         if (!$this->is_resettable()) {
275             throw new \moodle_exception('cannotreset', 'tool_dataprivacy');
276         }
278         $currentdata = $this->to_record();
279         unset($currentdata->id);
281         $clone = api::create_data_request($this->get('userid'), $this->get('type'));
282         $clone->set('comments', $this->get('comments'));
283         $clone->set('dpo', $this->get('dpo'));
284         $clone->set('requestedby', $this->get('requestedby'));
285         $clone->save();
287         return $clone;
288     }