MDL-62425 core_privacy: Add core to the list of components
[moodle.git] / privacy / classes / manager.php
CommitLineData
0f59848d
JD
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 * This file contains the core_privacy\manager class.
19 *
20 * @package core_privacy
21 * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace core_privacy;
25use core_privacy\local\metadata\collection;
26use core_privacy\local\request\contextlist_collection;
0f59848d
JD
27
28defined('MOODLE_INTERNAL') || die();
29
30/**
31 * The core_privacy\manager class, providing a facade to describe, export and delete personal data across Moodle and its components.
32 *
33 * This class is responsible for communicating with and collating privacy data from all relevant components, where relevance is
34 * determined through implementations of specific marker interfaces. These marker interfaces describe the responsibilities (in terms
35 * of personal data storage) as well as the relationship between the component and the core_privacy subsystem.
36 *
37 * The interface hierarchy is as follows:
38 * ├── local\metadata\null_provider
39 * ├── local\metadata\provider
40 * ├── local\request\data_provider
41 * └── local\request\core_data_provider
42 * └── local\request\core_user_data_provider
43 * └── local\request\plugin\provider
44 * └── local\request\subsystem\provider
45 * └── local\request\user_preference_provider
46 * └── local\request\shared_data_provider
47 * └── local\request\plugin\subsystem_provider
48 * └── local\request\plugin\subplugin_provider
49 * └── local\request\subsystem\plugin_provider
50 *
51 * Describing personal data:
52 * -------------------------
53 * All components must state whether they store personal data (and DESCRIBE it) by implementing one of the metadata providers:
54 * - local\metadata\null_provider (indicating they don't store personal data)
55 * - local\metadata\provider (indicating they do store personal data, and describing it)
56 *
57 * The manager requests metadata for all Moodle components implementing the local\metadata\provider interface.
58 *
59 * Export and deletion of personal data:
60 * -------------------------------------
61 * Those components storing personal data need to provide EXPORT and DELETION of this data by implementing a request provider.
62 * Which provider implementation depends on the nature of the component; whether it's a sub-component and which components it
63 * stores data for.
64 *
65 * Export and deletion for sub-components (or any component storing data on behalf of another component) is managed by the parent
66 * component. If a component contains sub-components, it must ask those sub-components to provide the relevant data. Only certain
67 * 'core provider' components are called directly from the manager and these must provide the personal data stored by both
68 * themselves, and by all sub-components. Because of this hierarchical structure, the core_privacy\manager needs to know which
69 * components are to be called directly by core: these are called core data providers. The providers implemented by sub-components
70 * are called shared data providers.
71 *
72 * The following are interfaces are not implemented directly, but are marker interfaces uses to classify components by nature:
73 * - local\request\data_provider:
74 * Not implemented directly. Used to classify components storing personal data of some kind. Includes both components storing
75 * personal data for themselves and on behalf of other components.
76 * Include: local\request\core_data_provider and local\request\shared_data_provider.
77 * - local\request\core_data_provider:
78 * Not implemented directly. Used to classify components storing personal data for themselves and which are to be called by the
79 * core_privacy subsystem directly.
80 * Includes: local\request\core_user_data_provider and local\request\user_preference_provider.
81 * - local\request\core_user_data_provider:
82 * Not implemented directly. Used to classify components storing personal data for themselves, which are either a plugin or
83 * subsystem and which are to be called by the core_privacy subsystem directly.
84 * Includes: local\request\plugin\provider and local\request\subsystem\provider.
85 * - local\request\shared_data_provider:
86 * Not implemented directly. Used to classify components storing personal data on behalf of other components and which are
87 * called by the owning component directly.
88 * Includes: local\request\plugin\subsystem_provider, local\request\plugin\subplugin_provider and local\request\subsystem\plugin_provider
89 *
90 * The manager only requests the export or deletion of personal data for components implementing the local\request\core_data_provider
91 * interface or one of its descendants; local\request\plugin\provider, local\request\subsystem\provider or local\request\user_preference_provider.
92 * Implementing one of these signals to the core_privacy subsystem that the component must be queried directly from the manager.
93 *
94 * Any component using another component to store personal data on its behalf, is responsible for making the relevant call to
95 * that component's relevant shared_data_provider class.
96 *
97 * For example:
98 * The manager calls a core_data_provider component (e.g. mod_assign) which, in turn, calls relevant subplugins or subsystems
99 * (which assign uses to store personal data) to get that data. All data for assign and its sub-components is aggregated by assign
100 * and returned to the core_privacy subsystem.
101 *
102 * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
103 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
104 */
105class manager {
106 /**
107 * Checks whether the given component is compliant with the core_privacy API.
108 * To be considered compliant, a component must declare whether (and where) it stores personal data.
109 *
110 * Components which do store personal data must:
111 * - Have implemented the core_privacy\local\metadata\provider interface (to describe the data it stores) and;
112 * - Have implemented the core_privacy\local\request\data_provider interface (to facilitate export of personal data)
113 * - Have implemented the core_privacy\local\request\deleter interface
114 *
115 * Components which do not store personal data must:
116 * - Have implemented the core_privacy\local\metadata\null_provider interface to signal that they don't store personal data.
117 *
118 * @param string $component frankenstyle component name, e.g. 'mod_assign'
119 * @return bool true if the component is compliant, false otherwise.
120 */
121 public function component_is_compliant(string $component) : bool {
122 // Components which don't store user data need only implement the null_provider.
123 if ($this->component_implements($component, \core_privacy\local\metadata\null_provider::class)) {
124 return true;
125 }
126 // Components which store user data must implement the local\metadata\provider and the local\request\data_provider.
127 if ($this->component_implements($component, \core_privacy\local\metadata\provider::class) &&
128 $this->component_implements($component, \core_privacy\local\request\data_provider::class)) {
129 return true;
130 }
131 return false;
132 }
133
0f6fb936
AG
134 /**
135 * Retrieve the reason for implementing the null provider interface.
136 *
137 * @param string $component Frankenstyle component name.
138 * @return string The key to retrieve the language string for the null provider reason.
139 */
140 public function get_null_provider_reason(string $component) : string {
141 if ($this->component_implements($component, \core_privacy\local\metadata\null_provider::class)) {
142 return $this->get_provider_classname($component)::get_reason();
143 } else {
144 throw new \coding_exception('Call to undefined method', 'Please only call this method on a null provider.');
145 }
146 }
147
0f59848d
JD
148 /**
149 * Get the privacy metadata for all components.
150 *
151 * @return collection[] The array of collection objects, indexed by frankenstyle component name.
152 */
153 public function get_metadata_for_components() : array {
154 // Get the metadata, and put into an assoc array indexed by component name.
155 $metadata = [];
156 foreach ($this->get_component_list() as $component) {
157 if ($this->component_implements($component, \core_privacy\local\metadata\provider::class)) {
158 $metadata[$component] = $this->get_provider_classname($component)::get_metadata(new collection($component));
159 }
160 }
161 return $metadata;
162 }
163
164 /**
165 * Gets a collection of resultset objects for all components.
166 *
167 * @param int $userid the id of the user we're fetching contexts for.
168 * @return contextlist_collection the collection of contextlist items for the respective components.
169 */
170 public function get_contexts_for_userid(int $userid) : contextlist_collection {
171 $clcollection = new contextlist_collection($userid);
172 foreach ($this->get_component_list() as $component) {
173 if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
174 $contextlist = $this->get_provider_classname($component)::get_contexts_for_userid($userid);
175 } else {
176 $contextlist = new local\request\contextlist();
177 }
178
179 // Each contextlist is tied to its respective component.
180 $contextlist->set_component($component);
181
182 // Add contexts that the component may not know about.
183 // Example of these include activity completion which modules do not know about themselves.
184 $contextlist = local\request\helper::add_shared_contexts_to_contextlist_for($userid, $contextlist);
185
186 if (count($contextlist)) {
187 $clcollection->add_contextlist($contextlist);
188 }
189 }
190
191 return $clcollection;
192 }
193
194 /**
195 * Export all user data for the specified approved_contextlist items.
196 *
197 * Note: userid and component are stored in each respective approved_contextlist.
198 *
199 * @param contextlist_collection $contextlistcollection the collection of contextlists for all components.
200 * @return string the location of the exported data.
201 * @throws \moodle_exception if the contextlist_collection does not contain all approved_contextlist items or if one of the
202 * approved_contextlists' components is not a core_data_provider.
203 */
204 public function export_user_data(contextlist_collection $contextlistcollection) {
205 // Export for the various components/contexts.
206 foreach ($contextlistcollection as $approvedcontextlist) {
207 if (!$approvedcontextlist instanceof \core_privacy\local\request\approved_contextlist) {
208 throw new \moodle_exception('Contextlist must be an approved_contextlist');
209 }
210
211 $component = $approvedcontextlist->get_component();
212 // Core user data providers.
213 if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
214 if (count($approvedcontextlist)) {
215 // This plugin has data it knows about. It is responsible for storing basic data about anything it is
216 // told to export.
217 $this->get_provider_classname($component)::export_user_data($approvedcontextlist);
218 }
9faf51a7 219 } else if (!$this->component_implements($component, \core_privacy\local\request\context_aware_provider::class)) {
0f59848d
JD
220 // This plugin does not know that it has data - export the shared data it doesn't know about.
221 local\request\helper::export_data_for_null_provider($approvedcontextlist);
222 }
223 }
224
225 // Check each component for non contextlist items too.
226 foreach ($this->get_component_list() as $component) {
227 // Core user preference providers.
228 if ($this->component_implements($component, \core_privacy\local\request\user_preference_provider::class)) {
229 $this->get_provider_classname($component)::export_user_preferences($contextlistcollection->get_userid());
230 }
0f59848d 231
e4a37fbe
JD
232 // Contextual information providers. Give each component a chance to include context information based on the
233 // existence of a child context in the contextlist_collection.
9faf51a7 234 if ($this->component_implements($component, \core_privacy\local\request\context_aware_provider::class)) {
e4a37fbe 235 $this->get_provider_classname($component)::export_context_data($contextlistcollection);
9faf51a7
AG
236 }
237 }
238
0f59848d
JD
239 return local\request\writer::with_context(\context_system::instance())->finalise_content();
240 }
241
242 /**
243 * Delete all user data for approved contexts lists provided in the collection.
244 *
245 * This call relates to the forgetting of an entire user.
246 *
247 * Note: userid and component are stored in each respective approved_contextlist.
248 *
249 * @param contextlist_collection $contextlistcollection the collections of approved_contextlist items on which to call deletion.
250 * @throws \moodle_exception if the contextlist_collection doesn't contain all approved_contextlist items, or if the component
251 * for an approved_contextlist isn't a core provider.
252 */
e98f0cf7 253 public function delete_data_for_user(contextlist_collection $contextlistcollection) {
0f59848d
JD
254 // Delete the data.
255 foreach ($contextlistcollection as $approvedcontextlist) {
256 if (!$approvedcontextlist instanceof \core_privacy\local\request\approved_contextlist) {
257 throw new \moodle_exception('Contextlist must be an approved_contextlist');
258 }
259
260 if ($this->component_is_core_provider($approvedcontextlist->get_component())) {
261 if (count($approvedcontextlist)) {
262 // The component knows about data that it has.
263 // Have it delete its own data.
e98f0cf7 264 $this->get_provider_classname($approvedcontextlist->get_component())::delete_data_for_user($approvedcontextlist);
0f59848d
JD
265 }
266 }
267
268 // Delete any shared user data it doesn't know about.
e98f0cf7 269 local\request\helper::delete_data_for_user($approvedcontextlist);
0f59848d
JD
270 }
271 }
272
273 /**
70f09234 274 * Delete all use data which matches the specified deletion criteria.
0f59848d 275 *
70f09234 276 * @param context $context The specific context to delete data for.
0f59848d 277 */
e98f0cf7 278 public function delete_data_for_all_users_in_context(\context $context) {
0f59848d
JD
279 foreach ($this->get_component_list() as $component) {
280 if ($this->component_implements($component, \core_privacy\local\request\core_user_data_provider::class)) {
281 // This component knows about specific data that it owns.
282 // Have it delete all of that user data for the context.
e98f0cf7 283 $this->get_provider_classname($component)::delete_data_for_all_users_in_context($context);
0f59848d
JD
284 }
285
286 // Delete any shared user data it doesn't know about.
e98f0cf7 287 local\request\helper::delete_data_for_all_users_in_context($component, $context);
0f59848d
JD
288 }
289 }
290
291 /**
292 * Check whether the specified component is a core provider.
293 *
294 * @param string $component the frankenstyle component name.
295 * @return bool true if the component is a core provider, false otherwise.
296 */
297 protected function component_is_core_provider($component) {
298 return $this->component_implements($component, \core_privacy\local\request\core_data_provider::class);
299 }
300
301 /**
302 * Returns a list of frankenstyle names of core components (plugins and subsystems).
303 *
304 * @return array the array of frankenstyle component names.
305 */
306 protected function get_component_list() {
66f2da2e 307 $components = array_keys(array_reduce(\core_component::get_component_list(), function($carry, $item) {
6e6ba823
JD
308 return array_merge($carry, $item);
309 }, []));
66f2da2e
AN
310 $components[] = 'core';
311
312 return $components;
0f59848d
JD
313 }
314
315 /**
316 * Return the fully qualified provider classname for the component.
317 *
318 * @param string $component the frankenstyle component name.
319 * @return string the fully qualified provider classname.
320 */
321 protected function get_provider_classname($component) {
322 return static::get_provider_classname_for_component($component);
323 }
324
325 /**
326 * Return the fully qualified provider classname for the component.
327 *
328 * @param string $component the frankenstyle component name.
329 * @return string the fully qualified provider classname.
330 */
331 public static function get_provider_classname_for_component(string $component) {
332 return "$component\privacy\provider";
333 }
334
335 /**
336 * Checks whether the component's provider class implements the specified interface.
337 * This can either be implemented directly, or by implementing a descendant (extension) of the specified interface.
338 *
339 * @param string $component the frankenstyle component name.
340 * @param string $interface the name of the interface we want to check.
341 * @return bool True if an implementation was found, false otherwise.
342 */
343 protected function component_implements(string $component, string $interface) : bool {
344 $providerclass = $this->get_provider_classname($component);
345 if (class_exists($providerclass)) {
346 $rc = new \ReflectionClass($providerclass);
347 return $rc->implementsInterface($interface);
348 }
349 return false;
350 }
4f9f035a
AN
351
352 /**
353 * Call the named method with the specified params on any plugintype implementing the relevant interface.
354 *
355 * @param string $plugintype The plugingtype to check
356 * @param string $interface The interface to implement
357 * @param string $methodname The method to call
358 * @param array $params The params to call
359 */
360 public static function plugintype_class_callback(string $plugintype, string $interface, string $methodname, array $params) {
361 $components = \core_component::get_plugin_list($plugintype);
362 foreach (array_keys($components) as $component) {
363 static::component_class_callback("{$plugintype}_{$component}", $interface, $methodname, $params);
364 }
365 }
366
367 /**
368 * Call the named method with the specified params on the supplied component if it implements the relevant interface on its provider.
369 *
370 * @param string $component The component to call
371 * @param string $interface The interface to implement
372 * @param string $methodname The method to call
373 * @param array $params The params to call
374 * @return mixed
375 */
376 public static function component_class_callback(string $component, string $interface, string $methodname, array $params) {
377 $classname = static::get_provider_classname_for_component($component);
378 if (class_exists($classname) && is_subclass_of($classname, $interface)) {
379 return component_class_callback($classname, $methodname, $params);
380 }
381
382 return null;
383 }
0f59848d 384}