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 core_external\privacy;
18
 
19
use context;
20
use context_user;
21
use core_privacy\local\metadata\collection;
22
use core_privacy\local\request\approved_contextlist;
23
use core_privacy\local\request\transform;
24
use core_privacy\local\request\writer;
25
use core_privacy\local\request\userlist;
26
use core_privacy\local\request\approved_userlist;
27
 
28
/**
29
 * Data provider class.
30
 *
31
 * @package    core_external
32
 * @copyright  2018 Frédéric Massart
33
 * @author     Frédéric Massart <fred@branchup.tech>
34
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35
 */
36
class provider implements
37
    \core_privacy\local\metadata\provider,
38
    \core_privacy\local\request\core_userlist_provider,
39
    \core_privacy\local\request\subsystem\provider {
40
 
41
    /**
42
     * Returns metadata.
43
     *
44
     * @param collection $collection The initialised collection to add items to.
45
     * @return collection A listing of user data stored through this system.
46
     */
47
    public static function get_metadata(collection $collection): collection {
48
 
49
        $collection->add_database_table('external_tokens', [
50
            'token' => 'privacy:metadata:tokens:token',
51
            'privatetoken' => 'privacy:metadata:tokens:privatetoken',
52
            'tokentype' => 'privacy:metadata:tokens:tokentype',
53
            'userid' => 'privacy:metadata:tokens:userid',
54
            'creatorid' => 'privacy:metadata:tokens:creatorid',
55
            'iprestriction' => 'privacy:metadata:tokens:iprestriction',
56
            'validuntil' => 'privacy:metadata:tokens:validuntil',
57
            'timecreated' => 'privacy:metadata:tokens:timecreated',
58
            'lastaccess' => 'privacy:metadata:tokens:lastaccess',
59
            'name' => 'privacy:metadata:tokens:name',
60
        ], 'privacy:metadata:tokens');
61
 
62
        $collection->add_database_table('external_services_users', [
63
            'userid' => 'privacy:metadata:serviceusers:userid',
64
            'iprestriction' => 'privacy:metadata:serviceusers:iprestriction',
65
            'validuntil' => 'privacy:metadata:serviceusers:validuntil',
66
            'timecreated' => 'privacy:metadata:serviceusers:timecreated',
67
        ], 'privacy:metadata:serviceusers');
68
 
69
        return $collection;
70
    }
71
 
72
    /**
73
     * Get the list of contexts that contain user information for the specified user.
74
     *
75
     * @param int $userid The user to search.
76
     * @return \core_privacy\local\request\contextlist $contextlist The contextlist containing the list of contexts
77
     *                                                              used in this plugin.
78
     */
79
    public static function get_contexts_for_userid(int $userid): \core_privacy\local\request\contextlist {
80
        $contextlist = new \core_privacy\local\request\contextlist();
81
 
82
        $sql = "
83
            SELECT ctx.id
84
              FROM {external_tokens} t
85
              JOIN {context} ctx
86
                ON ctx.instanceid = t.userid
87
               AND ctx.contextlevel = :userlevel
88
             WHERE t.userid = :userid1
89
                OR t.creatorid = :userid2";
90
        $contextlist->add_from_sql($sql, ['userlevel' => CONTEXT_USER, 'userid1' => $userid, 'userid2' => $userid]);
91
 
92
        $sql = "
93
            SELECT ctx.id
94
              FROM {external_services_users} su
95
              JOIN {context} ctx
96
                ON ctx.instanceid = su.userid
97
               AND ctx.contextlevel = :userlevel
98
             WHERE su.userid = :userid";
99
        $contextlist->add_from_sql($sql, ['userlevel' => CONTEXT_USER, 'userid' => $userid]);
100
 
101
        return $contextlist;
102
    }
103
 
104
    /**
105
     * Get the list of users within a specific context.
106
     *
107
     * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
108
     */
109
    public static function get_users_in_context(userlist $userlist) {
110
        global $DB;
111
 
112
        $context = $userlist->get_context();
113
 
114
        if (!$context instanceof \context_user) {
115
            return;
116
        }
117
 
118
        $userid = $context->instanceid;
119
 
120
        $hasdata = false;
121
        $hasdata = $hasdata || $DB->record_exists_select('external_tokens', 'userid = ? OR creatorid = ?', [$userid, $userid]);
122
        $hasdata = $hasdata || $DB->record_exists('external_services_users', ['userid' => $userid]);
123
 
124
        if ($hasdata) {
125
            $userlist->add_user($userid);
126
        }
127
    }
128
 
129
    /**
130
     * Export all user data for the specified user, in the specified contexts.
131
     *
132
     * @param approved_contextlist $contextlist The approved contexts to export information for.
133
     */
134
    public static function export_user_data(approved_contextlist $contextlist) {
135
        global $DB;
136
 
137
        $userid = $contextlist->get_user()->id;
138
        $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) use ($userid) {
139
            if ($context->contextlevel == CONTEXT_USER) {
140
                if ($context->instanceid == $userid) {
141
                    $carry['has_mine'] = true;
142
                } else {
143
                    $carry['others'][] = $context->instanceid;
144
                }
145
            }
146
            return $carry;
147
        }, [
148
            'has_mine' => false,
149
            'others' => []
150
        ]);
