1 |
efrain |
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 core_grades
|
|
|
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 core_grades\privacy;
|
|
|
27 |
defined('MOODLE_INTERNAL') || die();
|
|
|
28 |
|
|
|
29 |
use context;
|
|
|
30 |
use context_course;
|
|
|
31 |
use context_system;
|
|
|
32 |
use grade_item;
|
|
|
33 |
use grade_grade;
|
|
|
34 |
use grade_scale;
|
|
|
35 |
use stdClass;
|
|
|
36 |
use core_grades\privacy\grade_grade_with_history;
|
|
|
37 |
use core_privacy\local\metadata\collection;
|
|
|
38 |
use core_privacy\local\request\approved_contextlist;
|
|
|
39 |
use core_privacy\local\request\transform;
|
|
|
40 |
use core_privacy\local\request\writer;
|
|
|
41 |
|
|
|
42 |
require_once($CFG->libdir . '/gradelib.php');
|
|
|
43 |
|
|
|
44 |
/**
|
|
|
45 |
* Data provider class.
|
|
|
46 |
*
|
|
|
47 |
* @package core_grades
|
|
|
48 |
* @copyright 2018 Frédéric Massart
|
|
|
49 |
* @author Frédéric Massart <fred@branchup.tech>
|
|
|
50 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
51 |
*/
|
|
|
52 |
class provider implements
|
|
|
53 |
\core_privacy\local\metadata\provider,
|
|
|
54 |
\core_privacy\local\request\subsystem\provider,
|
|
|
55 |
\core_privacy\local\request\core_userlist_provider {
|
|
|
56 |
|
|
|
57 |
/**
|
|
|
58 |
* Returns metadata.
|
|
|
59 |
*
|
|
|
60 |
* @param collection $collection The initialised collection to add items to.
|
|
|
61 |
* @return collection A listing of user data stored through this system.
|
|
|
62 |
*/
|
|
|
63 |
public static function get_metadata(collection $collection): collection {
|
|
|
64 |
|
|
|
65 |
// Tables without 'real' user information.
|
|
|
66 |
$collection->add_database_table('grade_outcomes', [
|
|
|
67 |
'timemodified' => 'privacy:metadata:outcomes:timemodified',
|
|
|
68 |
'usermodified' => 'privacy:metadata:outcomes:usermodified',
|
|
|
69 |
], 'privacy:metadata:outcomes');
|
|
|
70 |
|
|
|
71 |
$collection->add_database_table('grade_outcomes_history', [
|
|
|
72 |
'timemodified' => 'privacy:metadata:history:timemodified',
|
|
|
73 |
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
|
|
74 |
], 'privacy:metadata:outcomeshistory');
|
|
|
75 |
|
|
|
76 |
$collection->add_database_table('grade_categories_history', [
|
|
|
77 |
'timemodified' => 'privacy:metadata:history:timemodified',
|
|
|
78 |
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
|
|
79 |
], 'privacy:metadata:categorieshistory');
|
|
|
80 |
|
|
|
81 |
$collection->add_database_table('grade_items_history', [
|
|
|
82 |
'timemodified' => 'privacy:metadata:history:timemodified',
|
|
|
83 |
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
|
|
84 |
], 'privacy:metadata:itemshistory');
|
|
|
85 |
|
|
|
86 |
$collection->add_database_table('scale', [
|
|
|
87 |
'userid' => 'privacy:metadata:scale:userid',
|
|
|
88 |
'timemodified' => 'privacy:metadata:scale:timemodified',
|
|
|
89 |
], 'privacy:metadata:scale');
|
|
|
90 |
|
|
|
91 |
$collection->add_database_table('scale_history', [
|
|
|
92 |
'userid' => 'privacy:metadata:scale:userid',
|
|
|
93 |
'timemodified' => 'privacy:metadata:history:timemodified',
|
|
|
94 |
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
|
|
95 |
], 'privacy:metadata:scalehistory');
|
|
|
96 |
|
|
|
97 |
// Table with user information.
|
|
|
98 |
$gradescommonfields = [
|
|
|
99 |
'userid' => 'privacy:metadata:grades:userid',
|
|
|
100 |
'usermodified' => 'privacy:metadata:grades:usermodified',
|
|
|
101 |
'finalgrade' => 'privacy:metadata:grades:finalgrade',
|
|
|
102 |
'feedback' => 'privacy:metadata:grades:feedback',
|
|
|
103 |
'information' => 'privacy:metadata:grades:information',
|
|
|
104 |
];
|
|
|
105 |
|
|
|
106 |
$collection->add_database_table('grade_grades', array_merge($gradescommonfields, [
|
|
|
107 |
'timemodified' => 'privacy:metadata:grades:timemodified',
|
|
|
108 |
]), 'privacy:metadata:grades');
|
|
|
109 |
|
|
|
110 |
$collection->add_database_table('grade_grades_history', array_merge($gradescommonfields, [
|
|
|
111 |
'timemodified' => 'privacy:metadata:history:timemodified',
|
|
|
112 |
'loggeduser' => 'privacy:metadata:history:loggeduser',
|
|
|
113 |
]), 'privacy:metadata:gradeshistory');
|
|
|
114 |
|
|
|
115 |
// The following tables are reported but not exported/deleted because their data is temporary and only
|
|
|
116 |
// used during an import. It's content is deleted after a successful, or failed, import.
|
|
|
117 |
|
|
|
118 |
$collection->add_database_table('grade_import_newitem', [
|
|
|
119 |
'itemname' => 'privacy:metadata:grade_import_newitem:itemname',
|
|
|
120 |
'importcode' => 'privacy:metadata:grade_import_newitem:importcode',
|
|
|
121 |
'importer' => 'privacy:metadata:grade_import_newitem:importer'
|
|
|
122 |
], 'privacy:metadata:grade_import_newitem');
|
|
|
123 |
|
|
|
124 |
$collection->add_database_table('grade_import_values', [
|
|
|
125 |
'userid' => 'privacy:metadata:grade_import_values:userid',
|
|
|
126 |
'finalgrade' => 'privacy:metadata:grade_import_values:finalgrade',
|
|
|
127 |
'feedback' => 'privacy:metadata:grade_import_values:feedback',
|
|
|
128 |
'importcode' => 'privacy:metadata:grade_import_values:importcode',
|
|
|
129 |
'importer' => 'privacy:metadata:grade_import_values:importer',
|
|
|
130 |
'importonlyfeedback' => 'privacy:metadata:grade_import_values:importonlyfeedback'
|
|
|
131 |
], 'privacy:metadata:grade_import_values');
|
|
|
132 |
|
|
|
133 |
$collection->link_subsystem('core_files', 'privacy:metadata:filepurpose');
|
|
|
134 |
|
|
|
135 |
return $collection;
|
|
|
136 |
}
|
|
|
137 |
|
|
|
138 |
/**
|
|
|
139 |
* Get the list of contexts that contain user information for the specified user.
|
|
|
140 |
*
|
|
|
141 |
* @param int $userid The user to search.
|
|
|
142 |
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
|
|
143 |
*/
|
|
|
144 |
public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist {
|
|
|
145 |
$contextlist = new \core_privacy\local\request\contextlist();
|
|
|
146 |
|
|
|
147 |
// Add where we modified outcomes.
|
|
|
148 |
$sql = "
|
|
|
149 |
SELECT DISTINCT ctx.id
|
|
|
150 |
FROM {grade_outcomes} go
|
|
|
151 |
JOIN {context} ctx
|
|
|
152 |
ON (go.courseid > 0 AND ctx.instanceid = go.courseid AND ctx.contextlevel = :courselevel)
|
|
|
153 |
OR ((go.courseid IS NULL OR go.courseid < 1) AND ctx.id = :syscontextid)
|
|
|
154 |
WHERE go.usermodified = :userid";
|
|
|
155 |
$params = ['userid' => $userid, 'courselevel' => CONTEXT_COURSE, 'syscontextid' => SYSCONTEXTID];
|
|
|
156 |
$contextlist->add_from_sql($sql, $params);
|
|
|
157 |
|
|
|
158 |
// Add where we modified scales.
|
|
|
159 |
$sql = "
|
|
|
160 |
SELECT DISTINCT ctx.id
|
|
|
161 |
FROM {scale} s
|
|
|
162 |
JOIN {context} ctx
|
|
|
163 |
ON (s.courseid > 0 AND ctx.instanceid = s.courseid AND ctx.contextlevel = :courselevel)
|
|
|
164 |
OR (s.courseid = 0 AND ctx.id = :syscontextid)
|
|
|
165 |
WHERE s.userid = :userid";
|
|
|
166 |
$params = ['userid' => $userid, 'courselevel' => CONTEXT_COURSE, 'syscontextid' => SYSCONTEXTID];
|
|
|
167 |
$contextlist->add_from_sql($sql, $params);
|
|
|
168 |
|
|
|
169 |
// Add where appear in the history of outcomes, categories, scales or items.
|
|
|
170 |
$sql = "
|
|
|
171 |
SELECT DISTINCT ctx.id
|
|
|
172 |
FROM {context} ctx
|
|
|
173 |
JOIN {grade_outcomes_history} goh ON goh.loggeduser = :userid1 AND goh.courseid > 0
|
|
|
174 |
AND goh.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel1";
|
|
|
175 |
$params = [
|
|
|
176 |
'courselevel1' => CONTEXT_COURSE,
|
|
|
177 |
'userid1' => $userid,
|
|
|
178 |
];
|
|
|
179 |
$contextlist->add_from_sql($sql, $params);
|
|
|
180 |
$sql = "
|
|
|
181 |
SELECT DISTINCT ctx.id
|
|
|
182 |
FROM {context} ctx
|
|
|
183 |
JOIN {grade_outcomes_history} goh ON goh.loggeduser = :userid1
|
|
|
184 |
AND (goh.courseid IS NULL OR goh.courseid < 1) AND ctx.id = :syscontextid1";
|
|
|
185 |
$params = [
|
|
|
186 |
'syscontextid1' => SYSCONTEXTID,
|
|
|
187 |
'courselevel1' => CONTEXT_COURSE,
|
|
|
188 |
'userid1' => $userid,
|
|
|
189 |
];
|
|
|
190 |
$contextlist->add_from_sql($sql, $params);
|
|
|
191 |
|
|
|
192 |
$sql = "
|
|
|
193 |
SELECT DISTINCT ctx.id
|
|
|
194 |
FROM {context} ctx
|
|
|
195 |
JOIN {grade_categories_history} gch ON gch.loggeduser = :userid2
|
|
|
196 |
AND gch.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel2";
|
|
|
197 |
$params = [
|
|
|
198 |
'courselevel2' => CONTEXT_COURSE,
|
|
|
199 |
'userid2' => $userid,
|
|
|
200 |
];
|
|
|
201 |
$contextlist->add_from_sql($sql, $params);
|
|
|
202 |
$sql = "
|
|
|
203 |
SELECT DISTINCT ctx.id
|
|
|
204 |
FROM {context} ctx
|
|
|
205 |
JOIN {grade_items_history} gih ON gih.loggeduser = :userid3
|
|
|
206 |
AND gih.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel3";
|
|
|
207 |
$params = [
|
|
|
208 |
'courselevel3' => CONTEXT_COURSE,
|
|
|
209 |
'userid3' => $userid,
|
|
|
210 |
];
|
|
|
211 |
$contextlist->add_from_sql($sql, $params);
|
|
|
212 |
$sql = "
|
|
|
213 |
SELECT DISTINCT ctx.id
|
|
|
214 |
FROM {context} ctx
|
|
|
215 |
JOIN {scale_history} sh ON sh.userid = :userid4
|
|
|
216 |
AND sh.courseid > 0 AND sh.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel4";
|
|
|
217 |
$params = [
|
|
|
218 |
'courselevel4' => CONTEXT_COURSE,
|
|
|
219 |
'userid4' => $userid,
|
|
|
220 |
];
|
|
|
221 |
$contextlist->add_from_sql($sql, $params);
|
|
|
222 |
$sql = "
|
|
|
223 |
SELECT DISTINCT ctx.id
|
|
|
224 |
FROM {context} ctx
|
|
|
225 |
JOIN {scale_history} sh ON sh.loggeduser = :userid5
|
|
|
226 |
AND sh.courseid > 0 AND sh.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel4";
|
|
|
227 |
$params = [
|
|
|
228 |
'courselevel4' => CONTEXT_COURSE,
|
|
|
229 |
'userid5' => $userid,
|
|
|
230 |
];
|
|
|
231 |
$contextlist->add_from_sql($sql, $params);
|
|
|
232 |
$sql = "
|
|
|
233 |
SELECT DISTINCT ctx.id
|
|
|
234 |
FROM {context} ctx
|
|
|
235 |
JOIN {scale_history} sh ON sh.userid = :userid4 AND sh.courseid = 0 AND ctx.id = :syscontextid2";
|
|
|
236 |
$params = [
|
|
|
237 |
'syscontextid2' => SYSCONTEXTID,
|
|
|
238 |
'userid4' => $userid,
|
|
|
239 |
];
|
|
|
240 |
$contextlist->add_from_sql($sql, $params);
|
|
|
241 |
$sql = "
|
|
|
242 |
SELECT DISTINCT ctx.id
|
|
|
243 |
FROM {context} ctx
|
|
|
244 |
JOIN {scale_history} sh ON sh.loggeduser = :userid5 AND sh.courseid = 0 AND ctx.id = :syscontextid2";
|
|
|
245 |
$params = [
|
|
|
246 |
'syscontextid2' => SYSCONTEXTID,
|
|
|
247 |
'userid5' => $userid,
|
|
|
248 |
];
|
|
|
249 |
$contextlist->add_from_sql($sql, $params);
|
|
|
250 |
|
|
|
251 |
// Add where we were graded or modified grades, including in the history table.
|
|
|
252 |
$sql = "
|
|
|
253 |
SELECT DISTINCT ctx.id
|
|
|
254 |
FROM {grade_items} gi
|
|
|
255 |
JOIN {context} ctx
|
|
|
256 |
ON ctx.instanceid = gi.courseid
|
|
|
257 |
AND ctx.contextlevel = :courselevel
|
|
|
258 |
JOIN {grade_grades} gg
|
|
|
259 |
ON gg.itemid = gi.id
|
|
|
260 |
WHERE gg.userid = :userid1 OR gg.usermodified = :userid2";
|
|
|
261 |
$params = [
|
|
|
262 |
'courselevel' => CONTEXT_COURSE,
|
|
|
263 |
'userid1' => $userid,
|
|
|
264 |
'userid2' => $userid
|
|
|
265 |
];
|
|
|
266 |
$contextlist->add_from_sql($sql, $params);
|
|
|
267 |
|
|
|
268 |
$sql = "
|
|
|
269 |
SELECT DISTINCT ctx.id
|
|
|
270 |
FROM {grade_items} gi
|
|
|
271 |
JOIN {context} ctx
|
|
|
272 |
ON ctx.instanceid = gi.courseid
|
|
|
273 |
AND ctx.contextlevel = :courselevel
|
|
|
274 |
JOIN {grade_grades_history} ggh
|
|
|
275 |
ON ggh.itemid = gi.id
|
|
|
276 |
WHERE ggh.userid = :userid1
|
|
|
277 |
OR ggh.loggeduser = :userid2
|
|
|
278 |
OR ggh.usermodified = :userid3";
|
|
|
279 |
$params = [
|
|
|
280 |
'courselevel' => CONTEXT_COURSE,
|
|
|
281 |
'userid1' => $userid,
|
|
|
282 |
'userid2' => $userid,
|
|
|
283 |
'userid3' => $userid
|
|
|
284 |
];
|
|
|
285 |
$contextlist->add_from_sql($sql, $params);
|
|
|
286 |
|
|
|
287 |
// Historical grades can be made orphans when the corresponding itemid is deleted. When that happens
|
|
|
288 |
// we cannot tie the historical grade to a course context, so we report the user context as a last resort.
|
|
|
289 |
$sql = "
|
|
|
290 |
SELECT DISTINCT ctx.id
|
|
|
291 |
FROM {context} ctx
|
|
|
292 |
JOIN {grade_grades_history} ggh
|
|
|
293 |
ON ctx.contextlevel = :userlevel
|
|
|
294 |
AND ggh.userid = ctx.instanceid
|
|
|
295 |
AND (
|
|
|
296 |
ggh.userid = :userid1
|
|
|
297 |
OR ggh.usermodified = :userid2
|
|
|
298 |
OR ggh.loggeduser = :userid3
|
|
|
299 |
)
|
|
|
300 |
LEFT JOIN {grade_items} gi
|
|
|
301 |
ON ggh.itemid = gi.id
|
|
|
302 |
WHERE gi.id IS NULL";
|
|
|
303 |
$params = [
|
|
|
304 |
'userlevel' => CONTEXT_USER,
|
|
|
305 |
'userid1' => $userid,
|
|
|
306 |
'userid2' => $userid,
|
|
|
307 |
'userid3' => $userid
|
|
|
308 |
];
|
|
|
309 |
$contextlist->add_from_sql($sql, $params);
|
|
|
310 |
|
|
|
311 |
return $contextlist;
|
|
|
312 |
}
|
|
|
313 |
|
|
|
314 |
/**
|
|
|
315 |
* Get the list of contexts that contain user information for the specified user.
|
|
|
316 |
*
|
|
|
317 |
* @param \core_privacy\local\request\userlist $userlist The userlist containing the list of users who have data
|
|
|
318 |
* in this context/plugin combination.
|
|
|
319 |
*/
|
|
|
320 |
public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
|
|
|
321 |
$context = $userlist->get_context();
|
|
|
322 |
|
|
|
323 |
if ($context->contextlevel == CONTEXT_COURSE) {
|
|
|
324 |
$params = ['contextinstanceid' => $context->instanceid];
|
|
|
325 |
|
|
|
326 |
$sql = "SELECT usermodified
|
|
|
327 |
FROM {grade_outcomes}
|
|
|
328 |
WHERE courseid = :contextinstanceid";
|
|
|
329 |
$userlist->add_from_sql('usermodified', $sql, $params);
|
|
|
330 |
|
|
|
331 |
$sql = "SELECT loggeduser
|
|
|
332 |
FROM {grade_outcomes_history}
|
|
|
333 |
WHERE courseid = :contextinstanceid";
|
|
|
334 |
$userlist->add_from_sql('loggeduser', $sql, $params);
|
|
|
335 |
|
|
|
336 |
$sql = "SELECT userid
|
|
|
337 |
FROM {scale}
|
|
|
338 |
WHERE courseid = :contextinstanceid";
|
|
|
339 |
$userlist->add_from_sql('userid', $sql, $params);
|
|
|
340 |
|
|
|
341 |
$sql = "SELECT loggeduser, userid
|
|
|
342 |
FROM {scale_history}
|
|
|
343 |
WHERE courseid = :contextinstanceid";
|
|
|
344 |
$userlist->add_from_sql('loggeduser', $sql, $params);
|
|
|
345 |
$userlist->add_from_sql('userid', $sql, $params);
|
|
|
346 |
|
|
|
347 |
$sql = "SELECT loggeduser
|
|
|
348 |
FROM {grade_items_history}
|
|
|
349 |
WHERE courseid = :contextinstanceid";
|
|
|
350 |
$userlist->add_from_sql('loggeduser', $sql, $params);
|
|
|
351 |
|
|
|
352 |
$sql = "SELECT ggh.userid
|
|
|
353 |
FROM {grade_grades_history} ggh
|
|
|
354 |
JOIN {grade_items} gi ON ggh.itemid = gi.id
|
|
|
355 |
WHERE gi.courseid = :contextinstanceid";
|
|
|
356 |
$userlist->add_from_sql('userid', $sql, $params);
|
|
|
357 |
|
|
|
358 |
$sql = "SELECT gg.userid, gg.usermodified
|
|
|
359 |
FROM {grade_grades} gg
|
|
|
360 |
JOIN {grade_items} gi ON gg.itemid = gi.id
|
|
|
361 |
WHERE gi.courseid = :contextinstanceid";
|
|
|
362 |
$userlist->add_from_sql('userid', $sql, $params);
|
|
|
363 |
$userlist->add_from_sql('usermodified', $sql, $params);
|
|
|
364 |
|
|
|
365 |
$sql = "SELECT loggeduser
|
|
|
366 |
FROM {grade_categories_history}
|
|
|
367 |
WHERE courseid = :contextinstanceid";
|
|
|
368 |
$userlist->add_from_sql('loggeduser', $sql, $params);
|
|
|
369 |
}
|
|
|
370 |
|
|
|
371 |
// None of these are currently used (user deletion).
|
|
|
372 |
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
|
|
373 |
$params = ['contextinstanceid' => 0];
|
|
|
374 |
|
|
|
375 |
$sql = "SELECT usermodified
|
|
|
376 |
FROM {grade_outcomes}
|
|
|
377 |
WHERE (courseid IS NULL OR courseid < 1)";
|
|
|
378 |
$userlist->add_from_sql('usermodified', $sql, []);
|
|
|
379 |
|
|
|
380 |
$sql = "SELECT loggeduser
|
|
|
381 |
FROM {grade_outcomes_history}
|
|
|
382 |
WHERE (courseid IS NULL OR courseid < 1)";
|
|
|
383 |
$userlist->add_from_sql('loggeduser', $sql, []);
|
|
|
384 |
|
|
|
385 |
$sql = "SELECT userid
|
|
|
386 |
FROM {scale}
|
|
|
387 |
WHERE courseid = :contextinstanceid";
|
|
|
388 |
$userlist->add_from_sql('userid', $sql, $params);
|
|
|
389 |
|
|
|
390 |
$sql = "SELECT loggeduser, userid
|
|
|
391 |
FROM {scale_history}
|
|
|
392 |
WHERE courseid = :contextinstanceid";
|
|
|
393 |
$userlist->add_from_sql('loggeduser', $sql, $params);
|
|
|
394 |
$userlist->add_from_sql('userid', $sql, $params);
|
|
|
395 |
}
|
|
|
396 |
|
|
|
397 |
if ($context->contextlevel == CONTEXT_USER) {
|
|
|
398 |
// If the grade item has been removed and we have an orphan entry then we link to the
|
|
|
399 |
// user context.
|
|
|
400 |
$sql = "SELECT ggh.userid
|
|
|
401 |
FROM {grade_grades_history} ggh
|
|
|
402 |
LEFT JOIN {grade_items} gi ON ggh.itemid = gi.id
|
|
|
403 |
WHERE gi.id IS NULL
|
|
|
404 |
AND ggh.userid = :contextinstanceid";
|
|
|
405 |
$userlist->add_from_sql('userid', $sql, ['contextinstanceid' => $context->instanceid]);
|
|
|
406 |
}
|
|
|
407 |
}
|
|
|
408 |
|
|
|
409 |
/**
|
|
|
410 |
* Export all user data for the specified user, in the specified contexts.
|
|
|
411 |
*
|
|
|
412 |
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
|
|
413 |
*/
|
|
|
414 |
public static function export_user_data(approved_contextlist $contextlist) {
|
|
|
415 |
global $DB;
|
|
|
416 |
|
|
|
417 |
$user = $contextlist->get_user();
|
|
|
418 |
$userid = $user->id;
|
|
|
419 |
$contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) use ($userid) {
|
|
|
420 |
if ($context->contextlevel == CONTEXT_COURSE) {
|
|
|
421 |
$carry[$context->contextlevel][] = $context;
|
|
|
422 |
|
|
|
423 |
} else if ($context->contextlevel == CONTEXT_USER) {
|
|
|
424 |
$carry[$context->contextlevel][] = $context;
|
|
|
425 |
|
|
|
426 |
}
|
|
|
427 |
|
|
|
428 |
return $carry;
|
|
|
429 |
}, [
|
|
|
430 |
CONTEXT_USER => [],
|
|
|
431 |
CONTEXT_COURSE => []
|
|
|
432 |
]);
|
|
|
433 |
|
|
|
434 |
$rootpath = [get_string('grades', 'core_grades')];
|
|
|
435 |
$relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);
|
|
|
436 |
|
|
|
437 |
// Export the outcomes.
|
|
|
438 |
static::export_user_data_outcomes_in_contexts($contextlist);
|
|
|
439 |
|
|
|
440 |
// Export the scales.
|
|
|
441 |
static::export_user_data_scales_in_contexts($contextlist);
|
|
|
442 |
|
|
|
443 |
// Export the historical grades which have become orphans (their grade items were deleted).
|
|
|
444 |
// We place those in ther user context of the graded user.
|
|
|
445 |
$userids = array_values(array_map(function($context) {
|
|
|
446 |
return $context->instanceid;
|
|
|
447 |
}, $contexts[CONTEXT_USER]));
|
|
|
448 |
if (!empty($userids)) {
|
|
|
449 |
|
|
|
450 |
// Export own historical grades and related ones.
|
|
|
451 |
list($inuseridsql, $inuseridparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
|
|
452 |
list($inusermodifiedsql, $inusermodifiedparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
|
|
453 |
list($inloggedusersql, $inloggeduserparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
|
|
454 |
$usercontext = $contexts[CONTEXT_USER];
|
|
|
455 |
$gghfields = static::get_fields_sql('grade_grades_history', 'ggh', 'ggh_');
|
|
|
456 |
$sql = "
|
|
|
457 |
SELECT $gghfields, ctx.id as ctxid
|
|
|
458 |
FROM {grade_grades_history} ggh
|
|
|
459 |
JOIN {context} ctx
|
|
|
460 |
ON ctx.instanceid = ggh.userid
|
|
|
461 |
AND ctx.contextlevel = :userlevel
|
|
|
462 |
LEFT JOIN {grade_items} gi
|
|
|
463 |
ON gi.id = ggh.itemid
|
|
|
464 |
WHERE gi.id IS NULL
|
|
|
465 |
AND (ggh.userid $inuseridsql
|
|
|
466 |
OR ggh.usermodified $inusermodifiedsql
|
|
|
467 |
OR ggh.loggeduser $inloggedusersql)
|
|
|
468 |
AND (ggh.userid = :userid1
|
|
|
469 |
OR ggh.usermodified = :userid2
|
|
|
470 |
OR ggh.loggeduser = :userid3)
|
|
|
471 |
ORDER BY ggh.userid, ggh.timemodified, ggh.id";
|
|
|
472 |
$params = array_merge($inuseridparams, $inusermodifiedparams, $inloggeduserparams,
|
|
|
473 |
['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid, 'userlevel' => CONTEXT_USER]);
|
|
|
474 |
|
|
|
475 |
$deletedstr = get_string('privacy:request:unknowndeletedgradeitem', 'core_grades');
|
|
|
476 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
477 |
static::recordset_loop_and_export($recordset, 'ctxid', [], function($carry, $record) use ($deletedstr, $userid) {
|
|
|
478 |
$context = context::instance_by_id($record->ctxid);
|
|
|
479 |
$gghrecord = static::extract_record($record, 'ggh_');
|
|
|
480 |
|
|
|
481 |
// Orphan grades do not have items, so we do not recreate a grade_grade item, and we do not format grades.
|
|
|
482 |
$carry[] = [
|
|
|
483 |
'name' => $deletedstr,
|
|
|
484 |
'graded_user_was_you' => transform::yesno($userid == $gghrecord->userid),
|
|
|
485 |
'grade' => $gghrecord->finalgrade,
|
|
|
486 |
'feedback' => format_text($gghrecord->feedback, $gghrecord->feedbackformat, ['context' => $context]),
|
|
|
487 |
'information' => format_text($gghrecord->information, $gghrecord->informationformat, ['context' => $context]),
|
|
|
488 |
'timemodified' => transform::datetime($gghrecord->timemodified),
|
|
|
489 |
'logged_in_user_was_you' => transform::yesno($userid == $gghrecord->loggeduser),
|
|
|
490 |
'author_of_change_was_you' => transform::yesno($userid == $gghrecord->usermodified),
|
|
|
491 |
'action' => static::transform_history_action($gghrecord->action)
|
|
|
492 |
];
|
|
|
493 |
|
|
|
494 |
return $carry;
|
|
|
495 |
|
|
|
496 |
}, function($ctxid, $data) use ($rootpath) {
|
|
|
497 |
$context = context::instance_by_id($ctxid);
|
|
|
498 |
writer::with_context($context)->export_related_data($rootpath, 'history', (object) ['grades' => $data]);
|
|
|
499 |
});
|
|
|
500 |
}
|
|
|
501 |
|
|
|
502 |
// Find out the course IDs.
|
|
|
503 |
$courseids = array_values(array_map(function($context) {
|
|
|
504 |
return $context->instanceid;
|
|
|
505 |
}, $contexts[CONTEXT_COURSE]));
|
|
|
506 |
if (empty($courseids)) {
|
|
|
507 |
return;
|
|
|
508 |
}
|
|
|
509 |
list($incoursesql, $incourseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
|
|
|
510 |
|
|
|
511 |
// Ensure that the grades are final and do not need regrading.
|
|
|
512 |
array_walk($courseids, function($courseid) {
|
|
|
513 |
grade_regrade_final_grades($courseid);
|
|
|
514 |
});
|
|
|
515 |
|
|
|
516 |
// Export own grades.
|
|
|
517 |
$ggfields = static::get_fields_sql('grade_grade', 'gg', 'gg_');
|
|
|
518 |
$gifields = static::get_fields_sql('grade_item', 'gi', 'gi_');
|
|
|
519 |
$scalefields = static::get_fields_sql('grade_scale', 'sc', 'sc_');
|
|
|
520 |
$sql = "
|
|
|
521 |
SELECT $ggfields, $gifields, $scalefields
|
|
|
522 |
FROM {grade_grades} gg
|
|
|
523 |
JOIN {grade_items} gi
|
|
|
524 |
ON gi.id = gg.itemid
|
|
|
525 |
LEFT JOIN {scale} sc
|
|
|
526 |
ON sc.id = gi.scaleid
|
|
|
527 |
WHERE gi.courseid $incoursesql
|
|
|
528 |
AND gg.userid = :userid
|
|
|
529 |
ORDER BY gi.courseid, gi.id, gg.id";
|
|
|
530 |
$params = array_merge($incourseparams, ['userid' => $userid]);
|
|
|
531 |
|
|
|
532 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
533 |
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
|
|
|
534 |
$context = context_course::instance($record->gi_courseid);
|
|
|
535 |
$gg = static::extract_grade_grade_from_record($record);
|
|
|
536 |
$carry[] = static::transform_grade($gg, $context, false);
|
|
|
537 |
|
|
|
538 |
return $carry;
|
|
|
539 |
|
|
|
540 |
}, function($courseid, $data) use ($rootpath) {
|
|
|
541 |
$context = context_course::instance($courseid);
|
|
|
542 |
|
|
|
543 |
$pathtofiles = [
|
|
|
544 |
get_string('grades', 'core_grades'),
|
|
|
545 |
get_string('feedbackfiles', 'core_grades')
|
|
|
546 |
];
|
|
|
547 |
foreach ($data as $key => $grades) {
|
|
|
548 |
$gg = $grades['gradeobject'];
|
|
|
549 |
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
|
|
|
550 |
GRADE_FEEDBACK_FILEAREA, $gg->id);
|
|
|
551 |
unset($data[$key]['gradeobject']); // Do not want to export this later.
|
|
|
552 |
}
|
|
|
553 |
|
|
|
554 |
writer::with_context($context)->export_data($rootpath, (object) ['grades' => $data]);
|
|
|
555 |
});
|
|
|
556 |
|
|
|
557 |
// Export own historical grades in courses.
|
|
|
558 |
$gghfields = static::get_fields_sql('grade_grades_history', 'ggh', 'ggh_');
|
|
|
559 |
$sql = "
|
|
|
560 |
SELECT $gghfields, $gifields, $scalefields
|
|
|
561 |
FROM {grade_grades_history} ggh
|
|
|
562 |
JOIN {grade_items} gi
|
|
|
563 |
ON gi.id = ggh.itemid
|
|
|
564 |
LEFT JOIN {scale} sc
|
|
|
565 |
ON sc.id = gi.scaleid
|
|
|
566 |
WHERE gi.courseid $incoursesql
|
|
|
567 |
AND ggh.userid = :userid
|
|
|
568 |
ORDER BY gi.courseid, ggh.timemodified, ggh.id";
|
|
|
569 |
$params = array_merge($incourseparams, ['userid' => $userid]);
|
|
|
570 |
|
|
|
571 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
572 |
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
|
|
|
573 |
$context = context_course::instance($record->gi_courseid);
|
|
|
574 |
$gg = static::extract_grade_grade_from_record($record, true);
|
|
|
575 |
$carry[] = array_merge(static::transform_grade($gg, $context, true), [
|
|
|
576 |
'action' => static::transform_history_action($record->ggh_action)
|
|
|
577 |
]);
|
|
|
578 |
return $carry;
|
|
|
579 |
|
|
|
580 |
}, function($courseid, $data) use ($rootpath) {
|
|
|
581 |
$context = context_course::instance($courseid);
|
|
|
582 |
|
|
|
583 |
$pathtofiles = [
|
|
|
584 |
get_string('grades', 'core_grades'),
|
|
|
585 |
get_string('feedbackhistoryfiles', 'core_grades')
|
|
|
586 |
];
|
|
|
587 |
foreach ($data as $key => $grades) {
|
|
|
588 |
/** @var grade_grade_with_history */
|
|
|
589 |
$gg = $grades['gradeobject'];
|
|
|
590 |
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
|
|
|
591 |
GRADE_HISTORY_FEEDBACK_FILEAREA, $gg->historyid);
|
|
|
592 |
unset($data[$key]['gradeobject']); // Do not want to export this later.
|
|
|
593 |
}
|
|
|
594 |
|
|
|
595 |
writer::with_context($context)->export_related_data($rootpath, 'history', (object) ['grades' => $data]);
|
|
|
596 |
});
|
|
|
597 |
|
|
|
598 |
// Export edits of categories history.
|
|
|
599 |
$sql = "
|
|
|
600 |
SELECT gch.id, gch.courseid, gch.fullname, gch.timemodified, gch.action
|
|
|
601 |
FROM {grade_categories_history} gch
|
|
|
602 |
WHERE gch.courseid $incoursesql
|
|
|
603 |
AND gch.loggeduser = :userid
|
|
|
604 |
ORDER BY gch.courseid, gch.timemodified, gch.id";
|
|
|
605 |
$params = array_merge($incourseparams, ['userid' => $userid]);
|
|
|
606 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
607 |
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
|
|
|
608 |
$carry[] = [
|
|
|
609 |
'name' => $record->fullname,
|
|
|
610 |
'timemodified' => transform::datetime($record->timemodified),
|
|
|
611 |
'logged_in_user_was_you' => transform::yesno(true),
|
|
|
612 |
'action' => static::transform_history_action($record->action),
|
|
|
613 |
];
|
|
|
614 |
return $carry;
|
|
|
615 |
|
|
|
616 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
617 |
$context = context_course::instance($courseid);
|
|
|
618 |
writer::with_context($context)->export_related_data($relatedtomepath, 'categories_history',
|
|
|
619 |
(object) ['modified_records' => $data]);
|
|
|
620 |
});
|
|
|
621 |
|
|
|
622 |
// Export edits of items history.
|
|
|
623 |
$sql = "
|
|
|
624 |
SELECT gih.id, gih.courseid, gih.itemname, gih.itemmodule, gih.iteminfo, gih.timemodified, gih.action
|
|
|
625 |
FROM {grade_items_history} gih
|
|
|
626 |
WHERE gih.courseid $incoursesql
|
|
|
627 |
AND gih.loggeduser = :userid
|
|
|
628 |
ORDER BY gih.courseid, gih.timemodified, gih.id";
|
|
|
629 |
$params = array_merge($incourseparams, ['userid' => $userid]);
|
|
|
630 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
631 |
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
|
|
|
632 |
$carry[] = [
|
|
|
633 |
'name' => $record->itemname,
|
|
|
634 |
'module' => $record->itemmodule,
|
|
|
635 |
'info' => $record->iteminfo,
|
|
|
636 |
'timemodified' => transform::datetime($record->timemodified),
|
|
|
637 |
'logged_in_user_was_you' => transform::yesno(true),
|
|
|
638 |
'action' => static::transform_history_action($record->action),
|
|
|
639 |
];
|
|
|
640 |
return $carry;
|
|
|
641 |
|
|
|
642 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
643 |
$context = context_course::instance($courseid);
|
|
|
644 |
writer::with_context($context)->export_related_data($relatedtomepath, 'items_history',
|
|
|
645 |
(object) ['modified_records' => $data]);
|
|
|
646 |
});
|
|
|
647 |
|
|
|
648 |
// Export edits of grades in course.
|
|
|
649 |
$sql = "
|
|
|
650 |
SELECT $ggfields, $gifields, $scalefields
|
|
|
651 |
FROM {grade_grades} gg
|
|
|
652 |
JOIN {grade_items} gi
|
|
|
653 |
ON gg.itemid = gi.id
|
|
|
654 |
LEFT JOIN {scale} sc
|
|
|
655 |
ON sc.id = gi.scaleid
|
|
|
656 |
WHERE gi.courseid $incoursesql
|
|
|
657 |
AND gg.userid <> :userid1 -- Our grades have already been exported.
|
|
|
658 |
AND gg.usermodified = :userid2
|
|
|
659 |
ORDER BY gi.courseid, gg.timemodified, gg.id";
|
|
|
660 |
$params = array_merge($incourseparams, ['userid1' => $userid, 'userid2' => $userid]);
|
|
|
661 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
662 |
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) {
|
|
|
663 |
$context = context_course::instance($record->gi_courseid);
|
|
|
664 |
$gg = static::extract_grade_grade_from_record($record);
|
|
|
665 |
$carry[] = array_merge(static::transform_grade($gg, $context, false), [
|
|
|
666 |
'userid' => transform::user($gg->userid),
|
|
|
667 |
'created_or_modified_by_you' => transform::yesno(true),
|
|
|
668 |
]);
|
|
|
669 |
return $carry;
|
|
|
670 |
|
|
|
671 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
672 |
$context = context_course::instance($courseid);
|
|
|
673 |
|
|
|
674 |
$pathtofiles = [
|
|
|
675 |
get_string('grades', 'core_grades'),
|
|
|
676 |
get_string('feedbackfiles', 'core_grades')
|
|
|
677 |
];
|
|
|
678 |
foreach ($data as $key => $grades) {
|
|
|
679 |
$gg = $grades['gradeobject'];
|
|
|
680 |
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
|
|
|
681 |
GRADE_FEEDBACK_FILEAREA, $gg->id);
|
|
|
682 |
unset($data[$key]['gradeobject']); // Do not want to export this later.
|
|
|
683 |
}
|
|
|
684 |
|
|
|
685 |
writer::with_context($context)->export_related_data($relatedtomepath, 'grades', (object) ['grades' => $data]);
|
|
|
686 |
});
|
|
|
687 |
|
|
|
688 |
// Export edits of grades history in course.
|
|
|
689 |
$sql = "
|
|
|
690 |
SELECT $gghfields, $gifields, $scalefields, ggh.loggeduser AS loggeduser
|
|
|
691 |
FROM {grade_grades_history} ggh
|
|
|
692 |
JOIN {grade_items} gi
|
|
|
693 |
ON ggh.itemid = gi.id
|
|
|
694 |
LEFT JOIN {scale} sc
|
|
|
695 |
ON sc.id = gi.scaleid
|
|
|
696 |
WHERE gi.courseid $incoursesql
|
|
|
697 |
AND ggh.userid <> :userid1 -- We've already exported our history.
|
|
|
698 |
AND (ggh.loggeduser = :userid2
|
|
|
699 |
OR ggh.usermodified = :userid3)
|
|
|
700 |
ORDER BY gi.courseid, ggh.timemodified, ggh.id";
|
|
|
701 |
$params = array_merge($incourseparams, ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid]);
|
|
|
702 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
703 |
static::recordset_loop_and_export($recordset, 'gi_courseid', [], function($carry, $record) use ($userid) {
|
|
|
704 |
$context = context_course::instance($record->gi_courseid);
|
|
|
705 |
$gg = static::extract_grade_grade_from_record($record, true);
|
|
|
706 |
$carry[] = array_merge(static::transform_grade($gg, $context, true), [
|
|
|
707 |
'userid' => transform::user($gg->userid),
|
|
|
708 |
'logged_in_user_was_you' => transform::yesno($userid == $record->loggeduser),
|
|
|
709 |
'author_of_change_was_you' => transform::yesno($userid == $gg->usermodified),
|
|
|
710 |
'action' => static::transform_history_action($record->ggh_action),
|
|
|
711 |
]);
|
|
|
712 |
return $carry;
|
|
|
713 |
|
|
|
714 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
715 |
$context = context_course::instance($courseid);
|
|
|
716 |
|
|
|
717 |
$pathtofiles = [
|
|
|
718 |
get_string('grades', 'core_grades'),
|
|
|
719 |
get_string('feedbackhistoryfiles', 'core_grades')
|
|
|
720 |
];
|
|
|
721 |
foreach ($data as $key => $grades) {
|
|
|
722 |
/** @var grade_grade_with_history */
|
|
|
723 |
$gg = $grades['gradeobject'];
|
|
|
724 |
writer::with_context($gg->get_context())->export_area_files($pathtofiles, GRADE_FILE_COMPONENT,
|
|
|
725 |
GRADE_HISTORY_FEEDBACK_FILEAREA, $gg->historyid);
|
|
|
726 |
unset($data[$key]['gradeobject']); // Do not want to export this later.
|
|
|
727 |
}
|
|
|
728 |
|
|
|
729 |
writer::with_context($context)->export_related_data($relatedtomepath, 'grades_history',
|
|
|
730 |
(object) ['modified_records' => $data]);
|
|
|
731 |
});
|
|
|
732 |
}
|
|
|
733 |
|
|
|
734 |
/**
|
|
|
735 |
* Delete all data for all users in the specified context.
|
|
|
736 |
*
|
|
|
737 |
* @param context $context The specific context to delete data for.
|
|
|
738 |
*/
|
|
|
739 |
public static function delete_data_for_all_users_in_context(context $context) {
|
|
|
740 |
global $DB;
|
|
|
741 |
|
|
|
742 |
switch ($context->contextlevel) {
|
|
|
743 |
case CONTEXT_USER:
|
|
|
744 |
// The user context is only reported when there are orphan historical grades, so we only delete those.
|
|
|
745 |
static::delete_orphan_historical_grades($context->instanceid);
|
|
|
746 |
break;
|
|
|
747 |
|
|
|
748 |
case CONTEXT_COURSE:
|
|
|
749 |
// We must not change the structure of the course, so we only delete user content.
|
|
|
750 |
$itemids = static::get_item_ids_from_course_ids([$context->instanceid]);
|
|
|
751 |
if (empty($itemids)) {
|
|
|
752 |
return;
|
|
|
753 |
}
|
|
|
754 |
|
|
|
755 |
self::delete_files($itemids, true);
|
|
|
756 |
self::delete_files($itemids, false);
|
|
|
757 |
|
|
|
758 |
list($insql, $inparams) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
|
|
|
759 |
$DB->delete_records_select('grade_grades', "itemid $insql", $inparams);
|
|
|
760 |
$DB->delete_records_select('grade_grades_history', "itemid $insql", $inparams);
|
|
|
761 |
break;
|
|
|
762 |
}
|
|
|
763 |
|
|
|
764 |
}
|
|
|
765 |
|
|
|
766 |
/**
|
|
|
767 |
* Delete all user data for the specified user, in the specified contexts.
|
|
|
768 |
*
|
|
|
769 |
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
|
|
770 |
*/
|
|
|
771 |
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
|
|
772 |
global $DB;
|
|
|
773 |
$userid = $contextlist->get_user()->id;
|
|
|
774 |
|
|
|
775 |
$courseids = [];
|
|
|
776 |
foreach ($contextlist->get_contexts() as $context) {
|
|
|
777 |
if ($context->contextlevel == CONTEXT_USER && $userid == $context->instanceid) {
|
|
|
778 |
// User attempts to delete data in their own context.
|
|
|
779 |
static::delete_orphan_historical_grades($userid);
|
|
|
780 |
|
|
|
781 |
} else if ($context->contextlevel == CONTEXT_COURSE) {
|
|
|
782 |
// Log the list of course IDs.
|
|
|
783 |
$courseids[] = $context->instanceid;
|
|
|
784 |
}
|
|
|
785 |
}
|
|
|
786 |
|
|
|
787 |
$itemids = static::get_item_ids_from_course_ids($courseids);
|
|
|
788 |
if (empty($itemids)) {
|
|
|
789 |
// Our job here is done!
|
|
|
790 |
return;
|
|
|
791 |
}
|
|
|
792 |
|
|
|
793 |
// Delete all the files.
|
|
|
794 |
self::delete_files($itemids, true, [$userid]);
|
|
|
795 |
self::delete_files($itemids, false, [$userid]);
|
|
|
796 |
|
|
|
797 |
// Delete all the grades.
|
|
|
798 |
list($insql, $inparams) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
|
|
|
799 |
$params = array_merge($inparams, ['userid' => $userid]);
|
|
|
800 |
|
|
|
801 |
$DB->delete_records_select('grade_grades', "itemid $insql AND userid = :userid", $params);
|
|
|
802 |
$DB->delete_records_select('grade_grades_history', "itemid $insql AND userid = :userid", $params);
|
|
|
803 |
}
|
|
|
804 |
|
|
|
805 |
|
|
|
806 |
/**
|
|
|
807 |
* Delete multiple users within a single context.
|
|
|
808 |
*
|
|
|
809 |
* @param \core_privacy\local\request\approved_userlist $userlist The approved context and user information to
|
|
|
810 |
* delete information for.
|
|
|
811 |
*/
|
|
|
812 |
public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
|
|
|
813 |
global $DB;
|
|
|
814 |
|
|
|
815 |
$context = $userlist->get_context();
|
|
|
816 |
$userids = $userlist->get_userids();
|
|
|
817 |
if ($context->contextlevel == CONTEXT_USER) {
|
|
|
818 |
if (array_search($context->instanceid, $userids) !== false) {
|
|
|
819 |
static::delete_orphan_historical_grades($context->instanceid);
|
|
|
820 |
}
|
|
|
821 |
return;
|
|
|
822 |
}
|
|
|
823 |
|
|
|
824 |
if ($context->contextlevel != CONTEXT_COURSE) {
|
|
|
825 |
return;
|
|
|
826 |
}
|
|
|
827 |
|
|
|
828 |
$itemids = static::get_item_ids_from_course_ids([$context->instanceid]);
|
|
|
829 |
if (empty($itemids)) {
|
|
|
830 |
// Our job here is done!
|
|
|
831 |
return;
|
|
|
832 |
}
|
|
|
833 |
|
|
|
834 |
// Delete all the files.
|
|
|
835 |
self::delete_files($itemids, true, $userids);
|
|
|
836 |
self::delete_files($itemids, false, $userids);
|
|
|
837 |
|
|
|
838 |
// Delete all the grades.
|
|
|
839 |
list($itemsql, $itemparams) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
|
|
|
840 |
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
|
|
841 |
$params = array_merge($itemparams, $userparams);
|
|
|
842 |
|
|
|
843 |
$DB->delete_records_select('grade_grades', "itemid $itemsql AND userid $usersql", $params);
|
|
|
844 |
$DB->delete_records_select('grade_grades_history', "itemid $itemsql AND userid $usersql", $params);
|
|
|
845 |
}
|
|
|
846 |
|
|
|
847 |
/**
|
|
|
848 |
* Delete orphan historical grades.
|
|
|
849 |
*
|
|
|
850 |
* @param int $userid The user ID.
|
|
|
851 |
* @return void
|
|
|
852 |
*/
|
|
|
853 |
protected static function delete_orphan_historical_grades($userid) {
|
|
|
854 |
global $DB;
|
|
|
855 |
$sql = "
|
|
|
856 |
SELECT ggh.id
|
|
|
857 |
FROM {grade_grades_history} ggh
|
|
|
858 |
LEFT JOIN {grade_items} gi
|
|
|
859 |
ON ggh.itemid = gi.id
|
|
|
860 |
WHERE gi.id IS NULL
|
|
|
861 |
AND ggh.userid = :userid";
|
|
|
862 |
$ids = $DB->get_fieldset_sql($sql, ['userid' => $userid]);
|
|
|
863 |
if (empty($ids)) {
|
|
|
864 |
return;
|
|
|
865 |
}
|
|
|
866 |
list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
|
|
|
867 |
|
|
|
868 |
// First, let's delete their files.
|
|
|
869 |
$sql = "
|
|
|
870 |
SELECT gi.id
|
|
|
871 |
FROM {grade_grades_history} ggh
|
|
|
872 |
JOIN {grade_items} gi
|
|
|
873 |
ON gi.id = ggh.itemid
|
|
|
874 |
WHERE ggh.userid = :userid";
|
|
|
875 |
$params = ['userid' => $userid];
|
|
|
876 |
$gradeitems = $DB->get_records_sql($sql, $params);
|
|
|
877 |
if ($gradeitems) {
|
|
|
878 |
$itemids = array_keys($gradeitems);
|
|
|
879 |
self::delete_files($itemids, true, [$userid]);
|
|
|
880 |
}
|
|
|
881 |
|
|
|
882 |
$DB->delete_records_select('grade_grades_history', "id $insql", $inparams);
|
|
|
883 |
}
|
|
|
884 |
|
|
|
885 |
/**
|
|
|
886 |
* Export the user data related to outcomes.
|
|
|
887 |
*
|
|
|
888 |
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
|
|
889 |
* @return void
|
|
|
890 |
*/
|
|
|
891 |
protected static function export_user_data_outcomes_in_contexts(approved_contextlist $contextlist) {
|
|
|
892 |
global $DB;
|
|
|
893 |
|
|
|
894 |
$rootpath = [get_string('grades', 'core_grades')];
|
|
|
895 |
$relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);
|
|
|
896 |
$userid = $contextlist->get_user()->id;
|
|
|
897 |
|
|
|
898 |
// Reorganise the contexts.
|
|
|
899 |
$reduced = array_reduce($contextlist->get_contexts(), function($carry, $context) {
|
|
|
900 |
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
|
|
901 |
$carry['in_system'] = true;
|
|
|
902 |
} else if ($context->contextlevel == CONTEXT_COURSE) {
|
|
|
903 |
$carry['courseids'][] = $context->instanceid;
|
|
|
904 |
}
|
|
|
905 |
return $carry;
|
|
|
906 |
}, [
|
|
|
907 |
'in_system' => false,
|
|
|
908 |
'courseids' => []
|
|
|
909 |
]);
|
|
|
910 |
|
|
|
911 |
// Construct SQL.
|
|
|
912 |
$sqltemplateparts = [];
|
|
|
913 |
$templateparams = [];
|
|
|
914 |
if ($reduced['in_system']) {
|
|
|
915 |
$sqltemplateparts[] = '{prefix}.courseid IS NULL';
|
|
|
916 |
}
|
|
|
917 |
if (!empty($reduced['courseids'])) {
|
|
|
918 |
list($insql, $inparams) = $DB->get_in_or_equal($reduced['courseids'], SQL_PARAMS_NAMED);
|
|
|
919 |
$sqltemplateparts[] = "{prefix}.courseid $insql";
|
|
|
920 |
$templateparams = array_merge($templateparams, $inparams);
|
|
|
921 |
}
|
|
|
922 |
if (empty($sqltemplateparts)) {
|
|
|
923 |
return;
|
|
|
924 |
}
|
|
|
925 |
$sqltemplate = '(' . implode(' OR ', $sqltemplateparts) . ')';
|
|
|
926 |
|
|
|
927 |
// Export edited outcomes.
|
|
|
928 |
$sqlwhere = str_replace('{prefix}', 'go', $sqltemplate);
|
|
|
929 |
$sql = "
|
|
|
930 |
SELECT go.id, COALESCE(go.courseid, 0) AS courseid, go.shortname, go.fullname, go.timemodified
|
|
|
931 |
FROM {grade_outcomes} go
|
|
|
932 |
WHERE $sqlwhere
|
|
|
933 |
AND go.usermodified = :userid
|
|
|
934 |
ORDER BY go.courseid, go.timemodified, go.id";
|
|
|
935 |
$params = array_merge($templateparams, ['userid' => $userid]);
|
|
|
936 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
937 |
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
|
|
|
938 |
$carry[] = [
|
|
|
939 |
'shortname' => $record->shortname,
|
|
|
940 |
'fullname' => $record->fullname,
|
|
|
941 |
'timemodified' => transform::datetime($record->timemodified),
|
|
|
942 |
'created_or_modified_by_you' => transform::yesno(true)
|
|
|
943 |
];
|
|
|
944 |
return $carry;
|
|
|
945 |
|
|
|
946 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
947 |
$context = $courseid ? context_course::instance($courseid) : context_system::instance();
|
|
|
948 |
writer::with_context($context)->export_related_data($relatedtomepath, 'outcomes',
|
|
|
949 |
(object) ['outcomes' => $data]);
|
|
|
950 |
});
|
|
|
951 |
|
|
|
952 |
// Export edits of outcomes history.
|
|
|
953 |
$sqlwhere = str_replace('{prefix}', 'goh', $sqltemplate);
|
|
|
954 |
$sql = "
|
|
|
955 |
SELECT goh.id, COALESCE(goh.courseid, 0) AS courseid, goh.shortname, goh.fullname, goh.timemodified, goh.action
|
|
|
956 |
FROM {grade_outcomes_history} goh
|
|
|
957 |
WHERE $sqlwhere
|
|
|
958 |
AND goh.loggeduser = :userid
|
|
|
959 |
ORDER BY goh.courseid, goh.timemodified, goh.id";
|
|
|
960 |
$params = array_merge($templateparams, ['userid' => $userid]);
|
|
|
961 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
962 |
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
|
|
|
963 |
$carry[] = [
|
|
|
964 |
'shortname' => $record->shortname,
|
|
|
965 |
'fullname' => $record->fullname,
|
|
|
966 |
'timemodified' => transform::datetime($record->timemodified),
|
|
|
967 |
'logged_in_user_was_you' => transform::yesno(true),
|
|
|
968 |
'action' => static::transform_history_action($record->action)
|
|
|
969 |
];
|
|
|
970 |
return $carry;
|
|
|
971 |
|
|
|
972 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
973 |
$context = $courseid ? context_course::instance($courseid) : context_system::instance();
|
|
|
974 |
writer::with_context($context)->export_related_data($relatedtomepath, 'outcomes_history',
|
|
|
975 |
(object) ['modified_records' => $data]);
|
|
|
976 |
});
|
|
|
977 |
}
|
|
|
978 |
|
|
|
979 |
/**
|
|
|
980 |
* Export the user data related to scales.
|
|
|
981 |
*
|
|
|
982 |
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
|
|
983 |
* @return void
|
|
|
984 |
*/
|
|
|
985 |
protected static function export_user_data_scales_in_contexts(approved_contextlist $contextlist) {
|
|
|
986 |
global $DB;
|
|
|
987 |
|
|
|
988 |
$rootpath = [get_string('grades', 'core_grades')];
|
|
|
989 |
$relatedtomepath = array_merge($rootpath, [get_string('privacy:path:relatedtome', 'core_grades')]);
|
|
|
990 |
$userid = $contextlist->get_user()->id;
|
|
|
991 |
|
|
|
992 |
// Reorganise the contexts.
|
|
|
993 |
$reduced = array_reduce($contextlist->get_contexts(), function($carry, $context) {
|
|
|
994 |
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
|
|
995 |
$carry['in_system'] = true;
|
|
|
996 |
} else if ($context->contextlevel == CONTEXT_COURSE) {
|
|
|
997 |
$carry['courseids'][] = $context->instanceid;
|
|
|
998 |
}
|
|
|
999 |
return $carry;
|
|
|
1000 |
}, [
|
|
|
1001 |
'in_system' => false,
|
|
|
1002 |
'courseids' => []
|
|
|
1003 |
]);
|
|
|
1004 |
|
|
|
1005 |
// Construct SQL.
|
|
|
1006 |
$sqltemplateparts = [];
|
|
|
1007 |
$templateparams = [];
|
|
|
1008 |
if ($reduced['in_system']) {
|
|
|
1009 |
$sqltemplateparts[] = '{prefix}.courseid = 0';
|
|
|
1010 |
}
|
|
|
1011 |
if (!empty($reduced['courseids'])) {
|
|
|
1012 |
list($insql, $inparams) = $DB->get_in_or_equal($reduced['courseids'], SQL_PARAMS_NAMED);
|
|
|
1013 |
$sqltemplateparts[] = "{prefix}.courseid $insql";
|
|
|
1014 |
$templateparams = array_merge($templateparams, $inparams);
|
|
|
1015 |
}
|
|
|
1016 |
if (empty($sqltemplateparts)) {
|
|
|
1017 |
return;
|
|
|
1018 |
}
|
|
|
1019 |
$sqltemplate = '(' . implode(' OR ', $sqltemplateparts) . ')';
|
|
|
1020 |
|
|
|
1021 |
// Export edited scales.
|
|
|
1022 |
$sqlwhere = str_replace('{prefix}', 's', $sqltemplate);
|
|
|
1023 |
$sql = "
|
|
|
1024 |
SELECT s.id, s.courseid, s.name, s.timemodified
|
|
|
1025 |
FROM {scale} s
|
|
|
1026 |
WHERE $sqlwhere
|
|
|
1027 |
AND s.userid = :userid
|
|
|
1028 |
ORDER BY s.courseid, s.timemodified, s.id";
|
|
|
1029 |
$params = array_merge($templateparams, ['userid' => $userid]);
|
|
|
1030 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
1031 |
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) {
|
|
|
1032 |
$carry[] = [
|
|
|
1033 |
'name' => $record->name,
|
|
|
1034 |
'timemodified' => transform::datetime($record->timemodified),
|
|
|
1035 |
'created_or_modified_by_you' => transform::yesno(true)
|
|
|
1036 |
];
|
|
|
1037 |
return $carry;
|
|
|
1038 |
|
|
|
1039 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
1040 |
$context = $courseid ? context_course::instance($courseid) : context_system::instance();
|
|
|
1041 |
writer::with_context($context)->export_related_data($relatedtomepath, 'scales',
|
|
|
1042 |
(object) ['scales' => $data]);
|
|
|
1043 |
});
|
|
|
1044 |
|
|
|
1045 |
// Export edits of scales history.
|
|
|
1046 |
$sqlwhere = str_replace('{prefix}', 'sh', $sqltemplate);
|
|
|
1047 |
$sql = "
|
|
|
1048 |
SELECT sh.id, sh.courseid, sh.name, sh.userid, sh.timemodified, sh.action, sh.loggeduser
|
|
|
1049 |
FROM {scale_history} sh
|
|
|
1050 |
WHERE $sqlwhere
|
|
|
1051 |
AND sh.loggeduser = :userid1
|
|
|
1052 |
OR sh.userid = :userid2
|
|
|
1053 |
ORDER BY sh.courseid, sh.timemodified, sh.id";
|
|
|
1054 |
$params = array_merge($templateparams, ['userid1' => $userid, 'userid2' => $userid]);
|
|
|
1055 |
$recordset = $DB->get_recordset_sql($sql, $params);
|
|
|
1056 |
static::recordset_loop_and_export($recordset, 'courseid', [], function($carry, $record) use ($userid) {
|
|
|
1057 |
$carry[] = [
|
|
|
1058 |
'name' => $record->name,
|
|
|
1059 |
'timemodified' => transform::datetime($record->timemodified),
|
|
|
1060 |
'author_of_change_was_you' => transform::yesno($record->userid == $userid),
|
|
|
1061 |
'author_of_action_was_you' => transform::yesno($record->loggeduser == $userid),
|
|
|
1062 |
'action' => static::transform_history_action($record->action)
|
|
|
1063 |
];
|
|
|
1064 |
return $carry;
|
|
|
1065 |
|
|
|
1066 |
}, function($courseid, $data) use ($relatedtomepath) {
|
|
|
1067 |
$context = $courseid ? context_course::instance($courseid) : context_system::instance();
|
|
|
1068 |
writer::with_context($context)->export_related_data($relatedtomepath, 'scales_history',
|
|
|
1069 |
(object) ['modified_records' => $data]);
|
|
|
1070 |
});
|
|
|
1071 |
}
|
|
|
1072 |
|
|
|
1073 |
/**
|
|
|
1074 |
* Extract grade_grade from a record.
|
|
|
1075 |
*
|
|
|
1076 |
* @param stdClass $record The record.
|
|
|
1077 |
* @param bool $ishistory Whether we're extracting a historical grade.
|
|
|
1078 |
* @return grade_grade
|
|
|
1079 |
*/
|
|
|
1080 |
protected static function extract_grade_grade_from_record(stdClass $record, $ishistory = false) {
|
|
|
1081 |
$prefix = $ishistory ? 'ggh_' : 'gg_';
|
|
|
1082 |
$ggrecord = static::extract_record($record, $prefix);
|
|
|
1083 |
if ($ishistory) {
|
|
|
1084 |
$gg = new grade_grade_with_history($ggrecord, false);
|
|
|
1085 |
} else {
|
|
|
1086 |
$gg = new grade_grade($ggrecord, false);
|
|
|
1087 |
}
|
|
|
1088 |
|
|
|
1089 |
// There is a grade item in the record.
|
|
|
1090 |
if (!empty($record->gi_id)) {
|
|
|
1091 |
$gi = new grade_item(static::extract_record($record, 'gi_'), false);
|
|
|
1092 |
$gg->grade_item = $gi; // This is a common hack throughout the grades API.
|
|
|
1093 |
}
|
|
|
1094 |
|
|
|
1095 |
// Load the scale, when it still exists.
|
|
|
1096 |
if (!empty($gi->scaleid) && !empty($record->sc_id)) {
|
|
|
1097 |
$scalerec = static::extract_record($record, 'sc_');
|
|
|
1098 |
$gi->scale = new grade_scale($scalerec, false);
|
|
|
1099 |
$gi->scale->load_items();
|
|
|
1100 |
}
|
|
|
1101 |
|
|
|
1102 |
return $gg;
|
|
|
1103 |
}
|
|
|
1104 |
|
|
|
1105 |
/**
|
|
|
1106 |
* Extract a record from another one.
|
|
|
1107 |
*
|
|
|
1108 |
* @param object $record The record to extract from.
|
|
|
1109 |
* @param string $prefix The prefix used.
|
|
|
1110 |
* @return object
|
|
|
1111 |
*/
|
|
|
1112 |
protected static function extract_record($record, $prefix) {
|
|
|
1113 |
$result = [];
|
|
|
1114 |
$prefixlength = strlen($prefix);
|
|
|
1115 |
foreach ($record as $key => $value) {
|
|
|
1116 |
if (strpos($key, $prefix) === 0) {
|
|
|
1117 |
$result[substr($key, $prefixlength)] = $value;
|
|
|
1118 |
}
|
|
|
1119 |
}
|
|
|
1120 |
return (object) $result;
|
|
|
1121 |
}
|
|
|
1122 |
|
|
|
1123 |
/**
|
|
|
1124 |
* Get fields SQL for a grade related object.
|
|
|
1125 |
*
|
|
|
1126 |
* @param string $target The related object.
|
|
|
1127 |
* @param string $alias The table alias.
|
|
|
1128 |
* @param string $prefix A prefix.
|
|
|
1129 |
* @return string
|
|
|
1130 |
*/
|
|
|
1131 |
protected static function get_fields_sql($target, $alias, $prefix) {
|
|
|
1132 |
switch ($target) {
|
|
|
1133 |
case 'grade_category':
|
|
|
1134 |
case 'grade_grade':
|
|
|
1135 |
case 'grade_item':
|
|
|
1136 |
case 'grade_outcome':
|
|
|
1137 |
case 'grade_scale':
|
|
|
1138 |
$obj = new $target([], false);
|
|
|
1139 |
$fields = array_merge(array_keys($obj->optional_fields), $obj->required_fields);
|
|
|
1140 |
break;
|
|
|
1141 |
|
|
|
1142 |
case 'grade_grades_history':
|
|
|
1143 |
$fields = ['id', 'action', 'oldid', 'source', 'timemodified', 'loggeduser', 'itemid', 'userid', 'rawgrade',
|
|
|
1144 |
'rawgrademax', 'rawgrademin', 'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 'locked', 'locktime',
|
|
|
1145 |
'exported', 'overridden', 'excluded', 'feedback', 'feedbackformat', 'information', 'informationformat'];
|
|
|
1146 |
break;
|
|
|
1147 |
|
|
|
1148 |
default:
|
|
|
1149 |
throw new \coding_exception('Unrecognised target: ' . $target);
|
|
|
1150 |
break;
|
|
|
1151 |
}
|
|
|
1152 |
|
|
|
1153 |
return implode(', ', array_map(function($field) use ($alias, $prefix) {
|
|
|
1154 |
return "{$alias}.{$field} AS {$prefix}{$field}";
|
|
|
1155 |
}, $fields));
|
|
|
1156 |
}
|
|
|
1157 |
|
|
|
1158 |
/**
|
|
|
1159 |
* Get all the items IDs from course IDs.
|
|
|
1160 |
*
|
|
|
1161 |
* @param array $courseids The course IDs.
|
|
|
1162 |
* @return array
|
|
|
1163 |
*/
|
|
|
1164 |
protected static function get_item_ids_from_course_ids($courseids) {
|
|
|
1165 |
global $DB;
|
|
|
1166 |
if (empty($courseids)) {
|
|
|
1167 |
return [];
|
|
|
1168 |
}
|
|
|
1169 |
list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
|
|
|
1170 |
return $DB->get_fieldset_select('grade_items', 'id', "courseid $insql", $inparams);
|
|
|
1171 |
}
|
|
|
1172 |
|
|
|
1173 |
/**
|
|
|
1174 |
* Loop and export from a recordset.
|
|
|
1175 |
*
|
|
|
1176 |
* @param moodle_recordset $recordset The recordset.
|
|
|
1177 |
* @param string $splitkey The record key to determine when to export.
|
|
|
1178 |
* @param mixed $initial The initial data to reduce from.
|
|
|
1179 |
* @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
|
|
|
1180 |
* @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
|
|
|
1181 |
* @return void
|
|
|
1182 |
*/
|
|
|
1183 |
protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
|
|
|
1184 |
callable $reducer, callable $export) {
|
|
|
1185 |
|
|
|
1186 |
$data = $initial;
|
|
|
1187 |
$lastid = null;
|
|
|
1188 |
|
|
|
1189 |
foreach ($recordset as $record) {
|
|
|
1190 |
if ($lastid !== null && $record->{$splitkey} != $lastid) {
|
|
|
1191 |
$export($lastid, $data);
|
|
|
1192 |
$data = $initial;
|
|
|
1193 |
}
|
|
|
1194 |
$data = $reducer($data, $record);
|
|
|
1195 |
$lastid = $record->{$splitkey};
|
|
|
1196 |
}
|
|
|
1197 |
$recordset->close();
|
|
|
1198 |
|
|
|
1199 |
if ($lastid !== null) {
|
|
|
1200 |
$export($lastid, $data);
|
|
|
1201 |
}
|
|
|
1202 |
}
|
|
|
1203 |
|
|
|
1204 |
/**
|
|
|
1205 |
* Transform an history action.
|
|
|
1206 |
*
|
|
|
1207 |
* @param int $action The action.
|
|
|
1208 |
* @return string
|
|
|
1209 |
*/
|
|
|
1210 |
protected static function transform_history_action($action) {
|
|
|
1211 |
switch ($action) {
|
|
|
1212 |
case GRADE_HISTORY_INSERT:
|
|
|
1213 |
return get_string('privacy:request:historyactioninsert', 'core_grades');
|
|
|
1214 |
break;
|
|
|
1215 |
case GRADE_HISTORY_UPDATE:
|
|
|
1216 |
return get_string('privacy:request:historyactionupdate', 'core_grades');
|
|
|
1217 |
break;
|
|
|
1218 |
case GRADE_HISTORY_DELETE:
|
|
|
1219 |
return get_string('privacy:request:historyactiondelete', 'core_grades');
|
|
|
1220 |
break;
|
|
|
1221 |
}
|
|
|
1222 |
|
|
|
1223 |
return '?';
|
|
|
1224 |
}
|
|
|
1225 |
|
|
|
1226 |
/**
|
|
|
1227 |
* Transform a grade.
|
|
|
1228 |
*
|
|
|
1229 |
* @param grade_grade $gg The grade object.
|
|
|
1230 |
* @param context $context The context.
|
|
|
1231 |
* @param bool $ishistory Whether we're extracting a historical grade.
|
|
|
1232 |
* @return array
|
|
|
1233 |
*/
|
|
|
1234 |
protected static function transform_grade(grade_grade $gg, context $context, bool $ishistory) {
|
|
|
1235 |
$gi = $gg->load_grade_item();
|
|
|
1236 |
$timemodified = $gg->timemodified ? transform::datetime($gg->timemodified) : null;
|
|
|
1237 |
$timecreated = $gg->timecreated ? transform::datetime($gg->timecreated) : $timemodified; // When null we use timemodified.
|
|
|
1238 |
|
|
|
1239 |
if ($gg instanceof grade_grade_with_history) {
|
|
|
1240 |
$filearea = GRADE_HISTORY_FEEDBACK_FILEAREA;
|
|
|
1241 |
$itemid = $gg->historyid;
|
|
|
1242 |
$subpath = get_string('feedbackhistoryfiles', 'core_grades');
|
|
|
1243 |
} else {
|
|
|
1244 |
$filearea = GRADE_FEEDBACK_FILEAREA;
|
|
|
1245 |
$itemid = $gg->id;
|
|
|
1246 |
$subpath = get_string('feedbackfiles', 'core_grades');
|
|
|
1247 |
}
|
|
|
1248 |
|
|
|
1249 |
$pathtofiles = [
|
|
|
1250 |
get_string('grades', 'core_grades'),
|
|
|
1251 |
$subpath
|
|
|
1252 |
];
|
|
|
1253 |
$gg->feedback = writer::with_context($gg->get_context())->rewrite_pluginfile_urls(
|
|
|
1254 |
$pathtofiles,
|
|
|
1255 |
GRADE_FILE_COMPONENT,
|
|
|
1256 |
$filearea,
|
|
|
1257 |
$itemid,
|
|
|
1258 |
$gg->feedback
|
|
|
1259 |
);
|
|
|
1260 |
|
|
|
1261 |
return [
|
|
|
1262 |
'gradeobject' => $gg,
|
|
|
1263 |
'item' => $gi->get_name(),
|
|
|
1264 |
'grade' => $gg->finalgrade,
|
|
|
1265 |
'grade_formatted' => grade_format_gradevalue($gg->finalgrade, $gi),
|
|
|
1266 |
'feedback' => format_text($gg->feedback, $gg->feedbackformat, ['context' => $context]),
|
|
|
1267 |
'information' => format_text($gg->information, $gg->informationformat, ['context' => $context]),
|
|
|
1268 |
'timecreated' => $timecreated,
|
|
|
1269 |
'timemodified' => $timemodified,
|
|
|
1270 |
];
|
|
|
1271 |
}
|
|
|
1272 |
|
|
|
1273 |
/**
|
|
|
1274 |
* Handles deleting files for a given list of grade items.
|
|
|
1275 |
*
|
|
|
1276 |
* If an array of userids if given then it handles deleting files for those users.
|
|
|
1277 |
*
|
|
|
1278 |
* @param array $itemids
|
|
|
1279 |
* @param bool $ishistory
|
|
|
1280 |
* @param array|null $userids
|
|
|
1281 |
* @throws \coding_exception
|
|
|
1282 |
* @throws \dml_exception
|
|
|
1283 |
*/
|
|
|
1284 |
protected static function delete_files(array $itemids, bool $ishistory, array $userids = null) {
|
|
|
1285 |
global $DB;
|
|
|
1286 |
|
|
|
1287 |
list($iteminnsql, $params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
|
|
|
1288 |
if (!is_null($userids)) {
|
|
|
1289 |
list($userinnsql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
|
|
1290 |
$params = array_merge($params, $userparams);
|
|
|
1291 |
}
|
|
|
1292 |
|
|
|
1293 |
if ($ishistory) {
|
|
|
1294 |
$gradefields = static::get_fields_sql('grade_grades_history', 'ggh', 'ggh_');
|
|
|
1295 |
$gradetable = 'grade_grades_history';
|
|
|
1296 |
$tableprefix = 'ggh';
|
|
|
1297 |
$filearea = GRADE_HISTORY_FEEDBACK_FILEAREA;
|
|
|
1298 |
} else {
|
|
|
1299 |
$gradefields = static::get_fields_sql('grade_grade', 'gg', 'gg_');
|
|
|
1300 |
$gradetable = 'grade_grades';
|
|
|
1301 |
$tableprefix = 'gg';
|
|
|
1302 |
$filearea = GRADE_FEEDBACK_FILEAREA;
|
|
|
1303 |
}
|
|
|
1304 |
|
|
|
1305 |
$gifields = static::get_fields_sql('grade_item', 'gi', 'gi_');
|
|
|
1306 |
|
|
|
1307 |
$fs = new \file_storage();
|
|
|
1308 |
$sql = "SELECT $gradefields, $gifields
|
|
|
1309 |
FROM {{$gradetable}} $tableprefix
|
|
|
1310 |
JOIN {grade_items} gi
|
|
|
1311 |
ON gi.id = {$tableprefix}.itemid
|
|
|
1312 |
WHERE gi.id $iteminnsql ";
|
|
|
1313 |
if (!is_null($userids)) {
|
|
|
1314 |
$sql .= "AND {$tableprefix}.userid $userinnsql";
|
|
|
1315 |
}
|
|
|
1316 |
|
|
|
1317 |
$grades = $DB->get_recordset_sql($sql, $params);
|
|
|
1318 |
foreach ($grades as $grade) {
|
|
|
1319 |
$gg = static::extract_grade_grade_from_record($grade, $ishistory);
|
|
|
1320 |
if ($gg instanceof grade_grade_with_history) {
|
|
|
1321 |
$fileitemid = $gg->historyid;
|
|
|
1322 |
} else {
|
|
|
1323 |
$fileitemid = $gg->id;
|
|
|
1324 |
}
|
|
|
1325 |
$fs->delete_area_files($gg->get_context()->id, GRADE_FILE_COMPONENT, $filearea, $fileitemid);
|
|
|
1326 |
}
|
|
|
1327 |
$grades->close();
|
|
|
1328 |
}
|
|
|
1329 |
}
|