Completed
Pull Request — v2.2-stable (#83)
by Jesus
02:03
created

provider   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 289
rs 10
c 0
b 0
f 0
wmc 25
lcom 1
cbo 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A _get_metadata() 0 30 1
A _get_contexts_for_userid() 0 29 2
A _export_user_data() 0 3 1
A _delete_data_for_all_users_in_context() 0 10 2
A _delete_data_for_user() 0 15 4
A _export_user_data_bigbliebuttonbn_logs() 0 44 3
A get_instance_ids_to_cmids_from_cmids() 0 16 1
A recordset_loop_and_export() 0 19 5
A get_users_in_context() 0 22 2
A delete_data_for_users() 0 12 1
A is_context_module() 0 10 3
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
 * Privacy class for requesting user data.
19
 *
20
 * @package   mod_bigbluebuttonbn
21
 * @copyright 2018 - present, Blindside Networks Inc
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
24
 */
25
26
namespace mod_bigbluebuttonbn\privacy;
27
28
use \core_privacy\local\metadata\collection;
29
use \core_privacy\local\metadata\provider as metadataprovider;
30
use \core_privacy\local\request\approved_contextlist;
31
use \core_privacy\local\request\contextlist;
32
use \core_privacy\local\request\helper;
33
use \core_privacy\local\request\transform;
34
use \core_privacy\local\request\writer;
35
use \core_privacy\local\request\plugin\provider as pluginprovider;
36
37
defined('MOODLE_INTERNAL') || die();
38
39
require_once($CFG->dirroot . '/mod/bigbluebuttonbn/locallib.php');
40
41
/**
42
 * Privacy class for requesting user data.
43
 *
44
 * @package   mod_bigbluebuttonbn
45
 * @copyright 2018 - present, Blindside Networks Inc
46
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47
 * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
48
 */
49
class provider implements metadataprovider, pluginprovider {
50
51
    // This trait must be included.
52
    use \core_privacy\local\legacy_polyfill;
53
54
    /**
55
     * Returns metadata.
56
     *
57
     * @param collection $collection The initialised collection to add items to.
58
     * @return collection A listing of user data stored through this system.
59
     */
60
    public static function _get_metadata(collection $collection) {
61
62
         // The table bigbluebuttonbn stores only the room properties.
63
         // However, there is a chance that some personal information is stored as metadata.
64
         // This would be done in the column 'participants' where rules can be set to define BBB roles.
65
         // It is fair to say that only the userid is stored, which is useless if user is removed.
66
         // But if this is a concern a refactoring on the way the rules are stored will be required.
67
        $collection->add_database_table('bigbluebuttonbn', [
68
            'participants' => 'privacy:metadata:bigbluebuttonbn:participants',
69
        ], 'privacy:metadata:bigbluebuttonbn');
70
71
        // The table bigbluebuttonbn_logs stores events triggered by users when using the plugin.
72
        // Some personal information along with the resource accessed is stored.
73
        $collection->add_database_table('bigbluebuttonbn_logs', [
74
            'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid',
75
            'timecreated' => 'privacy:metadata:bigbluebuttonbn_logs:timecreated',
76
            'meetingid' => 'privacy:metadata:bigbluebuttonbn_logs:meetingid',
77
            'log' => 'privacy:metadata:bigbluebuttonbn_logs:log',
78
            'meta' => 'privacy:metadata:bigbluebuttonbn_logs:meta',
79
        ], 'privacy:metadata:bigbluebuttonbn_logs');
80
81
        // Personal information has to be passed to BigBlueButton.
82
        // This includes the user ID and fullname.
83
        $collection->add_external_location_link('bigbluebutton', [
84
                'userid' => 'privacy:metadata:bigbluebutton:userid',
85
                'fullname' => 'privacy:metadata:bigbluebutton:fullname',
86
            ], 'privacy:metadata:bigbluebutton');
87
88
        return $collection;
89
    }
90
91
    /**
92
     * Get the list of contexts that contain user information for the specified user.
93
     *
94
     * @param   int           $userid       The user to search.
95
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.
96
     */
97
    public static function _get_contexts_for_userid(int $userid) {
98
        // If user was already deleted, do nothing.
99
        if (!\core_user::get_user($userid)) {
100
            return;
101
        }
102
        // Fetch all bigbluebuttonbn logs.
103
        $sql = "SELECT c.id
104
                  FROM {context} c
105
            INNER JOIN {course_modules} cm
106
                    ON cm.id = c.instanceid
107
                   AND c.contextlevel = :contextlevel
108
            INNER JOIN {modules} m
109
                    ON m.id = cm.module
110
                   AND m.name = :modname
111
            INNER JOIN {bigbluebuttonbn} bigbluebuttonbn
112
                    ON bigbluebuttonbn.id = cm.instance
113
            INNER JOIN {bigbluebuttonbn_logs} bigbluebuttonbnlogs
114
                    ON bigbluebuttonbnlogs.bigbluebuttonbnid = bigbluebuttonbn.id
115
                 WHERE bigbluebuttonbnlogs.userid = :userid";
116
117
        $params = [
118
            'modname' => 'bigbluebuttonbn',
119
            'contextlevel' => CONTEXT_MODULE,
120
            'userid' => $userid,
121
        ];
122
        $contextlist = new contextlist();
123
        $contextlist->add_from_sql($sql, $params);
124
        return $contextlist;
125
    }
126
127
    /**
128
     * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
129
     *
130
     * @param approved_contextlist $contextlist a list of contexts approved for export.
131
     */
132
    public static function _export_user_data(approved_contextlist $contextlist) {
133
        self::_export_user_data_bigbliebuttonbn_logs($contextlist);
134
    }
135
136
    /**
137
     * Delete all data for all users in the specified context.
138
     *
139
     * @param \context $context the context to delete in.
140
     */
141
    public static function _delete_data_for_all_users_in_context(\context $context) {
142
        global $DB;
143
144
        if (!$context instanceof \context_module) {
0 ignored issues
show
Bug introduced by
The class context_module does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
145
            return;
146
        }
147
148
        $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
149
        $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid]);
150
    }
