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

provider::_get_metadata()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 16
nc 1
nop 1
dl 0
loc 30
rs 8.8571
c 0
b 0
f 0
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_access\context;
29
use \core_access\context_module;
30
use \core_privacy\local\metadata\collection;
31
use \core_privacy\local\metadata\provider as metadataprovider;
32
use \core_privacy\local\request\approved_contextlist;
33
use \core_privacy\local\request\contextlist;
34
use \core_privacy\local\request\helper;
35
use \core_privacy\local\request\transform;
36
use \core_privacy\local\request\writer;
37
use \core_privacy\local\request\plugin\provider as pluginprovider;
38
39
defined('MOODLE_INTERNAL') || die();
40
41
require_once($CFG->dirroot . '/mod/bigbluebuttonbn/locallib.php');
42
43
/**
44
 * Privacy class for requesting user data.
45
 *
46
 * @package   mod_bigbluebuttonbn
47
 * @copyright 2018 - present, Blindside Networks Inc
48
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49
 * @author    Jesus Federico  (jesus [at] blindsidenetworks [dt] com)
50
 */
51
class provider implements metadataprovider, pluginprovider {
52
53
    // This trait must be included.
54
    use \core_privacy\local\legacy_polyfill;
55
56
    /**
57
     * Returns metadata.
58
     *
59
     * @param collection $collection The initialised collection to add items to.
60
     * @return collection A listing of user data stored through this system.
61
     */
62
    public static function _get_metadata(collection $collection) {
63
64
         // The table bigbluebuttonbn stores only the room properties.
65
         // However, there is a chance that some personal information is stored as metadata.
66
         // This would be done in the column 'participants' where rules can be set to define BBB roles.
67
         // It is fair to say that only the userid is stored, which is useless if user is removed.
68
         // But if this is a concern a refactoring on the way the rules are stored will be required.
69
        $collection->add_database_table('bigbluebuttonbn', [
70
            'participants' => 'privacy:metadata:bigbluebuttonbn:participants',
71
        ], 'privacy:metadata:bigbluebuttonbn');
72
73
        // The table bigbluebuttonbn_logs stores events triggered by users when using the plugin.
74
        // Some personal information along with the resource accessed is stored.
75
        $collection->add_database_table('bigbluebuttonbn_logs', [
76
            'userid' => 'privacy:metadata:bigbluebuttonbn_logs:userid',
77
            'timecreated' => 'privacy:metadata:bigbluebuttonbn_logs:timecreated',
78
            'meetingid' => 'privacy:metadata:bigbluebuttonbn_logs:meetingid',
79
            'log' => 'privacy:metadata:bigbluebuttonbn_logs:log',
80
            'meta' => 'privacy:metadata:bigbluebuttonbn_logs:meta',
81
        ], 'privacy:metadata:bigbluebuttonbn_logs');
82
83
        // Personal information has to be passed to BigBlueButton.
84
        // This includes the user ID and fullname.
85
        $collection->add_external_location_link('bigbluebutton', [
86
                'userid' => 'privacy:metadata:bigbluebutton:userid',
87
                'fullname' => 'privacy:metadata:bigbluebutton:fullname',
88
            ], 'privacy:metadata:bigbluebutton');
89
90
        return $collection;
91
    }
92
93
    /**
94
     * Get the list of contexts that contain user information for the specified user.
95
     *
96
     * @param   int           $userid       The user to search.
97
     * @return  contextlist   $contextlist  The list of contexts used in this plugin.
98
     */
99
    public static function _get_contexts_for_userid(int $userid) {
100
        // Fetch all bigbluebuttonbn logs.
101
        $sql = "SELECT c.id
102
                  FROM {context} c
103
            INNER JOIN {course_modules} cm
104
                    ON cm.id = c.instanceid
105
                   AND c.contextlevel = :contextlevel
106
            INNER JOIN {modules} m
107
                    ON m.id = cm.module
108
                   AND m.name = :modname
109
            INNER JOIN {bigbluebuttonbn} bigbluebuttonbn
110
                    ON bigbluebuttonbn.id = cm.instance
111
            INNER JOIN {bigbluebuttonbn_logs} bigbluebuttonbnlogs
112
                    ON bigbluebuttonbnlogs.bigbluebuttonbnid = bigbluebuttonbn.id
113
                 WHERE bigbluebuttonbnlogs.userid = :userid";
114
115
        $params = [
116
            'modname' => 'bigbluebuttonbn',
117
            'contextlevel' => CONTEXT_MODULE,
118
            'userid' => $userid,
119
        ];
120
        $contextlist = new contextlist();
121
        $contextlist->add_from_sql($sql, $params);
122
123
        return $contextlist;
124
    }
125
126
    /**
127
     * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
128
     *
129
     * @param approved_contextlist $contextlist a list of contexts approved for export.
130
     */
131
    public static function _export_user_data(approved_contextlist $contextlist) {
132
        self::_export_user_data_bigbliebuttonbn_logs($contextlist);
133
    }
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. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
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
160
        if (empty($contextlist->count())) {
161
            return;
162
        }
163
164
        $userid = $contextlist->get_user()->id;
165
        foreach ($contextlist->get_contexts() as $context) {
166
            if (!$context instanceof \context_module) {
0 ignored issues
show
Bug introduced by
The class context_module does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
167
                return;
168
            }
169
            $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
170
            $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid, 'userid' => $userid]);
