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 provider implementation for core_contentbank.
19 *
20 * @package    core_contentbank
21 * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace core_contentbank\privacy;
26
27use core_privacy\local\metadata\collection;
28use core_privacy\local\request\approved_contextlist;
29use core_privacy\local\request\contextlist;
30use core_privacy\local\request\transform;
31use core_privacy\local\request\writer;
32use core_privacy\local\request\userlist;
33use core_privacy\local\request\approved_userlist;
34use context_system;
35use context_coursecat;
36use context_course;
37
38/**
39 * Privacy provider implementation for core_contentbank.
40 *
41 * @copyright  2020 Amaia Anabitarte <amaia@moodle.com>
42 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 */
44class provider implements
45    \core_privacy\local\metadata\provider,
46    \core_privacy\local\request\core_userlist_provider,
47    \core_privacy\local\request\plugin\provider,
48    \core_privacy\local\request\user_preference_provider {
49
50    /**
51     * Returns meta data about this system.
52     *
53     * @param collection $collection The initialised collection to add items to.
54     * @return collection A listing of user data stored through this system.
55     */
56    public static function get_metadata(collection $collection): collection {
57        $collection->add_database_table('contentbank_content', [
58            'name' => 'privacy:metadata:content:name',
59            'contenttype' => 'privacy:metadata:content:contenttype',
60            'usercreated' => 'privacy:metadata:content:usercreated',
61            'usermodified' => 'privacy:metadata:content:usermodified',
62            'timecreated' => 'privacy:metadata:content:timecreated',
63            'timemodified' => 'privacy:metadata:content:timemodified',
64        ], 'privacy:metadata:contentbankcontent');
65
66        return $collection;
67    }
68
69    /**
70     * Export all user preferences for the contentbank
71     *
72     * @param int $userid The userid of the user whose data is to be exported.
73     */
74    public static function export_user_preferences(int $userid) {
75        $preference = get_user_preferences('core_contentbank_view_list', null, $userid);
76        if (isset($preference)) {
77            writer::export_user_preference(
78                    'core_contentbank',
79                    'core_contentbank_view_list',
80                    $preference,
81                    get_string('privacy:request:preference:set', 'core_contentbank', (object) [
82                            'name' => 'core_contentbank_view_list',
83                            'value' => $preference,
84                    ])
85            );
86        }
87    }
88
89    /**
90     * Get the list of contexts that contain user information for the specified user.
91     *
92     * @param   int $userid The user to search.
93     * @return  contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
94     */
95    public static function get_contexts_for_userid(int $userid): contextlist {
96        $sql = "SELECT DISTINCT ctx.id
97                  FROM {context} ctx
98                  JOIN {contentbank_content} cb
99                       ON cb.contextid = ctx.id
100                 WHERE cb.usercreated = :userid
101                       AND (ctx.contextlevel = :contextlevel1
102                           OR ctx.contextlevel = :contextlevel2
103                           OR ctx.contextlevel = :contextlevel3)";
104
105        $params = [
106            'userid'        => $userid,
107            'contextlevel1' => CONTEXT_SYSTEM,
108            'contextlevel2' => CONTEXT_COURSECAT,
109            'contextlevel3' => CONTEXT_COURSE,
110        ];
111
112        $contextlist = new contextlist();
113        $contextlist->add_from_sql($sql, $params);
114
115        return $contextlist;
116    }
117
118    /**
119     * Get the list of users within a specific context.
120     *
121     * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
122     */
123    public static function get_users_in_context(userlist $userlist) {
124        $context = $userlist->get_context();
125
126        $allowedcontextlevels = [
127            CONTEXT_SYSTEM,
128            CONTEXT_COURSECAT,
129            CONTEXT_COURSE,
130        ];
131
132        if (!in_array($context->contextlevel, $allowedcontextlevels)) {
133            return;
134        }
135
136        $sql = "SELECT cb.usercreated as userid
137                  FROM {contentbank_content} cb
138                 WHERE cb.contextid = :contextid";
139
140        $params = [
141            'contextid' => $context->id
142        ];
143
144        $userlist->add_from_sql('userid', $sql, $params);
145    }
146
147    /**
148     * Export all user data for the specified user, in the specified contexts.
149     *
150     * @param approved_contextlist $contextlist The approved contexts to export information for.
151     */
152    public static function export_user_data(approved_contextlist $contextlist) {
153        global $DB;
154
155        // Remove contexts different from SYSTEM, COURSECAT or COURSE.
156        $contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
157            if ($context->contextlevel == CONTEXT_SYSTEM || $context->contextlevel == CONTEXT_COURSECAT
158                || $context->contextlevel == CONTEXT_COURSE) {
159                $carry[] = $context->id;
160            }
161            return $carry;
162        }, []);
163
164        if (empty($contextids)) {
165            return;
166        }
167
168        $userid = $contextlist->get_user()->id;
169
170        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
171        // Retrieve the contentbank_content records created for the user.
172        $sql = "SELECT cb.id,
173                       cb.name,
174                       cb.contenttype,
175                       cb.usercreated,
176                       cb.usermodified,
177                       cb.timecreated,
178                       cb.timemodified,
179                       cb.contextid
180                  FROM {contentbank_content} cb
181                 WHERE cb.usercreated = :userid
182                       AND cb.contextid {$contextsql}
183                 ORDER BY cb.contextid";
184
185        $params = ['userid' => $userid] + $contextparams;
186
187        $contents = $DB->get_recordset_sql($sql, $params);
188        $data = [];
189        $lastcontextid = null;
190        $subcontext = [
191            get_string('name', 'core_contentbank'),
192        ];
193        foreach ($contents as $content) {
194            // The core_contentbank data export is organised in:
195            // {Sytem|Course Category|Course Context Level}/Content/data.json.
196            if ($lastcontextid && $lastcontextid != $content->contextid) {
197                $context = \context::instance_by_id($lastcontextid);
198                writer::with_context($context)->export_data($subcontext, (object)$data);
199                $data = [];
200            }
201            $data[] = (object) [
202                'name' => $content->name,
203                'contenttype' => $content->contenttype,
204                'usercreated' => transform::user($content->usercreated),
205                'usermodified' => transform::user($content->usermodified),
206                'timecreated' => transform::datetime($content->timecreated),
207                'timemodified' => transform::datetime($content->timemodified)
208            ];
209            $lastcontextid = $content->contextid;
210
211            // The core_contentbank files export is organised in:
212            // {Sytem|Course Category|Course Context Level}/Content/_files/public/_itemid/filename.
213            $context = \context::instance_by_id($lastcontextid);
214            writer::with_context($context)->export_area_files($subcontext, 'contentbank', 'public', $content->id);
215        }
216        if (!empty($data)) {
217            $context = \context::instance_by_id($lastcontextid);
218            writer::with_context($context)->export_data($subcontext, (object)$data);
219        }
220        $contents->close();
221    }
222
223    /**
224     * Delete all data for all users in the specified context.
225     *
226     * @param   context $context The specific context to delete data for.
227     */
228    public static function delete_data_for_all_users_in_context(\context $context) {
229        global $DB;
230
231        if (!$context instanceof context_system && !$context instanceof context_coursecat
232                && !$context instanceof context_course) {
233            return;
234        }
235
236        static::delete_data($context, []);
237    }
238
239    /**
240     * Delete multiple users within a single context.
241     *
242     * @param approved_userlist $userlist The approved context and user information to delete information for.
243     */
244    public static function delete_data_for_users(approved_userlist $userlist) {
245        $context = $userlist->get_context();
246
247        if (!$context instanceof context_system && !$context instanceof context_coursecat
248                && !$context instanceof context_course) {
249            return;
250        }
251
252        static::delete_data($context, $userlist->get_userids());
253    }
254
255    /**
256     * Delete all user data for the specified user, in the specified contexts.
257     *
258     * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
259     */
260    public static function delete_data_for_user(approved_contextlist $contextlist) {
261        if (empty($contextlist->count())) {
262            return;
263        }
264
265        $userid = $contextlist->get_user()->id;
266        foreach ($contextlist->get_contexts() as $context) {
267            if (!$context instanceof context_system && !$context instanceof context_coursecat
268            && !$context instanceof context_course) {
269                continue;
270            }
271            static::delete_data($context, [$userid]);
272        }
273    }
274
275    /**
276     * Delete data related to a context and users (if defined).
277     *
278     * @param context $context A context.
279     * @param array $userids The user IDs.
280     */
281    protected static function delete_data(\context $context, array $userids) {
282        global $DB;
283
284        $params = ['contextid' => $context->id];
285        $select = 'contextid = :contextid';
286
287        // Delete the Content Bank files.
288        if (!empty($userids)) {
289            list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
290            $params += $inparams;
291            $select .= ' AND usercreated '.$insql;
292        }
293        $fs = get_file_storage();
294        $contents = $DB->get_records_select('contentbank_content',
295            $select, $params);
296        foreach ($contents as $content) {
297            $fs->delete_area_files($content->contextid, 'contentbank', 'public', $content->id);
298        }
299
300        // Delete all the contents.
301        $DB->delete_records_select('contentbank_content', $select, $params);
302    }
303}
304