Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
 * Defines {@link \mod_workshop\privacy\provider} class.
19
 *
20
 * @package     mod_workshop
21
 * @category    privacy
22
 * @copyright   2018 David Mudrák <david@moodle.com>
23
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace mod_workshop\privacy;
27
 
28
use core_privacy\local\metadata\collection;
29
use core_privacy\local\request\approved_contextlist;
30
use core_privacy\local\request\approved_userlist;
31
use core_privacy\local\request\contextlist;
32
use core_privacy\local\request\deletion_criteria;
33
use core_privacy\local\request\helper;
34
use core_privacy\local\request\transform;
35
use core_privacy\local\request\userlist;
36
use core_privacy\local\request\writer;
37
 
38
defined('MOODLE_INTERNAL') || die();
39
 
40
require_once($CFG->dirroot.'/mod/workshop/locallib.php');
41
 
42
/**
43
 * Privacy API implementation for the Workshop activity module.
44
 *
45
 * @copyright 2018 David Mudrák <david@moodle.com>
46
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47
 */
48
class provider implements
49
        \core_privacy\local\metadata\provider,
50
        \core_privacy\local\request\core_userlist_provider,
51
        \core_privacy\local\request\user_preference_provider,
52
        \core_privacy\local\request\plugin\provider {
53
 
54
    /**
55
     * Describe all the places where the Workshop module stores some personal data.
56
     *
57
     * @param collection $collection Collection of items to add metadata to.
58
     * @return collection Collection with our added items.
59
     */
60
    public static function get_metadata(collection $collection): collection {
61
 
62
        $collection->add_database_table('workshop_submissions', [
63
            'workshopid' => 'privacy:metadata:workshopid',
64
            'authorid' => 'privacy:metadata:authorid',
65
            'example' => 'privacy:metadata:example',
66
            'timecreated' => 'privacy:metadata:timecreated',
67
            'timemodified' => 'privacy:metadata:timemodified',
68
            'title' => 'privacy:metadata:submissiontitle',
69
            'content' => 'privacy:metadata:submissioncontent',
70
            'contentformat' => 'privacy:metadata:submissioncontentformat',
71
            'grade' => 'privacy:metadata:submissiongrade',
72
            'gradeover' => 'privacy:metadata:submissiongradeover',
73
            'feedbackauthor' => 'privacy:metadata:feedbackauthor',
74
            'feedbackauthorformat' => 'privacy:metadata:feedbackauthorformat',
75
            'published' => 'privacy:metadata:published',
76
            'late' => 'privacy:metadata:late',
77
        ], 'privacy:metadata:workshopsubmissions');
78
 
79
        $collection->add_database_table('workshop_assessments', [
80
            'submissionid' => 'privacy:metadata:submissionid',
81
            'reviewerid' => 'privacy:metadata:reviewerid',
82
            'weight' => 'privacy:metadata:weight',
83
            'timecreated' => 'privacy:metadata:timecreated',
84
            'timemodified' => 'privacy:metadata:timemodified',
85
            'grade' => 'privacy:metadata:assessmentgrade',
86
            'gradinggrade' => 'privacy:metadata:assessmentgradinggrade',
87
            'gradinggradeover' => 'privacy:metadata:assessmentgradinggradeover',
88
            'feedbackauthor' => 'privacy:metadata:feedbackauthor',
89
            'feedbackauthorformat' => 'privacy:metadata:feedbackauthorformat',
90
            'feedbackreviewer' => 'privacy:metadata:feedbackreviewer',
91
            'feedbackreviewerformat' => 'privacy:metadata:feedbackreviewerformat',
92
        ], 'privacy:metadata:workshopassessments');
93
 
94
        $collection->add_database_table('workshop_grades', [
95
            'assessmentid' => 'privacy:metadata:assessmentid',
96
            'strategy' => 'privacy:metadata:strategy',
97
            'dimensionid' => 'privacy:metadata:dimensionid',
98
            'grade' => 'privacy:metadata:dimensiongrade',
99
            'peercomment' => 'privacy:metadata:peercomment',
100
            'peercommentformat' => 'privacy:metadata:peercommentformat',
101
        ], 'privacy:metadata:workshopgrades');
102
 
103
        $collection->add_database_table('workshop_aggregations', [
104
            'workshopid' => 'privacy:metadata:workshopid',
105
            'userid' => 'privacy:metadata:userid',
106
            'gradinggrade' => 'privacy:metadata:aggregatedgradinggrade',
107
            'timegraded' => 'privacy:metadata:timeaggregated',
108
        ], 'privacy:metadata:workshopaggregations');
109
 
110
        $collection->add_subsystem_link('core_files', [], 'privacy:metadata:subsystem:corefiles');
111
        $collection->add_subsystem_link('core_plagiarism', [], 'privacy:metadata:subsystem:coreplagiarism');
112
 
113
        $userprefs = self::get_user_prefs();
114
        foreach ($userprefs as $userpref) {
115
            if ($userpref === 'workshop_perpage') {
116
                $collection->add_user_preference('workshop_perpage', 'privacy:metadata:preference:perpage');
117
            } else {
118
                $summary = str_replace('workshop-', '', $userpref);
119
                $collection->add_user_preference($userpref, "privacy:metadata:preference:$summary");
120
            }
121
        }
122
 
123
        return $collection;
124
    }
125
 
126
    /**
127
     * Get the list of contexts that contain personal data for the specified user.
128
     *
129
     * User has personal data in the workshop if any of the following cases happens:
130
     *
131
     * - the user has submitted in the workshop
132
     * - the user has overridden a submission grade
133
     * - the user has been assigned as a reviewer of a submission
134
     * - the user has overridden a grading grade
135
     * - the user has a grading grade (existing or to be calculated)
136
     *
137
     * @param int $userid ID of the user.
138
     * @return contextlist List of contexts containing the user's personal data.
139
     */
140
    public static function get_contexts_for_userid(int $userid): contextlist {
141
 
142
        $contextlist = new contextlist();
143
        $sql = "SELECT ctx.id
144
                  FROM {course_modules} cm
145
                  JOIN {modules} m ON cm.module = m.id AND m.name = :module
146
                  JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
147
                  JOIN {workshop} w ON cm.instance = w.id
148
             LEFT JOIN {workshop_submissions} ws ON ws.workshopid = w.id
149
             LEFT JOIN {workshop_assessments} wa ON wa.submissionid = ws.id AND (
150
                    wa.reviewerid = :wareviewerid
151
                        OR
152
                    wa.gradinggradeoverby = :wagradinggradeoverby
153
                )
154
             LEFT JOIN {workshop_aggregations} wr ON wr.workshopid = w.id AND wr.userid = :wruserid
155
                 WHERE ws.authorid = :wsauthorid
156
                    OR ws.gradeoverby = :wsgradeoverby
157
                    OR wa.id IS NOT NULL
158
                    OR wr.id IS NOT NULL";
159
 
160
        $params = [
161
            'module' => 'workshop',
162
            'contextlevel' => CONTEXT_MODULE,
163
            'wsauthorid' => $userid,
164
            'wsgradeoverby' => $userid,
165
            'wareviewerid' => $userid,
166
            'wagradinggradeoverby' => $userid,
167
            'wruserid' => $userid,
168
        ];
169
 
170
        $contextlist->add_from_sql($sql, $params);
171
 
172
        return $contextlist;
173
    }
174
 
175
    /**
176
     * Get the list of users within a specific context.
177
     *
178
     * @param userlist $userlist To be filled list of users who have data in this context/plugin combination.
179
     */
180
    public static function get_users_in_context(userlist $userlist) {
181
        global $DB;
182
 
183
        $context = $userlist->get_context();
184
 
185
        if (!$context instanceof \context_module) {
186
            return;
187
        }
188
 
189
        $params = [
190
            'instanceid' => $context->instanceid,
191
            'module' => 'workshop',
192
        ];
193
 
194
        // One query to fetch them all, one query to find them, one query to bring them all and into the userlist add them.
195
        $sql = "SELECT ws.authorid, ws.gradeoverby, wa.reviewerid, wa.gradinggradeoverby, wr.userid
196
                  FROM {course_modules} cm
197
                  JOIN {modules} m ON cm.module = m.id AND m.name = :module
198
                  JOIN {workshop} w ON cm.instance = w.id
199
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id
200
             LEFT JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
201
             LEFT JOIN {workshop_aggregations} wr ON wr.workshopid = w.id
202
                 WHERE cm.id = :instanceid";
203
 
204
        $userids = [];
205
        $rs = $DB->get_recordset_sql($sql, $params);
206
 
207
        foreach ($rs as $r) {
208
            if ($r->authorid) {
209
                $userids[$r->authorid] = true;
210
            }
211
            if ($r->gradeoverby) {
212
                $userids[$r->gradeoverby] = true;
213
            }
214
            if ($r->reviewerid) {
215
                $userids[$r->reviewerid] = true;
216
            }
217
            if ($r->gradinggradeoverby) {
218
                $userids[$r->gradinggradeoverby] = true;
219
            }
220
            if ($r->userid) {
221
                $userids[$r->userid] = true;
222
            }
223
        }
224
 
225
        $rs->close();
226
 
227
        if ($userids) {
228
            $userlist->add_users(array_keys($userids));
229
        }
230
    }
231
 
232
    /**
233
     * Export personal data stored in the given contexts.
234
     *
235
     * @param approved_contextlist $contextlist List of contexts approved for export.
236
     */
237
    public static function export_user_data(approved_contextlist $contextlist) {
238
        global $DB;
239
 
240
        if (!count($contextlist)) {
241
            return;
242
        }
243
 
244
        $user = $contextlist->get_user();
245
 
246
        // Export general information about all workshops.
247
        foreach ($contextlist->get_contexts() as $context) {
248
            if ($context->contextlevel != CONTEXT_MODULE) {
249
                continue;
250
            }
251
            $data = helper::get_context_data($context, $user);
252
            static::append_extra_workshop_data($context, $user, $data, []);
253
            writer::with_context($context)->export_data([], $data);
254
            helper::export_context_files($context, $user);
255
        }
256
 
257
        // Export the user's own submission and all example submissions he/she created.
258
        static::export_submissions($contextlist);
259
 
260
        // Export all given assessments.
261
        static::export_assessments($contextlist);
262
    }
263
 
264
    /**
265
     * Export user preferences controlled by this plugin.
266
     *
267
     * @param int $userid ID of the user we are exporting data for
268
     */
269
    public static function export_user_preferences(int $userid) {
270
        $userprefs = self::get_user_prefs();
271
        $expandstr = get_string('expand');
272
        $collapsestr = get_string('collapse');
273
        foreach ($userprefs as $userpref) {
274
            $userprefval = get_user_preferences($userpref, null, $userid);
275
            if ($userprefval !== null) {
276
                $langid = str_replace('workshop-', '', $userpref);
277
                $description = get_string("privacy:metadata:preference:$langid", 'mod_workshop');
278
                if ($userpref === 'workshop_perpage') {
279
                    writer::export_user_preference('mod_workshop', $userpref, $userprefval,
280
                            get_string('privacy:metadata:preference:perpage', 'mod_workshop'));
281
                } else {
282
                    writer::export_user_preference('mod_workshop', $userpref,
283
                        $userprefval == 1 ? $collapsestr : $expandstr, $description);
284
                }
285
            }
286
        }
287
    }
288
 
289
    /**
290
     * Append additional relevant data into the base data about the workshop instance.
291
     *
292
     * Relevant are data that are important for interpreting or evaluating the performance of the user expressed in
293
     * his/her exported personal data. For example, we need to know what were the instructions for submissions or what
294
     * was the phase of the workshop when it was exported.
295
     *
296
     * @param \context $context Workshop module content.
297
     * @param stdClass $user User for which we are exporting data.
298
     * @param stdClass $data Base data about the workshop instance to append to.
299
     * @param array $subcontext Subcontext path items to eventually write files into.
300
     */
301
    protected static function append_extra_workshop_data(\context $context, \stdClass $user, \stdClass $data, array $subcontext) {
302
        global $DB;
303
 
304
        if ($context->contextlevel != CONTEXT_MODULE) {
305
            throw new \coding_exception('Unexpected context provided');
306
        }
307
 
308
        $sql = "SELECT w.instructauthors, w.instructauthorsformat, w.instructreviewers, w.instructreviewersformat, w.phase,
309
                       w.strategy, w.evaluation, w.latesubmissions, w.submissionstart, w.submissionend, w.assessmentstart,
310
                       w.assessmentend, w.conclusion, w.conclusionformat
311
                  FROM {course_modules} cm
312
                  JOIN {workshop} w ON cm.instance = w.id
313
                 WHERE cm.id = :cmid";
314
 
315
        $params = [
316
            'cmid' => $context->instanceid,
317
        ];
318
 
319
        $record = $DB->get_record_sql($sql, $params, MUST_EXIST);
320
        $writer = writer::with_context($context);
321
 
322
        if ($record->phase >= \workshop::PHASE_SUBMISSION) {
323
            $data->instructauthors = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'instructauthors', 0,
324
                $record->instructauthors);
325
            $data->instructauthorsformat = $record->instructauthorsformat;
326
        }
327
 
328
        if ($record->phase >= \workshop::PHASE_ASSESSMENT) {
329
            $data->instructreviewers = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'instructreviewers', 0,
330
                $record->instructreviewers);
331
            $data->instructreviewersformat = $record->instructreviewersformat;
332
        }
