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_h5pactivity\privacy;
18
 
19
use core_privacy\local\metadata\collection;
20
use core_privacy\local\request\approved_contextlist;
21
use core_privacy\local\request\approved_userlist;
22
use core_privacy\local\request\contextlist;
23
use core_privacy\local\request\helper;
24
use core_privacy\local\request\transform;
25
use core_privacy\local\request\userlist;
26
use core_privacy\local\request\writer;
27
use stdClass;
28
 
29
/**
30
 * Privacy API implementation for the H5P activity plugin.
31
 *
32
 * @package    mod_h5pactivity
33
 * @category   privacy
34
 * @copyright  2020 Ferran Recio <ferran@moodle.com>
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class provider implements
38
        \core_privacy\local\metadata\provider,
39
        \core_privacy\local\request\core_userlist_provider,
40
        \core_privacy\local\request\plugin\provider {
41
 
42
    /**
43
     * Return the fields which contain personal data.
44
     *
45
     * @param   collection $collection The initialised collection to add items to.
46
     * @return  collection A listing of user data stored through this system.
47
     */
48
    public static function get_metadata(collection $collection): collection {
49
        $collection->add_database_table('h5pactivity_attempts', [
50
                'userid' => 'privacy:metadata:userid',
51
                'attempt' => 'privacy:metadata:attempt',
52
                'timecreated' => 'privacy:metadata:timecreated',
53
                'timemodified' => 'privacy:metadata:timemodified',
54
                'rawscore' => 'privacy:metadata:rawscore',
55
            ], 'privacy:metadata:xapi_track');
56
 
57
        $collection->add_database_table('h5pactivity_attempts_results', [
58
                'attempt' => 'privacy:metadata:attempt',
59
                'timecreated' => 'privacy:metadata:timecreated',
60
                'rawscore' => 'privacy:metadata:rawscore',
61
            ], 'privacy:metadata:xapi_track_results');
62
 
63
        $collection->add_subsystem_link('core_xapi', [], 'privacy:metadata:xapisummary');
64
 
65
        return $collection;
66
    }
67
 
68
    /**
69
     * Get the list of contexts that contain user information for the specified user.
70
     *
71
     * @param int $userid The user to search.
72
     * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
73
     */
74
    public static function get_contexts_for_userid(int $userid): contextlist {
75
        $sql = "SELECT ctx.id
76
                  FROM {h5pactivity_attempts} ss
77
                  JOIN {modules} m
78
                    ON m.name = :activityname
79
                  JOIN {course_modules} cm
80
                    ON cm.instance = ss.h5pactivityid
81
                   AND cm.module = m.id
82
                  JOIN {context} ctx
83
                    ON ctx.instanceid = cm.id
84
                   AND ctx.contextlevel = :modlevel
85
                 WHERE ss.userid = :userid";
86
 
87
        $params = ['activityname' => 'h5pactivity', 'modlevel' => CONTEXT_MODULE, 'userid' => $userid];
88
        $contextlist = new contextlist();
89
        $contextlist->add_from_sql($sql, $params);
90
 
91
        \core_xapi\privacy\provider::add_contexts_for_userid($contextlist, $userid, 'mod_h5pactivity');
92
 
93
        return $contextlist;
94
    }
95
 
96
    /**
97
     * Get the list of users who have data within a context.
98
     *
99
     * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
100
     */
101
    public static function get_users_in_context(userlist $userlist) {
102
        $context = $userlist->get_context();
103
 
104
        if (!is_a($context, \context_module::class)) {
105
            return;
106
        }
107
 
108
        $sql = "SELECT ss.userid
109
                  FROM {h5pactivity_attempts} ss
110
                  JOIN {modules} m
111
                    ON m.name = 'h5pactivity'
112
                  JOIN {course_modules} cm
113
                    ON cm.instance = ss.h5pactivityid
114
                   AND cm.module = m.id
115
                  JOIN {context} ctx
116
                    ON ctx.instanceid = cm.id
117
                   AND ctx.contextlevel = :modlevel
118
                 WHERE ctx.id = :contextid";
119
 
120
        $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];
121
 
122
        $userlist->add_from_sql('userid', $sql, $params);
123
 
124
        \core_xapi\privacy\provider::add_userids_for_context($userlist);
125
    }
126
 
127
    /**
128
     * Export all user data for the specified user, in the specified contexts.
129
     *
130
     * @param approved_contextlist $contextlist The approved contexts to export information for.
131
     */
132
    public static function export_user_data(approved_contextlist $contextlist) {
133
        global $DB;
134
 
135
        // Remove contexts different from CONTEXT_MODULE.
136
        $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
137
            if ($context->contextlevel == CONTEXT_MODULE) {
138
                $carry[] = $context->id;
139
            }
140
            return $carry;
141
        }, []);
142
 
143
        if (empty($contexts)) {
144
            return;
145
        }
146
 
147
        $user = $contextlist->get_user();
148
        $userid = $user->id;
149
        // Get H5P attempts data.
150
        foreach ($contexts as $contextid) {
151
            $context = \context::instance_by_id($contextid);
152
            $data = helper::get_context_data($context, $user);
153
            writer::with_context($context)->export_data([], $data);
154
            helper::export_context_files($context, $user);
155
 
156
            // Get user's xAPI state data for the particular context.
157
            $state = \core_xapi\privacy\provider::get_xapi_states_for_user($contextlist->get_user()->id,
158
                    'mod_h5pactivity', $context->instanceid);
159
            if ($state) {
160
                // If the activity has xAPI state data by the user, include it in the export.
161
                writer::with_context($context)->export_data(
162
                        [get_string('privacy:xapistate', 'core_xapi')], (object) $state);
163
            }
164
 
165
        }