151
 
152
        $path = [get_string('services', 'core_external')];
153
 
154
        // Exporting my stuff.
155
        if ($contexts['has_mine']) {
156
 
157
            $data = [];
158
 
159
            // Exporting my tokens.
160
            $sql = "
161
                SELECT t.*, s.name as externalservicename
162
                  FROM {external_tokens} t
163
                  JOIN {external_services} s
164
                    ON s.id = t.externalserviceid
165
                 WHERE t.userid = :userid
166
              ORDER BY t.id";
167
            $recordset = $DB->get_recordset_sql($sql, ['userid' => $userid]);
168
            foreach ($recordset as $record) {
169
                if (!isset($data['tokens'])) {
170
                    $data['tokens'] = [];
171
                }
172
                $data['tokens'][] = static::transform_token($record);
173
            }
174
            $recordset->close();
175
 
176
            // Exporting the services I have access to.
177
            $sql = "
178
                SELECT su.*, s.name as externalservicename
179
                  FROM {external_services_users} su
180
                  JOIN {external_services} s
181
                    ON s.id = su.externalserviceid
182
                 WHERE su.userid = :userid
183
              ORDER BY su.id";
184
            $recordset = $DB->get_recordset_sql($sql, ['userid' => $userid]);
185
            foreach ($recordset as $record) {
186
                if (!isset($data['services_user'])) {
187
                    $data['services_user'] = [];
188
                }
189
                $data['services_user'][] = [
190
                    'external_service' => $record->externalservicename,
191
                    'ip_restriction' => $record->iprestriction,
192
                    'valid_until' => $record->validuntil ? transform::datetime($record->validuntil) : null,
193
                    'created_on' => transform::datetime($record->timecreated),
194
                ];
195
            }
196
            $recordset->close();
197
 
198
            if (!empty($data)) {
199
                writer::with_context(context_user::instance($userid))->export_data($path, (object) $data);
200
            };
201
        }
202
 
203
        // Exporting the tokens I created.
