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
namespace mod_scorm\privacy;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
global $CFG;
22
require_once("{$CFG->dirroot}/mod/scorm/locallib.php");
23
 
24
use core_privacy\local\metadata\collection;
25
use core_privacy\local\request\approved_contextlist;
26
use core_privacy\local\request\approved_userlist;
27
use core_privacy\local\request\contextlist;
28
use core_privacy\local\request\helper;
29
use core_privacy\local\request\transform;
30
use core_privacy\local\request\userlist;
31
use core_privacy\local\request\writer;
32
 
33
/**
34
 * Privacy class for requesting user data.
35
 *
36
 * @package    mod_scorm
37
 * @copyright  2018 Sara Arjona <sara@moodle.com>
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class provider implements
41
        \core_privacy\local\metadata\provider,
42
        \core_privacy\local\request\core_userlist_provider,
43
        \core_privacy\local\request\plugin\provider {
44
 
45
    /**
46
     * Return the fields which contain personal data.
47
     *
48
     * @param   collection $collection The initialised collection to add items to.
49
     * @return  collection A listing of user data stored through this system.
50
     */
51
    public static function get_metadata(collection $collection): collection {
52
        $collection->add_database_table('scorm_attempt', [
53
                'userid' => 'privacy:metadata:userid',
54
                'attempt' => 'privacy:metadata:attempt',
55
            ], 'privacy:metadata:scorm_attempt');
56
 
57
        $collection->add_database_table('scorm_aicc_session', [
58
                'userid' => 'privacy:metadata:userid',
59
                'scormmode' => 'privacy:metadata:aicc_session:scormmode',
60
                'scormstatus' => 'privacy:metadata:aicc_session:scormstatus',
61
                'attempt' => 'privacy:metadata:attempt',
62
                'lessonstatus' => 'privacy:metadata:aicc_session:lessonstatus',
63
                'sessiontime' => 'privacy:metadata:aicc_session:sessiontime',
64
                'timecreated' => 'privacy:metadata:aicc_session:timecreated',
65
                'timemodified' => 'privacy:metadata:timemodified',
66
            ], 'privacy:metadata:scorm_aicc_session');
67
 
68
        $collection->add_external_location_link('aicc', [
69
                'data' => 'privacy:metadata:aicc:data'
70
            ], 'privacy:metadata:aicc:externalpurpose');
71
 
72
        return $collection;
73
    }
74
 
75
    /**
76
     * Get the list of contexts that contain user information for the specified user.
77
     *
78
     * @param int $userid The user to search.
79
     * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
80
     */
81
    public static function get_contexts_for_userid(int $userid): contextlist {
82
        $sql = "SELECT ctx.id
83
                  FROM {%s} ss
84
                  JOIN {modules} m
85
                    ON m.name = 'scorm'
86
                  JOIN {course_modules} cm
87
                    ON cm.instance = ss.scormid
88
                   AND cm.module = m.id
89
                  JOIN {context} ctx
90
                    ON ctx.instanceid = cm.id
91
                   AND ctx.contextlevel = :modlevel
92
                 WHERE ss.userid = :userid";
93
 
94
        $params = ['modlevel' => CONTEXT_MODULE, 'userid' => $userid];
95
        $contextlist = new contextlist();
96
        $contextlist->add_from_sql(sprintf($sql, 'scorm_attempt'), $params);
97
        $contextlist->add_from_sql(sprintf($sql, 'scorm_aicc_session'), $params);
98
 
99
        return $contextlist;
100
    }
101
 
102
    /**
103
     * Get the list of users who have data within a context.
104
     *
105
     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
106
     */
107
    public static function get_users_in_context(userlist $userlist) {
108
        $context = $userlist->get_context();
109
 
110
        if (!is_a($context, \context_module::class)) {
111
            return;
112
        }
113
 
114
        $sql = "SELECT ss.userid
115
                  FROM {%s} ss
116
                  JOIN {modules} m
117
                    ON m.name = 'scorm'
118
                  JOIN {course_modules} cm
119
                    ON cm.instance = ss.scormid
120
                   AND cm.module = m.id
121
                  JOIN {context} ctx
122
                    ON ctx.instanceid = cm.id
123
                   AND ctx.contextlevel = :modlevel
124
                 WHERE ctx.id = :contextid";
125
 
126
        $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];
127
 
128
        $userlist->add_from_sql('userid', sprintf($sql, 'scorm_attempt'), $params);
129
        $userlist->add_from_sql('userid', sprintf($sql, 'scorm_aicc_session'), $params);
130
    }
131
 
132
    /**
133
     * Export all user data for the specified user, in the specified contexts.
134
     *
135
     * @param approved_contextlist $contextlist The approved contexts to export information for.
136
     */