151
152
    /**
153
     * Delete all user data for the specified user, in the specified contexts.
154
     *
155
     * @param approved_contextlist $contextlist a list of contexts approved for deletion.
156
     */
157
    public static function _delete_data_for_user(approved_contextlist $contextlist) {
158
        global $DB;
159
        $count = $contextlist->count();
160
        if (empty($count)) {
161
            return;
162
        }
163
        $userid = $contextlist->get_user()->id;
164
        foreach ($contextlist->get_contexts() as $context) {
165
            if (!$context instanceof \context_module) {
0 ignored issues
show
Bug introduced by
The class context_module does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
166
                return;
167
            }
168
            $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
169
            $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid, 'userid' => $userid]);
170
        }
171
    }
172
173
    /**
174
     * Export personal data for the given approved_contextlist related to bigbluebuttonbn logs.
175
     *
176
     * @param approved_contextlist $contextlist a list of contexts approved for export.
177
     */
178
    protected static function _export_user_data_bigbliebuttonbn_logs(approved_contextlist $contextlist) {
179
        global $DB;
180
181
        // Filter out any contexts that are not related to modules.
182
        $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
183
            if ($context->contextlevel == CONTEXT_MODULE) {
184
                $carry[] = $context->instanceid;
185
            }
186
            return $carry;
187
        }, []);
188
189
        if (empty($cmids)) {
190
            return;
191
        }
192
193
        $user = $contextlist->get_user();
194
195
        // Get all the bigbluebuttonbn activities associated with the above course modules.
196
        $instanceidstocmids = self::get_instance_ids_to_cmids_from_cmids($cmids);
197
        $instanceids = array_keys($instanceidstocmids);
198
199
        list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED);
200
        $params = array_merge($inparams, ['userid' => $user->id]);
201
        $recordset = $DB->get_recordset_select(
202
            'bigbluebuttonbn_logs', "bigbluebuttonbnid $insql AND userid = :userid", $params, 'timecreated, id');
203
        self::recordset_loop_and_export($recordset, 'bigbluebuttonbnid', [],
204
            function($carry, $record) use ($user, $instanceidstocmids) {
205
                $carry[] = [
206
                    'timecreated' => transform::datetime($record->timecreated),
207
                    'meetingid' => $record->meetingid,
208
                    'log' => $record->log,
209
                    'meta' => $record->meta,
210
                  ];
211
                return $carry;
212
            },
213
            function($instanceid, $data) use ($user, $instanceidstocmids) {
214
                $context = \context_module::instance($instanceidstocmids[$instanceid]);
215
                $contextdata = helper::get_context_data($context, $user);
216
                $finaldata = (object) array_merge((array) $contextdata, ['logs' => $data]);
217
                helper::export_context_files($context, $user);
218
                writer::with_context($context)->export_data([], $finaldata);
219
            }
220
        );