333
 
334
        if ($record->phase >= \workshop::PHASE_CLOSED) {
335
            $data->conclusion = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'conclusion', 0, $record->conclusion);
336
            $data->conclusionformat = $record->conclusionformat;
337
        }
338
 
339
        $data->strategy = \workshop::available_strategies_list()[$record->strategy];
340
        $data->evaluation = \workshop::available_evaluators_list()[$record->evaluation];
341
        $data->latesubmissions = transform::yesno($record->latesubmissions);
342
        $data->submissionstart = $record->submissionstart ? transform::datetime($record->submissionstart) : null;
343
        $data->submissionend = $record->submissionend ? transform::datetime($record->submissionend) : null;
344
        $data->assessmentstart = $record->assessmentstart ? transform::datetime($record->assessmentstart) : null;
345
        $data->assessmentend = $record->assessmentend ? transform::datetime($record->assessmentend) : null;
346
 
347
        switch ($record->phase) {
348
            case \workshop::PHASE_SETUP:
349
                $data->phase = get_string('phasesetup', 'mod_workshop');
350
                break;
351
            case \workshop::PHASE_SUBMISSION:
352
                $data->phase = get_string('phasesubmission', 'mod_workshop');
353
                break;
354
            case \workshop::PHASE_ASSESSMENT:
355
                $data->phase = get_string('phaseassessment', 'mod_workshop');
356
                break;
357
            case \workshop::PHASE_EVALUATION:
358
                $data->phase = get_string('phaseevaluation', 'mod_workshop');
359
                break;
360
            case \workshop::PHASE_CLOSED:
361
                $data->phase = get_string('phaseclosed', 'mod_workshop');
362
                break;
363
        }