204
        if (!empty($contexts['others'])) {
205
            list($insql, $inparams) = $DB->get_in_or_equal($contexts['others'], SQL_PARAMS_NAMED);
206
            $sql = "
207
                SELECT t.*, s.name as externalservicename
208
                  FROM {external_tokens} t
209
                  JOIN {external_services} s
210
                    ON s.id = t.externalserviceid
211
                 WHERE t.userid $insql
212
                   AND t.creatorid = :userid1
213
                   AND t.userid <> :userid2
214
              ORDER BY t.userid, t.id";
215
            $params = array_merge($inparams, ['userid1' => $userid, 'userid2' => $userid]);
216
            $recordset = $DB->get_recordset_sql($sql, $params);
217
            static::recordset_loop_and_export($recordset, 'userid', [], function($carry, $record) {
218
                $carry[] = static::transform_token($record);
219
                return $carry;
220
            }, function($userid, $data) use ($path) {
221
                writer::with_context(context_user::instance($userid))->export_related_data($path, 'created_by_you', (object) [
222
                    'tokens' => $data
223
                ]);
224
            });
225
        }
226
    }
227
 
228
    /**
229
     * Delete all data for all users in the specified context.
230
     *
231
     * @param context $context The specific context to delete data for.
232
     */
233
    public static function delete_data_for_all_users_in_context(context $context) {
234
        if ($context->contextlevel != CONTEXT_USER) {
235
            return;
236
        }
237
        static::delete_user_data($context->instanceid);
238
    }
239
 
240
    /**
241
     * Delete multiple users within a single context.
242
     *
243
     * @param approved_userlist $userlist The approved context and user information to delete information for.
244
     */
245
    public static function delete_data_for_users(approved_userlist $userlist) {
246
 
247
        $context = $userlist->get_context();
248
 
249
        if ($context instanceof \context_user) {
250
            static::delete_user_data($context->instanceid);
251
        }
252
    }
253
 
254
    /**
255
     * Delete all user data for the specified user, in the specified contexts.
256
     *
257
     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
258
     */
259
    public static function delete_data_for_user(approved_contextlist $contextlist) {
260
        $userid = $contextlist->get_user()->id;
261
        foreach ($contextlist as $context) {
262
            if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) {
263
                static::delete_user_data($context->instanceid);
264
                break;
265
            }
266
        }
267
    }
268
 
269
    /**
270
     * Delete user data.
271
     *
272
     * @param int $userid The user ID.
273
     * @return void
274
     */
275
    protected static function delete_user_data($userid) {
276
        global $DB;
277
        $DB->delete_records('external_tokens', ['userid' => $userid]);
278
        $DB->delete_records('external_services_users', ['userid' => $userid]);
279
    }
280
 
281
    /**
282
     * Transform a token entry.
283
     *
284
     * @param object $record The token record.
285
     * @return array
286
     */
287
    protected static function transform_token($record) {
288
        $notexportedstr = get_string('privacy:request:notexportedsecurity', 'core_external');
289
        return [
290
            'external_service' => $record->externalservicename,
291
            'token' => $notexportedstr,
292
            'private_token' => $record->privatetoken ? $notexportedstr : null,
293
            'ip_restriction' => $record->iprestriction,
294
            'valid_until' => $record->validuntil ? transform::datetime($record->validuntil) : null,
295
            'created_on' => transform::datetime($record->timecreated),
296
            'last_access' => $record->lastaccess ? transform::datetime($record->lastaccess) : null,
297
            'name' => $record->name,
298
        ];
299
    }
300
 
301
    /**
302
     * Loop and export from a recordset.
303
     *
304
     * @param \moodle_recordset $recordset The recordset.
305
     * @param string $splitkey The record key to determine when to export.
306
     * @param mixed $initial The initial data to reduce from.
307
     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
308
     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
309
     * @return void
310
     */
311
    protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
312
            callable $reducer, callable $export) {
313
 
314
        $data = $initial;
315
        $lastid = null;
316
 
317
        foreach ($recordset as $record) {
318
            if ($lastid && $record->{$splitkey} != $lastid) {
319
                $export($lastid, $data);
320
                $data = $initial;
321
            }
322
            $data = $reducer($data, $record);
323
            $lastid = $record->{$splitkey};
324
        }
325
        $recordset->close();
326
 
327
        if (!empty($lastid)) {
328
            $export($lastid, $data);
329
        }
330
    }
331
}