166
 
167
        // Get attempts track data.
168
        list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
169
        $sql = "SELECT har.id,
170
                       ha.attempt,
171
                       har.description,
172
                       har.interactiontype,
173
                       har.response,
174
                       har.additionals,
175
                       har.rawscore,
176
                       har.maxscore,
177
                       har.duration,
178
                       har.timecreated,
179
                       ctx.id as contextid
180
                  FROM {h5pactivity_attempts_results} har
181
                  JOIN {h5pactivity_attempts} ha
182
                    ON har.attemptid = ha.id
183
                  JOIN {course_modules} cm
184
                    ON cm.instance = ha.h5pactivityid
185
                  JOIN {context} ctx
186
                    ON ctx.instanceid = cm.id
187
                 WHERE ctx.id $insql
188
                   AND ha.userid = :userid";
189
        $params = array_merge($inparams, ['userid' => $userid]);
190
 
191
        $alldata = [];
192
        $attemptsdata = $DB->get_recordset_sql($sql, $params);
193
        foreach ($attemptsdata as $track) {
194
            $alldata[$track->contextid][$track->attempt][] = (object)[
195
                    'description' => $track->description,
196
                    'response' => $track->response,
197
                    'interactiontype' => $track->interactiontype,
198
                    'additionals' => $track->additionals,
199
                    'rawscore' => $track->rawscore,
200
                    'maxscore' => $track->maxscore,
201
                    'duration' => $track->duration,
202
                    'timecreated' => transform::datetime($track->timecreated),
203
                ];
204
        }
205
        $attemptsdata->close();
206
 
207
        // The result data is organised in:
208
        // {Course name}/{H5P activity name}/{My attempts}/{Attempt X}/data.json
209
        // where X is the attempt number.
210
        array_walk($alldata, function($attemptsdata, $contextid) {
211
            $context = \context::instance_by_id($contextid);
212
            array_walk($attemptsdata, function($data, $attempt) use ($context) {
213
                $subcontext = [
214
                    get_string('myattempts', 'mod_h5pactivity'),
215
                    get_string('attempt', 'mod_h5pactivity'). " $attempt"
216
                ];
217
                writer::with_context($context)->export_data(
218
                    $subcontext,
219
                    (object)['results' => $data]
220
                );
221
            });
222
        });
223
    }
224
 
225
    /**
226
     * Delete all user data which matches the specified context.
227
     *
228
     * @param \context $context A user context.
229
     */
230
    public static function delete_data_for_all_users_in_context(\context $context) {
231
        // This should not happen, but just in case.
232
        if ($context->contextlevel != CONTEXT_MODULE) {
233
            return;
234
        }
235
 
236
        $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid);
237
        if (!$cm) {
238
            // Only h5pactivity module will be handled.
239
            return;
240
        }
241
 
242
        self::delete_all_attempts($cm);
243
 
244
        // Delete xAPI state data.
245
        \core_xapi\privacy\provider::delete_states_for_all_users($context, 'mod_h5pactivity');
246
 
247
    }
248
 
249
    /**
250
     * Delete all user data for the specified user, in the specified contexts.
251
     *
252
     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
253
     */
254
    public static function delete_data_for_user(approved_contextlist $contextlist) {
255
 
256
        foreach ($contextlist as $context) {
257
            if ($context->contextlevel != CONTEXT_MODULE) {
258
                continue;
259
            }
260
 
261
            $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid);
262
            if (!$cm) {
263
                // Only h5pactivity module will be handled.
264
                continue;
265
            }
266
 
267
            $user = $contextlist->get_user();
268
 
269
            self::delete_all_attempts($cm, $user);
270
 
271
            // Delete xAPI state data.
272
            \core_xapi\privacy\provider::delete_states_for_user($contextlist, 'mod_h5pactivity');
273
        }
274
    }
275
 
276
    /**
277
     * Delete multiple users within a single context.
278
     *
279
     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
280
     */
281
    public static function delete_data_for_users(approved_userlist $userlist) {
282
 
283
        $context = $userlist->get_context();
284
 
285
        if (!is_a($context, \context_module::class)) {
286
            return;
287
        }
288
 
289
        $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid);
290
        if (!$cm) {
291
            // Only h5pactivity module will be handled.
292
            return;
293
        }
294
 
295
        $userids = $userlist->get_userids();
296
 
297
        foreach ($userids as $userid) {
298
            self::delete_all_attempts ($cm, (object)['id' => $userid]);
299
        }
300
 
301
        // Delete xAPI states data.
302
        \core_xapi\privacy\provider::delete_states_for_userlist($userlist);
303
 
304
    }
305
 
306
    /**
307
     * Wipe all attempt data for specific course_module and an optional user.
308
     *
309
     * @param stdClass $cm a course_module record
310
     * @param stdClass $user a user record
311
     */
312
    private static function delete_all_attempts(stdClass $cm, stdClass $user = null): void {
313
        global $DB;
314
 
315
        $where = 'a.h5pactivityid = :h5pactivityid';
316
        $conditions = ['h5pactivityid' => $cm->instance];
317
        if (!empty($user)) {
318
            $where .= ' AND a.userid = :userid';
319
            $conditions['userid'] = $user->id;
320
        }
321
 
322
        $DB->delete_records_select('h5pactivity_attempts_results', "attemptid IN (
323
                SELECT a.id
324
                FROM {h5pactivity_attempts} a
325
                WHERE $where)", $conditions);
326
 
327
        $DB->delete_records('h5pactivity_attempts', $conditions);
328
    }
329
}