137
    public static function export_user_data(approved_contextlist $contextlist) {
138
        global $DB;
139
 
140
        // Remove contexts different from COURSE_MODULE.
141
        $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
142
            if ($context->contextlevel == CONTEXT_MODULE) {
143
                $carry[] = $context->id;
144
            }
145
            return $carry;
146
        }, []);
147
 
148
        if (empty($contexts)) {
149
            return;
150
        }
151
 
152
        $user = $contextlist->get_user();
153
        $userid = $user->id;
154
        // Get SCORM data.
155
        foreach ($contexts as $contextid) {
156
            $context = \context::instance_by_id($contextid);
157
            $data = helper::get_context_data($context, $user);
158
            writer::with_context($context)->export_data([], $data);
159
            helper::export_context_files($context, $user);
160
        }
161
 
162
        // Get scoes_track data.
163
        list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
164
        $sql = "SELECT v.id,
165
                       a.attempt,
166
                       e.element,
167
                       v.value,
168
                       v.timemodified,
169
                       ctx.id as contextid
170
                  FROM {scorm_attempt} a
171
                  JOIN {scorm_scoes_value} v ON a.id = v.attemptid
172
                  JOIN {scorm_element} e on e.id = v.elementid
173
                  JOIN {course_modules} cm
174
                    ON cm.instance = a.scormid
175
                  JOIN {context} ctx
176
                    ON ctx.instanceid = cm.id
177
                 WHERE ctx.id $insql
178
                   AND a.userid = :userid";
179
        $params = array_merge($inparams, ['userid' => $userid]);
180
 
181
        $alldata = [];
182
        $scoestracks = $DB->get_recordset_sql($sql, $params);
183
        foreach ($scoestracks as $track) {
184
            $alldata[$track->contextid][$track->attempt][] = (object)[
185
                    'element' => $track->element,
186
                    'value' => $track->value,
187
                    'timemodified' => transform::datetime($track->timemodified),
188
                ];
189
        }
190
        $scoestracks->close();
191
 
192
        // The scoes_track data is organised in: {Course name}/{SCORM activity name}/{My attempts}/{Attempt X}/data.json
193
        // where X is the attempt number.
194
        array_walk($alldata, function($attemptsdata, $contextid) {
195
            $context = \context::instance_by_id($contextid);
196
            array_walk($attemptsdata, function($data, $attempt) use ($context) {
197
                $subcontext = [
198
                    get_string('myattempts', 'scorm'),
199
                    get_string('attempt', 'scorm'). " $attempt"
200
                ];
201
                writer::with_context($context)->export_data(
202
                    $subcontext,
203
                    (object)['scoestrack' => $data]
204
                );
205
            });
206
        });
207
 
208
        // Get aicc_session data.
209
        $sql = "SELECT ss.id,
210
                       ss.scormmode,
211
                       ss.scormstatus,
212
                       ss.attempt,
213
                       ss.lessonstatus,
214
                       ss.sessiontime,
215
                       ss.timecreated,
216
                       ss.timemodified,
217
                       ctx.id as contextid
218
                  FROM {scorm_aicc_session} ss
219
                  JOIN {course_modules} cm
220
                    ON cm.instance = ss.scormid
221
                  JOIN {context} ctx
222
                    ON ctx.instanceid = cm.id
223
                 WHERE ctx.id $insql
224
                   AND ss.userid = :userid";
225
        $params = array_merge($inparams, ['userid' => $userid]);
226
 
227
        $alldata = [];
228
        $aiccsessions = $DB->get_recordset_sql($sql, $params);
229
        foreach ($aiccsessions as $aiccsession) {
230
            $alldata[$aiccsession->contextid][] = (object)[
231
                    'scormmode' => $aiccsession->scormmode,
232
                    'scormstatus' => $aiccsession->scormstatus,
233
                    'lessonstatus' => $aiccsession->lessonstatus,
234
                    'attempt' => $aiccsession->attempt,
235
                    'sessiontime' => $aiccsession->sessiontime,
236
                    'timecreated' => transform::datetime($aiccsession->timecreated),
237
                    'timemodified' => transform::datetime($aiccsession->timemodified),
238
                ];
239
        }
240
        $aiccsessions->close();
241
 
242
        // The aicc_session data is organised in: {Course name}/{SCORM activity name}/{My AICC sessions}/data.json
243
        // In this case, the attempt hasn't been included in the json file because it can be null.
244
        array_walk($alldata, function($data, $contextid) {
245
            $context = \context::instance_by_id($contextid);
246
            $subcontext = [
247
                get_string('myaiccsessions', 'scorm')
248
            ];
249
            writer::with_context($context)->export_data(
250
                $subcontext,
251
                (object)['sessions' => $data]
252
            );
253
        });
254
    }
255
 