364
 
365
        $writer->export_area_files($subcontext, 'mod_workshop', 'instructauthors', 0);
366
        $writer->export_area_files($subcontext, 'mod_workshop', 'instructreviewers', 0);
367
        $writer->export_area_files($subcontext, 'mod_workshop', 'conclusion', 0);
368
    }
369
 
370
    /**
371
     * Export all user's submissions and example submissions he/she created in the given contexts.
372
     *
373
     * @param approved_contextlist $contextlist List of contexts approved for export.
374
     */
375
    protected static function export_submissions(approved_contextlist $contextlist) {
376
        global $DB;
377
 
378
        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
379
        $user = $contextlist->get_user();
380
 
381
        $sql = "SELECT ws.id, ws.authorid, ws.example, ws.timecreated, ws.timemodified, ws.title,
382
                       ws.content, ws.contentformat, ws.grade, ws.gradeover, ws.feedbackauthor, ws.feedbackauthorformat,
383
                       ws.published, ws.late,
384
                       w.phase, w.course, cm.id AS cmid, ".\context_helper::get_preload_record_columns_sql('ctx')."
385
                  FROM {course_modules} cm
386
                  JOIN {modules} m ON cm.module = m.id AND m.name = :module
387
                  JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
388
                  JOIN {workshop} w ON cm.instance = w.id
389
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id AND ws.authorid = :authorid
390
                 WHERE ctx.id {$contextsql}";
391
 
392
        $params = $contextparams + [
393
            'module' => 'workshop',
394
            'contextlevel' => CONTEXT_MODULE,
395
            'authorid' => $user->id,
396
        ];
397
 
398
        $rs = $DB->get_recordset_sql($sql, $params);
399
 
400
        foreach ($rs as $record) {
401
            \context_helper::preload_from_record($record);
402
            $context = \context_module::instance($record->cmid);
403
            $writer = \core_privacy\local\request\writer::with_context($context);
404
 
405
            if ($record->example) {
406
                $subcontext = [get_string('examplesubmissions', 'mod_workshop'), $record->id];
407
                $mysubmission = null;
408
            } else {
409
                $subcontext = [get_string('mysubmission', 'mod_workshop')];
410
                $mysubmission = $record;
411
            }
412
 
413
            $phase = $record->phase;
414
            $courseid = $record->course;
415
 
416
            $data = (object) [
417
                'example' => transform::yesno($record->example),
418
                'timecreated' => transform::datetime($record->timecreated),
419
                'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
420
                'title' => $record->title,
421
                'content' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
422
                    'submission_content', $record->id, $record->content),
423
                'contentformat' => $record->contentformat,
424
                'grade' => $record->grade,
425
                'gradeover' => $record->gradeover,
426
                'feedbackauthor' => $record->feedbackauthor,
427
                'feedbackauthorformat' => $record->feedbackauthorformat,
428
                'published' => transform::yesno($record->published),
429
                'late' => transform::yesno($record->late),
430
            ];
431
 
432
            $writer->export_data($subcontext, $data);
433
            $writer->export_area_files($subcontext, 'mod_workshop', 'submission_content', $record->id);
434
            $writer->export_area_files($subcontext, 'mod_workshop', 'submission_attachment', $record->id);
435
 
436
            // Export peer-assessments of my submission if the workshop was closed. We do not export received
437
            // assessments from peers before they were actually effective. Before the workshop is closed, grades are not
438
            // pushed into the gradebook. So peer assessments did not affect evaluation of the user's performance and
439
            // they should not be considered as their personal data. This is different from assessments given by the
440
            // user that are always exported.
441
            if ($mysubmission && $phase == \workshop::PHASE_CLOSED) {
442
                $assessments = $DB->get_records('workshop_assessments', ['submissionid' => $mysubmission->id], '',
443
                    'id, reviewerid, weight, timecreated, timemodified, grade, feedbackauthor, feedbackauthorformat');
444
 
445
                foreach ($assessments as $assessment) {
446
                    $assid = $assessment->id;
447
                    $assessment->selfassessment = transform::yesno($assessment->reviewerid == $user->id);
448
                    $assessment->timecreated = transform::datetime($assessment->timecreated);
449
                    $assessment->timemodified = $assessment->timemodified ? transform::datetime($assessment->timemodified) : null;
450
                    $assessment->feedbackauthor = $writer->rewrite_pluginfile_urls($subcontext,
451
                        'mod_workshop', 'overallfeedback_content', $assid, $assessment->feedbackauthor);
452
 
453
                    $assessmentsubcontext = array_merge($subcontext, [get_string('assessments', 'mod_workshop'), $assid]);
454
 
455
                    unset($assessment->id);
456
                    unset($assessment->reviewerid);
457
 
458
                    $writer->export_data($assessmentsubcontext, $assessment);
459
                    $writer->export_area_files($assessmentsubcontext, 'mod_workshop', 'overallfeedback_content', $assid);
460
                    $writer->export_area_files($assessmentsubcontext, 'mod_workshop', 'overallfeedback_attachment', $assid);
461
 
462
                    // Export details of how the assessment forms were filled.
463
                    static::export_assessment_forms($user, $context, $assessmentsubcontext, $assid);
464
                }
465
            }
466
 
467
            // Export plagiarism data related to the submission content.
468
            // The last $linkarray argument consistent with how we call {@link plagiarism_get_links()} in the renderer.
469
            \core_plagiarism\privacy\provider::export_plagiarism_user_data($user->id, $context, $subcontext, [
470
                'userid' => $user->id,
471
                'content' => format_text($data->content, $data->contentformat, ['overflowdiv' => true]),
472
                'cmid' => $context->instanceid,
473
                'course' => $courseid,
474
            ]);
475
        }