171
        }
172
    }
173
174
    /**
175
     * Export personal data for the given approved_contextlist related to bigbluebuttonbn logs.
176
     *
177
     * @param approved_contextlist $contextlist a list of contexts approved for export.
178
     */
179
    protected static function _export_user_data_bigbliebuttonbn_logs(approved_contextlist $contextlist) {
180
        global $DB;
181
182
        // Filter out any contexts that are not related to modules.
183
        $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
184
            if ($context->contextlevel == CONTEXT_MODULE) {
185
                $carry[] = $context->instanceid;
186
            }
187
            return $carry;
188
        }, []);
189
190
        if (empty($cmids)) {
191
            return;
192
        }
193
194
        $user = $contextlist->get_user();
195
196
        // Get all the bigbluebuttonbn activities associated with the above course modules.
197
        $instanceidstocmids = self::get_instance_ids_to_cmids_from_cmids($cmids);
198
        $instanceids = array_keys($instanceidstocmids);
199
200
        list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED);
201
        $params = array_merge($inparams, ['userid' => $user->id]);
202
        $recordset = $DB->get_recordset_select(
203
            'bigbluebuttonbn_logs', "bigbluebuttonbnid $insql AND userid = :userid", $params, 'timecreated, id');
204
        self::recordset_loop_and_export($recordset, 'bigbluebuttonbnid', [],
205
            function($carry, $record) use ($user, $instanceidstocmids) {
206
                $carry[] = [
207
                    'timecreated' => transform::datetime($record->timecreated),
208
                    'meetingid' => $record->meetingid,
209
                    'log' => $record->log,
210
                    'meta' => $record->meta,
211
                  ];
212
                return $carry;
213
            },
214
            function($instanceid, $data) use ($user, $instanceidstocmids) {
215
                $context = \context_module::instance($instanceidstocmids[$instanceid]);
216
                $contextdata = helper::get_context_data($context, $user);
217
                $finaldata = (object) array_merge((array) $contextdata, ['logs' => $data]);
218
                helper::export_context_files($context, $user);
219
                writer::with_context($context)->export_data([], $finaldata);
220
            }
221
        );
222
    }
223
224
    /**
225
     * Return a dict of bigbluebuttonbn IDs mapped to their course module ID.
226
     *
227
     * @param array $cmids The course module IDs.
228
     * @return array In the form of [$bigbluebuttonbnid => $cmid].
229
     */
230
    protected static function get_instance_ids_to_cmids_from_cmids(array $cmids) {
231
        global $DB;
232
233
        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
234
        $sql = "SELECT bigbluebuttonbn.id, cm.id AS cmid
235
                 FROM {bigbluebuttonbn} bigbluebuttonbn
236
                 JOIN {modules} m
237
                   ON m.name = :bigbluebuttonbn
238
                 JOIN {course_modules} cm
239
                   ON cm.instance = bigbluebuttonbn.id
240
                  AND cm.module = m.id
241
                WHERE cm.id $insql";
242
        $params = array_merge($inparams, ['bigbluebuttonbn' => 'bigbluebuttonbn']);
243
244
        return $DB->get_records_sql_menu($sql, $params);
245
    }
246
247
    /**
248
     * Loop and export from a recordset.
249
     *
250
     * @param \moodle_recordset $recordset The recordset.
251
     * @param string $splitkey The record key to determine when to export.
252
     * @param mixed $initial The initial data to reduce from.
253
     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
254
     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
255
     * @return void
256
     */
257
    protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
258
                                                        callable $reducer, callable $export) {
259
        $data = $initial;
260
        $lastid = null;
261
262
        foreach ($recordset as $record) {
263
            if ($lastid && $record->{$splitkey} != $lastid) {
264
                $export($lastid, $data);
265
                $data = $initial;
266
            }
267
            $data = $reducer($data, $record);
268
            $lastid = $record->{$splitkey};
269
        }
270
        $recordset->close();
271
272
        if (!empty($lastid)) {
273
            $export($lastid, $data);
274
        }
275
    }
276
}
277