221
    }
222
223
    /**
224
     * Return a dict of bigbluebuttonbn IDs mapped to their course module ID.
225
     *
226
     * @param array $cmids The course module IDs.
227
     * @return array In the form of [$bigbluebuttonbnid => $cmid].
228
     */
229
    protected static function get_instance_ids_to_cmids_from_cmids(array $cmids) {
230
        global $DB;
231
232
        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
233
        $sql = "SELECT bigbluebuttonbn.id, cm.id AS cmid
234
                 FROM {bigbluebuttonbn} bigbluebuttonbn
235
                 JOIN {modules} m
236
                   ON m.name = :bigbluebuttonbn
237
                 JOIN {course_modules} cm
238
                   ON cm.instance = bigbluebuttonbn.id
239
                  AND cm.module = m.id
240
                WHERE cm.id $insql";
241
        $params = array_merge($inparams, ['bigbluebuttonbn' => 'bigbluebuttonbn']);
242
243
        return $DB->get_records_sql_menu($sql, $params);
244
    }
245
246
    /**
247
     * Loop and export from a recordset.
248
     *
249
     * @param \moodle_recordset $recordset The recordset.
250
     * @param string $splitkey The record key to determine when to export.
251
     * @param mixed $initial The initial data to reduce from.
252
     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
253
     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
254
     * @return void
255
     */
256
    protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
257
                                                        callable $reducer, callable $export) {
258
        $data = $initial;
259
        $lastid = null;
260
261
        foreach ($recordset as $record) {
262
            if ($lastid && $record->{$splitkey} != $lastid) {
263
                $export($lastid, $data);
264
                $data = $initial;
265
            }
266
            $data = $reducer($data, $record);
267
            $lastid = $record->{$splitkey};
268
        }
269
        $recordset->close();
270
271
        if (!empty($lastid)) {
272
            $export($lastid, $data);
273
        }
274
    }
275
276
    /**
277
     * Get the list of users who have data within a context.
278
     *
279
     * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
280
     */
281
    public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) {
282
283
        $context = $userlist->get_context();
284
285
        if (!self::is_context_module()) {
286
            return;
287
        }
288
289
        $params = [
290
            'instanceid'    => $context->instanceid,
291
            'modulename'    => 'bigbluebuttonbn',
292
        ];
293
294
        $sql = "SELECT bnl.userid
295
                  FROM {course_modules} cm
296
                  JOIN {modules} m ON m.id = cm.module AND m.name = :modulename
297
                  JOIN {bigbluebuttonbn} bn ON bn.id = cm.instance
298
                  JOIN {bigbluebuttonbn_logs} bnl ON bnl.bigbluebuttonbnid = bn.id
299
                 WHERE cm.id = :instanceid";
300
301
        $userlist->add_from_sql('userid', $sql, $params);
302
    }
303
304
    /**
305
     * Delete multiple users within a single context.
306
     *
307
     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
308
     */
309
    public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) {
310
        global $DB;
311
312
        $context = $userlist->get_context();
313
        $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]);
314
315
        list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
316
        $params = array_merge(['bigbluebuttonbnid' => $cm->instance], $userinparams);
317
        $sql = "bigbluebuttonbnid = :bigbluebuttonbnid AND userid {$userinsql}";
318
319
        $DB->delete_records_select('bigbluebuttonbn_logs', $sql, $params);
320
    }
321
322
    /**
323
     * Helper for working around PHP version compatibility.
324
     *
325
     * @return void
326
     */
327
    protected static function is_context_module() {
328
        $base = "context_module";
329
        if (version_compare(phpversion(), "5.5.0") >= 0) {
330
            // PHP version 5.5.0 or later.
331
            $base = \context_module::class;
332
        }
333
        if (!is_a($context, $base)) {
0 ignored issues
show
Bug introduced by
The variable $context does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
334
          return false;
335
        }
336
    }
337
}
338