256
    /**
257
     * Delete all user data which matches the specified context.
258
     *
259
     * @param context $context A user context.
260
     */
261
    public static function delete_data_for_all_users_in_context(\context $context) {
262
        // This should not happen, but just in case.
263
        if ($context->contextlevel != CONTEXT_MODULE) {
264
            return;
265
        }
266
 
267
        // Prepare SQL to gather all IDs to delete.
268
        $sql = "SELECT ss.id
269
                  FROM {%s} ss
270
                  JOIN {modules} m
271
                    ON m.name = 'scorm'
272
                  JOIN {course_modules} cm
273
                    ON cm.instance = ss.scormid
274
                   AND cm.module = m.id
275
                 WHERE cm.id = :cmid";
276
        $params = ['cmid' => $context->instanceid];
277
 
278
        static::delete_data('scorm_aicc_session', $sql, $params);
279
        $coursemodule = get_coursemodule_from_id('scorm', $context->instanceid);
280
        scorm_delete_tracks($coursemodule->instance);
281
    }
282
 
283
    /**
284
     * Delete all user data for the specified user, in the specified contexts.
285
     *
286
     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
287
     */
288
    public static function delete_data_for_user(approved_contextlist $contextlist) {
289
        global $DB;
290
 
291
        // Remove contexts different from COURSE_MODULE.
292
        $contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
293
            if ($context->contextlevel == CONTEXT_MODULE) {
294
                $carry[] = $context->id;
295
            }
296
            return $carry;
297
        }, []);
298
 
299
        if (empty($contextids)) {
300
            return;
301
        }
302
        $userid = $contextlist->get_user()->id;
303
        // Prepare SQL to gather all completed IDs.
304
        list($insql, $inparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
305
        $sql = "SELECT ss.id
306
                  FROM {%s} ss
307
                  JOIN {modules} m
308
                    ON m.name = 'scorm'
309
                  JOIN {course_modules} cm
310
                    ON cm.instance = ss.scormid
311
                   AND cm.module = m.id
312
                  JOIN {context} ctx
313
                    ON ctx.instanceid = cm.id
314
                 WHERE ss.userid = :userid
315
                   AND ctx.id $insql";
316
        $params = array_merge($inparams, ['userid' => $userid]);
317
 
318
        static::delete_data('scorm_aicc_session', $sql, $params);
319
        foreach ($contextlist->get_contexts() as $context) {
320
            if ($context->contextlevel == CONTEXT_MODULE) {
321
                $coursemodule = get_coursemodule_from_id('scorm', $context->instanceid);
322
                scorm_delete_tracks($coursemodule->instance, null, $userid);
323
            }
324
        }
325
    }
326
 
327
    /**
328
     * Delete multiple users within a single context.
329
     *
330
     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
331
     */
332
    public static function delete_data_for_users(approved_userlist $userlist) {
333
        global $DB;
334
        $context = $userlist->get_context();
335
 
336
        if (!is_a($context, \context_module::class)) {
337
            return;
338
        }
339
 
340
        // Prepare SQL to gather all completed IDs.
341
        $userids = $userlist->get_userids();
342
        list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
343
 
344
        $sql = "SELECT ss.id
345
                  FROM {%s} ss
346
                  JOIN {modules} m
347
                    ON m.name = 'scorm'
348
                  JOIN {course_modules} cm
349
                    ON cm.instance = ss.scormid
350
                   AND cm.module = m.id
351
                  JOIN {context} ctx
352
                    ON ctx.instanceid = cm.id
353
                 WHERE ctx.id = :contextid
354
                   AND ss.userid $insql";
355
        $params = array_merge($inparams, ['contextid' => $context->id]);
356
 
357
        static::delete_data('scorm_aicc_session', $sql, $params);
358
        $coursemodule = get_coursemodule_from_id('scorm', $context->instanceid);
359
        foreach ($userlist->get_userids() as $userid) {
360
            scorm_delete_tracks($coursemodule->instance, null, $userid);
361
        }
362
    }
363
 
364
    /**
365
     * Delete data from $tablename with the IDs returned by $sql query.
366
     *
367
     * @param  string $tablename  Table name where executing the SQL query.
368
     * @param  string $sql    SQL query for getting the IDs of the scoestrack entries to delete.
369
     * @param  array  $params SQL params for the query.
370
     */
371
    protected static function delete_data(string $tablename, string $sql, array $params) {
372
        global $DB;
373
 
374
        $scoestracksids = $DB->get_fieldset_sql(sprintf($sql, $tablename), $params);
375
        if (!empty($scoestracksids)) {
376
            list($insql, $inparams) = $DB->get_in_or_equal($scoestracksids, SQL_PARAMS_NAMED);
377
            $DB->delete_records_select($tablename, "id $insql", $inparams);
378
        }
379
    }
380
}