Completed
Pull Request — v2.2-stable (#51)
by Jesus
02:35
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_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
        // Fetch all bigbluebuttonbn logs.
99
        $sql = "SELECT c.id
100
                  FROM {context} c
101
            INNER JOIN {course_modules} cm
102
                    ON cm.id = c.instanceid
103
                   AND c.contextlevel = :contextlevel
104
            INNER JOIN {modules} m
105
                    ON m.id = cm.module
106
                   AND m.name = :modname
107
            INNER JOIN {bigbluebuttonbn} bigbluebuttonbn
108
                    ON bigbluebuttonbn.id = cm.instance
109
            INNER JOIN {bigbluebuttonbn_logs} bigbluebuttonbnlogs
110
                    ON bigbluebuttonbnlogs.bigbluebuttonbnid = bigbluebuttonbn.id
111
                 WHERE bigbluebuttonbnlogs.userid = :userid";
112
113
        $params = [
114
            'modname' => 'bigbluebuttonbn',
115
            'contextlevel' => CONTEXT_MODULE,
116
            'userid' => $userid,
117
        ];
118
        $contextlist = new contextlist();
119
        $contextlist->add_from_sql($sql, $params);
120
121
        return $contextlist;
122
    }
123
124
    /**
125
     * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
126
     *
127
     * @param approved_contextlist $contextlist a list of contexts approved for export.
128
     */
129
    public static function _export_user_data(approved_contextlist $contextlist) {
130
        self::_export_user_data_bigbliebuttonbn_logs($contextlist);
131
    }
132
133
134
    /**
135
     * Delete all data for all users in the specified context.
136
     *
137
     * @param \context $context the context to delete in.
138
     */
139
    public static function _delete_data_for_all_users_in_context(\context $context) {
140
        global $DB;
141
142
        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...
143
            return;
144
        }
145
146
        $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
147
        $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid]);
148
    }
149
150
    /**
151
     * Delete all user data for the specified user, in the specified contexts.
152
     *
153
     * @param approved_contextlist $contextlist a list of contexts approved for deletion.
154
     */
155
    public static function _delete_data_for_user(approved_contextlist $contextlist) {
156
        global $DB;
157
158
        if (empty($contextlist->count())) {
159
            return;
160
        }
161
162
        $userid = $contextlist->get_user()->id;
163
        foreach ($contextlist->get_contexts() as $context) {
164
            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...
165
                return;
166
            }
167
            $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
168
            $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid, 'userid' => $userid]);
169
        }
170
    }
171
172
    /**
173
     * Export personal data for the given approved_contextlist related to bigbluebuttonbn logs.
174
     *
175
     * @param approved_contextlist $contextlist a list of contexts approved for export.
176
     */
177
    protected static function _export_user_data_bigbliebuttonbn_logs(approved_contextlist $contextlist) {
178
        global $DB;
179
180
        // Filter out any contexts that are not related to modules.
181
        $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
182
            if ($context->contextlevel == CONTEXT_MODULE) {
183
                $carry[] = $context->instanceid;
184
            }
185
            return $carry;
186
        }, []);
187
188
        if (empty($cmids)) {
189
            return;
190
        }
191
192
        $user = $contextlist->get_user();
193
194
        // Get all the bigbluebuttonbn activities associated with the above course modules.
195
        $instanceidstocmids = self::get_instance_ids_to_cmids_from_cmids($cmids);
196
        $instanceids = array_keys($instanceidstocmids);
197
198
        list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED);
199
        $params = array_merge($inparams, ['userid' => $user->id]);
200
        $recordset = $DB->get_recordset_select(
201
            'bigbluebuttonbn_logs', "bigbluebuttonbnid $insql AND userid = :userid", $params, 'timecreated, id');
202
        self::recordset_loop_and_export($recordset, 'bigbluebuttonbnid', [],
203
            function($carry, $record) use ($user, $instanceidstocmids) {
204
                $carry[] = [
205
                    'timecreated' => transform::datetime($record->timecreated),
206
                    'meetingid' => $record->meetingid,
207
                    'log' => $record->log,
208
                    'meta' => $record->meta,
209
                  ];
210
                return $carry;
211
            },
212
            function($instanceid, $data) use ($user, $instanceidstocmids) {
213
                $context = \context_module::instance($instanceidstocmids[$instanceid]);
214
                $contextdata = helper::get_context_data($context, $user);
215
                $finaldata = (object) array_merge((array) $contextdata, ['logs' => $data]);
216
                helper::export_context_files($context, $user);
217
                writer::with_context($context)->export_data([], $finaldata);
218
            }
219
        );
220
    }
221
222
    /**
223
     * Return a dict of bigbluebuttonbn IDs mapped to their course module ID.
224
     *
225
     * @param array $cmids The course module IDs.
226
     * @return array In the form of [$bigbluebuttonbnid => $cmid].
227
     */
228
    protected static function get_instance_ids_to_cmids_from_cmids(array $cmids) {
229
        global $DB;
230
231
        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
232
        $sql = "SELECT bigbluebuttonbn.id, cm.id AS cmid
233
                 FROM {bigbluebuttonbn} bigbluebuttonbn
234
                 JOIN {modules} m
235
                   ON m.name = :bigbluebuttonbn
236
                 JOIN {course_modules} cm
237
                   ON cm.instance = bigbluebuttonbn.id
238
                  AND cm.module = m.id
239
                WHERE cm.id $insql";
240
        $params = array_merge($inparams, ['bigbluebuttonbn' => 'bigbluebuttonbn']);
241
242
        return $DB->get_records_sql_menu($sql, $params);
243
    }
244
245
    /**
246
     * Loop and export from a recordset.
247
     *
248
     * @param \moodle_recordset $recordset The recordset.
249
     * @param string $splitkey The record key to determine when to export.
250
     * @param mixed $initial The initial data to reduce from.
251
     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
252
     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
253
     * @return void
254
     */
255
    protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
256
                                                        callable $reducer, callable $export) {
257
        $data = $initial;
258
        $lastid = null;
259
260
        foreach ($recordset as $record) {
261
            if ($lastid && $record->{$splitkey} != $lastid) {
262
                $export($lastid, $data);
263
                $data = $initial;
264
            }
265
            $data = $reducer($data, $record);
266
            $lastid = $record->{$splitkey};
267
        }
268
        $recordset->close();
269
270
        if (!empty($lastid)) {
271
            $export($lastid, $data);
272
        }
273
    }
274
}
275