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) { |
|
|
|
|
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) { |
|
|
|
|
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)) { |
|
|
|
|
334
|
|
|
return false; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.