476
 
477
        $rs->close();
478
    }
479
 
480
    /**
481
     * Export all assessments given by the user.
482
     *
483
     * @param approved_contextlist $contextlist List of contexts approved for export.
484
     */
485
    protected static function export_assessments(approved_contextlist $contextlist) {
486
        global $DB;
487
 
488
        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
489
        $user = $contextlist->get_user();
490
 
491
        $sql = "SELECT ws.authorid, ws.example, ws.timecreated, ws.timemodified, ws.title, ws.content, ws.contentformat,
492
                       wa.id, wa.submissionid, wa.reviewerid, wa.weight, wa.timecreated, wa.timemodified, wa.grade,
493
                       wa.gradinggrade, wa.gradinggradeover, wa.feedbackauthor, wa.feedbackauthorformat, wa.feedbackreviewer,
494
                       wa.feedbackreviewerformat, cm.id AS cmid, ".\context_helper::get_preload_record_columns_sql('ctx')."
495
                  FROM {course_modules} cm
496
                  JOIN {modules} m ON cm.module = m.id AND m.name = :module
497
                  JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
498
                  JOIN {workshop} w ON cm.instance = w.id
499
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id
500
                  JOIN {workshop_assessments} wa ON wa.submissionid = ws.id AND wa.reviewerid = :reviewerid
501
                 WHERE ctx.id {$contextsql}";
502
 
503
        $params = $contextparams + [
504
            'module' => 'workshop',
505
            'contextlevel' => CONTEXT_MODULE,
506
            'reviewerid' => $user->id,
507
        ];
508
 
509
        $rs = $DB->get_recordset_sql($sql, $params);
510
 
511
        foreach ($rs as $record) {
512
            \context_helper::preload_from_record($record);
513
            $context = \context_module::instance($record->cmid);
514
            $writer = \core_privacy\local\request\writer::with_context($context);
515
            $subcontext = [get_string('myassessments', 'mod_workshop'), $record->id];
516
 
517
            $data = (object) [
518
                'weight' => $record->weight,
519
                'timecreated' => transform::datetime($record->timecreated),
520
                'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
521
                'grade' => $record->grade,
522
                'gradinggrade' => $record->gradinggrade,
523
                'gradinggradeover' => $record->gradinggradeover,
524
                'feedbackauthor' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
525
                    'overallfeedback_content', $record->id, $record->feedbackauthor),
526
                'feedbackauthorformat' => $record->feedbackauthorformat,
527
                'feedbackreviewer' => $record->feedbackreviewer,
528
                'feedbackreviewerformat' => $record->feedbackreviewerformat,
529
            ];
530
 
531
            $submission = (object) [
532
                'myownsubmission' => transform::yesno($record->authorid == $user->id),
533
                'example' => transform::yesno($record->example),
534
                'timecreated' => transform::datetime($record->timecreated),
535
                'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
536
                'title' => $record->title,
537
                'content' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
538
                    'submission_content', $record->submissionid, $record->content),
539
                'contentformat' => $record->contentformat,
540
            ];
