Completed
Pull Request — master (#57)
by Jesus
02:24
created

provider::_get_metadata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 30
rs 9.44
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
        $count = $contextlist->count();
158
        if (empty($count)) {
159
            return;
160
        }
161
        $userid = $contextlist->get_user()->id;
162
        foreach ($contextlist->get_contexts() as $context) {
163
            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...
164
                return;
165
            }
166
            $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
167
            $DB->delete_records('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => $instanceid, 'userid' => $userid]);
168
        }
169
    }
170
171
    /**
172
     * Export personal data for the given approved_contextlist related to bigbluebuttonbn logs.
173
     *
174
     * @param approved_contextlist $contextlist a list of contexts approved for export.
175
     */
176
    protected static function _export_user_data_bigbliebuttonbn_logs(approved_contextlist $contextlist) {
177
        global $DB;
178
179
        // Filter out any contexts that are not related to modules.
180
        $cmids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
181
            if ($context->contextlevel == CONTEXT_MODULE) {
182
                $carry[] = $context->instanceid;
183
            }
184
            return $carry;
185
        }, []);
186
187
        if (empty($cmids)) {
188
            return;
189
        }
190
191
        $user = $contextlist->get_user();
192
193
        // Get all the bigbluebuttonbn activities associated with the above course modules.
194
        $instanceidstocmids = self::get_instance_ids_to_cmids_from_cmids($cmids);
195
        $instanceids = array_keys($instanceidstocmids);
196
197
        list($insql, $inparams) = $DB->get_in_or_equal($instanceids, SQL_PARAMS_NAMED);
198
        $params = array_merge($inparams, ['userid' => $user->id]);
199
        $recordset = $DB->get_recordset_select(
200
            'bigbluebuttonbn_logs', "bigbluebuttonbnid $insql AND userid = :userid", $params, 'timecreated, id');
201
        self::recordset_loop_and_export($recordset, 'bigbluebuttonbnid', [],
202
            function($carry, $record) use ($user, $instanceidstocmids) {
203
                $carry[] = [
204
                    'timecreated' => transform::datetime($record->timecreated),
205
                    'meetingid' => $record->meetingid,
206
                    'log' => $record->log,
207
                    'meta' => $record->meta,
208
                  ];
209
                return $carry;
210
            },
211
            function($instanceid, $data) use ($user, $instanceidstocmids) {
212
                $context = \context_module::instance($instanceidstocmids[$instanceid]);
213
                $contextdata = helper::get_context_data($context, $user);
214
                $finaldata = (object) array_merge((array) $contextdata, ['logs' => $data]);
215
                helper::export_context_files($context, $user);
216
                writer::with_context($context)->export_data([], $finaldata);
217
            }
218
        );
219
    }
220
221
    /**
222
     * Return a dict of bigbluebuttonbn IDs mapped to their course module ID.
223
     *
224
     * @param array $cmids The course module IDs.
225
     * @return array In the form of [$bigbluebuttonbnid => $cmid].
226
     */
227
    protected static function get_instance_ids_to_cmids_from_cmids(array $cmids) {
228
        global $DB;
229
230
        list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED);
231
        $sql = "SELECT bigbluebuttonbn.id, cm.id AS cmid
232
                 FROM {bigbluebuttonbn} bigbluebuttonbn
233
                 JOIN {modules} m
234
                   ON m.name = :bigbluebuttonbn
235
                 JOIN {course_modules} cm
236
                   ON cm.instance = bigbluebuttonbn.id
237
                  AND cm.module = m.id
238
                WHERE cm.id $insql";
239
        $params = array_merge($inparams, ['bigbluebuttonbn' => 'bigbluebuttonbn']);
240
241
        return $DB->get_records_sql_menu($sql, $params);
242
    }
243
244
    /**
245
     * Loop and export from a recordset.
246
     *
247
     * @param \moodle_recordset $recordset The recordset.
248
     * @param string $splitkey The record key to determine when to export.
249
     * @param mixed $initial The initial data to reduce from.
250
     * @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
251
     * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
252
     * @return void
253
     */
254
    protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
255
                                                        callable $reducer, callable $export) {
256
        $data = $initial;
257
        $lastid = null;
258
259
        foreach ($recordset as $record) {
260
            if ($lastid && $record->{$splitkey} != $lastid) {
261
                $export($lastid, $data);
262
                $data = $initial;
263
            }
264
            $data = $reducer($data, $record);
265
            $lastid = $record->{$splitkey};
266
        }
267
        $recordset->close();
268
269
        if (!empty($lastid)) {
270
            $export($lastid, $data);
271
        }
272
    }
273
}
274