541
 
542
            $writer->export_data($subcontext, $data);
543
            $writer->export_related_data($subcontext, 'submission', $submission);
544
            $writer->export_area_files($subcontext, 'mod_workshop', 'overallfeedback_content', $record->id);
545
            $writer->export_area_files($subcontext, 'mod_workshop', 'overallfeedback_attachment', $record->id);
546
            $writer->export_area_files($subcontext, 'mod_workshop', 'submission_content', $record->submissionid);
547
            $writer->export_area_files($subcontext, 'mod_workshop', 'submission_attachment', $record->submissionid);
548
 
549
            // Export details of how the assessment forms were filled.
550
            static::export_assessment_forms($user, $context, $subcontext, $record->id);
551
        }
552
 
553
        $rs->close();
554
    }
555
 
556
    /**
557
     * Export the grading strategy data related to the particular assessment.
558
     *
559
     * @param stdClass $user User we are exporting for
560
     * @param context $context Workshop activity content
561
     * @param array $subcontext Subcontext path of the assessment
562
     * @param int $assessmentid ID of the exported assessment
563
     */
564
    protected static function export_assessment_forms(\stdClass $user, \context $context, array $subcontext, int $assessmentid) {
565
 
566
        foreach (\workshop::available_strategies_list() as $strategy => $title) {
567
            $providername = '\workshopform_'.$strategy.'\privacy\provider';
568
 
569
            if (is_subclass_of($providername, '\mod_workshop\privacy\workshopform_provider')) {
570
                component_class_callback($providername, 'export_assessment_form',
571
                    [
572
                        $user,
573
                        $context,
574
                        array_merge($subcontext, [get_string('assessmentform', 'mod_workshop'), $title]),
575
                        $assessmentid,
576
                    ]
577
                );
578
 
579
            } else {
580
                debugging('Missing class '.$providername.' implementing workshopform_provider interface', DEBUG_DEVELOPER);
581
            }
582
        }
583
    }
584
 
585
    /**
586
     * Delete personal data for all users in the context.
587
     *
588
     * @param context $context Context to delete personal data from.
589
     */
590
    public static function delete_data_for_all_users_in_context(\context $context) {
591
        global $CFG, $DB;
592
        require_once($CFG->libdir.'/gradelib.php');
593
 
594
        if ($context->contextlevel != CONTEXT_MODULE) {
595
            return;
596
        }
597
 
598
        $cm = get_coursemodule_from_id('workshop', $context->instanceid, 0, false, IGNORE_MISSING);
599
 
600
        if (!$cm) {
601
            // Probably some kind of expired context.
602
            return;
603
        }
604
 
605
        $workshop = $DB->get_record('workshop', ['id' => $cm->instance], 'id, course', MUST_EXIST);
606
 
607
        $submissions = $DB->get_records('workshop_submissions', ['workshopid' => $workshop->id], '', 'id');
608
        $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
609
 
610
        $DB->delete_records('workshop_aggregations', ['workshopid' => $workshop->id]);
611
        $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
612
        $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
613
        $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
614
 
615
        $fs = get_file_storage();
616
        $fs->delete_area_files($context->id, 'mod_workshop', 'submission_content');
617
        $fs->delete_area_files($context->id, 'mod_workshop', 'submission_attachment');
618
        $fs->delete_area_files($context->id, 'mod_workshop', 'overallfeedback_content');
619
        $fs->delete_area_files($context->id, 'mod_workshop', 'overallfeedback_attachment');
620
 
621
        grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, ['reset' => true]);
622
        grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, ['reset' => true]);
623
 
624
        \core_plagiarism\privacy\provider::delete_plagiarism_for_context($context);
625
    }
626
 
627
    /**
628
     * Delete personal data for the user in a list of contexts.
629
     *
630
     * Removing assessments of submissions from the Workshop is not trivial. Removing one user's data can easily affect
631
     * other users' grades and completion criteria. So we replace the non-essential contents with a "deleted" message,
632
     * but keep the actual info in place. The argument is that one's right for privacy should not overweight others'
633
     * right for accessing their own personal data and be evaluated on their basis.
634
     *
635
     * @param approved_contextlist $contextlist List of contexts to delete data from.
636
     */
637
    public static function delete_data_for_user(approved_contextlist $contextlist) {
638
        global $DB;
639
 
640
        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
641
        $user = $contextlist->get_user();
642
        $fs = get_file_storage();
643
 
644
        // Replace sensitive data in all submissions by the user in the given contexts.
645
 
646
        $sql = "SELECT ws.id AS submissionid
647
                  FROM {course_modules} cm
648
                  JOIN {modules} m ON cm.module = m.id AND m.name = :module
649
                  JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
650
                  JOIN {workshop} w ON cm.instance = w.id
651
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id AND ws.authorid = :authorid
652
                 WHERE ctx.id {$contextsql}";
653
 
654
        $params = $contextparams + [
655
            'module' => 'workshop',
656
            'contextlevel' => CONTEXT_MODULE,
657
            'authorid' => $user->id,
658
        ];
659
 
660
        $submissionids = $DB->get_fieldset_sql($sql, $params);
661
 
662
        if ($submissionids) {
663
            list($submissionidsql, $submissionidparams) = $DB->get_in_or_equal($submissionids, SQL_PARAMS_NAMED);
664
 
665
            $DB->set_field_select('workshop_submissions', 'title', get_string('privacy:request:delete:title',
666
                'mod_workshop'), "id $submissionidsql", $submissionidparams);
667
            $DB->set_field_select('workshop_submissions', 'content', get_string('privacy:request:delete:content',
668
                'mod_workshop'), "id $submissionidsql", $submissionidparams);
669
            $DB->set_field_select('workshop_submissions', 'feedbackauthor', get_string('privacy:request:delete:content',
670
                'mod_workshop'), "id $submissionidsql", $submissionidparams);
671
 
672
            foreach ($contextlist->get_contextids() as $contextid) {
673
                $fs->delete_area_files_select($contextid, 'mod_workshop', 'submission_content',
674
                    $submissionidsql, $submissionidparams);
675
                $fs->delete_area_files_select($contextid, 'mod_workshop', 'submission_attachment',
676
                    $submissionidsql, $submissionidparams);
677
            }
678
        }
679
 
680
        // Replace personal data in received assessments - feedback is seen as belonging to the recipient.
681
 
682
        $sql = "SELECT wa.id AS assessmentid
683
                  FROM {course_modules} cm
684
                  JOIN {modules} m ON cm.module = m.id AND m.name = :module
685
                  JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
686
                  JOIN {workshop} w ON cm.instance = w.id
687
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id AND ws.authorid = :authorid
688
                  JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
689
                 WHERE ctx.id {$contextsql}";
690
 
691
        $params = $contextparams + [
692
            'module' => 'workshop',
693
            'contextlevel' => CONTEXT_MODULE,
694
            'authorid' => $user->id,
695
        ];
696
 
697
        $assessmentids = $DB->get_fieldset_sql($sql, $params);
698
 
699
        if ($assessmentids) {
700
            list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
701
 
702
            $DB->set_field_select('workshop_assessments', 'feedbackauthor', get_string('privacy:request:delete:content',
703
                'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
704
 
705
            foreach ($contextlist->get_contextids() as $contextid) {
706
                $fs->delete_area_files_select($contextid, 'mod_workshop', 'overallfeedback_content',
707
                    $assessmentidsql, $assessmentidparams);
708
                $fs->delete_area_files_select($contextid, 'mod_workshop', 'overallfeedback_attachment',
709
                    $assessmentidsql, $assessmentidparams);
710
            }
711
        }
712
 
713
        // Replace sensitive data in provided assessments records.
714
 
715
        $sql = "SELECT wa.id AS assessmentid
716
                  FROM {course_modules} cm
717
                  JOIN {modules} m ON cm.module = m.id AND m.name = :module
718
                  JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
719
                  JOIN {workshop} w ON cm.instance = w.id
720
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id
721
                  JOIN {workshop_assessments} wa ON wa.submissionid = ws.id AND wa.reviewerid = :reviewerid
722
                 WHERE ctx.id {$contextsql}";
723
 
724
        $params = $contextparams + [
725
            'module' => 'workshop',
726
            'contextlevel' => CONTEXT_MODULE,
727
            'reviewerid' => $user->id,
728
        ];
729
 
730
        $assessmentids = $DB->get_fieldset_sql($sql, $params);
731
 
732
        if ($assessmentids) {
733
            list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
734
 
735
            $DB->set_field_select('workshop_assessments', 'feedbackreviewer', get_string('privacy:request:delete:content',
736
                'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
737
        }
738
 
739
        foreach ($contextlist as $context) {
740
            \core_plagiarism\privacy\provider::delete_plagiarism_for_user($user->id, $context);
741
        }
742
    }
743
 
744
    /**
745
     * Delete personal data for multiple users within a single workshop context.
746
     *
747
     * See documentation for {@link self::delete_data_for_user()} for more details on what we do and don't actually
748
     * delete and why.
749
     *
750
     * @param approved_userlist $userlist The approved context and user information to delete information for.
751
     */
752
    public static function delete_data_for_users(approved_userlist $userlist) {
753
        global $DB;
754
 
755
        $context = $userlist->get_context();
756
        $fs = get_file_storage();
757
 
758
        if ($context->contextlevel != CONTEXT_MODULE) {
759
            // This should not happen but let's be double sure when it comes to deleting data.
760
            return;
761
        }
762
 
763
        $cm = get_coursemodule_from_id('workshop', $context->instanceid, 0, false, IGNORE_MISSING);
764
 
765
        if (!$cm) {
766
            // Probably some kind of expired context.
767
            return;
768
        }
769
 
770
        $userids = $userlist->get_userids();
771
 
772
        if (!$userids) {
773
            return;
774
        }
775
 
776
        list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
777
 
778
        // Erase sensitive data in all submissions by all the users in the given context.
779
 
780
        $sql = "SELECT ws.id AS submissionid
781
                  FROM {workshop} w
782
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id
783
                 WHERE w.id = :workshopid AND ws.authorid $usersql";
784
 
785
        $params = $userparams + [
786
            'workshopid' => $cm->instance,
787
        ];
788
 
789
        $submissionids = $DB->get_fieldset_sql($sql, $params);
790
 
791
        if ($submissionids) {
792
            list($submissionidsql, $submissionidparams) = $DB->get_in_or_equal($submissionids, SQL_PARAMS_NAMED);
793
 
794
            $DB->set_field_select('workshop_submissions', 'title', get_string('privacy:request:delete:title',
795
                'mod_workshop'), "id $submissionidsql", $submissionidparams);
796
            $DB->set_field_select('workshop_submissions', 'content', get_string('privacy:request:delete:content',
797
                'mod_workshop'), "id $submissionidsql", $submissionidparams);
798
            $DB->set_field_select('workshop_submissions', 'feedbackauthor', get_string('privacy:request:delete:content',
799
                'mod_workshop'), "id $submissionidsql", $submissionidparams);
800
 
801
            $fs->delete_area_files_select($context->id, 'mod_workshop', 'submission_content',
802
                $submissionidsql, $submissionidparams);
803
            $fs->delete_area_files_select($context->id, 'mod_workshop', 'submission_attachment',
804
                $submissionidsql, $submissionidparams);
805
        }
806
 
807
        // Erase personal data in received assessments - feedback is seen as belonging to the recipient.
808
 
809
        $sql = "SELECT wa.id AS assessmentid
810
                  FROM {workshop} w
811
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id
812
                  JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
813
                 WHERE w.id = :workshopid AND ws.authorid $usersql";
814
 
815
        $params = $userparams + [
816
            'workshopid' => $cm->instance,
817
        ];
818
 
819
        $assessmentids = $DB->get_fieldset_sql($sql, $params);
820
 
821
        if ($assessmentids) {
822
            list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
823
 
824
            $DB->set_field_select('workshop_assessments', 'feedbackauthor', get_string('privacy:request:delete:content',
825
                'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
826
 
827
            $fs->delete_area_files_select($context->id, 'mod_workshop', 'overallfeedback_content',
828
                $assessmentidsql, $assessmentidparams);
829
            $fs->delete_area_files_select($context->id, 'mod_workshop', 'overallfeedback_attachment',
830
                $assessmentidsql, $assessmentidparams);
831
        }
832
 
833
        // Erase sensitive data in provided assessments records.
834
 
835
        $sql = "SELECT wa.id AS assessmentid
836
                  FROM {workshop} w
837
                  JOIN {workshop_submissions} ws ON ws.workshopid = w.id
838
                  JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
839
                 WHERE w.id = :workshopid AND wa.reviewerid $usersql";
840
 
841
        $params = $userparams + [
842
            'workshopid' => $cm->instance,
843
        ];
844
 
845
        $assessmentids = $DB->get_fieldset_sql($sql, $params);
846
 
847
        if ($assessmentids) {
848
            list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
849
 
850
            $DB->set_field_select('workshop_assessments', 'feedbackreviewer', get_string('privacy:request:delete:content',
851
                'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
852
        }
853
 
854
        foreach ($userids as $userid) {
855
            \core_plagiarism\privacy\provider::delete_plagiarism_for_user($userid, $context);
856
        }
857
    }
858
 
859
    /**
860
     * Get the user preferences.
861
     *
862
     * @return array List of user preferences
863
     */
864
    protected static function get_user_prefs(): array {
865
        return [
866
            'workshop_perpage',
867
            'workshop-viewlet-allexamples-collapsed',
868
            'workshop-viewlet-allsubmissions-collapsed',
869
            'workshop-viewlet-assessmentform-collapsed',
870
            'workshop-viewlet-assignedassessments-collapsed',
871
            'workshop-viewlet-cleargrades-collapsed',
872
            'workshop-viewlet-conclusion-collapsed',
873
            'workshop-viewlet-examples-collapsed',
874
            'workshop-viewlet-examplesfail-collapsed',
875
            'workshop-viewlet-gradereport-collapsed',
876
            'workshop-viewlet-instructauthors-collapsed',
877
            'workshop-viewlet-instructreviewers-collapsed',
878
            'workshop-viewlet-intro-collapsed',
879
            'workshop-viewlet-overallfeedback-collapsed',
880
            'workshop-viewlet-ownsubmission-collapsed',
881
            'workshop-viewlet-publicsubmissions-collapsed',
882
            'workshop-viewlet-yourgrades-collapsed'
883
        ];
884
    }
885
}