MDL-63495 core_rating: Add helper to fetch users in context
[moodle.git] / rating / classes / privacy / provider.php
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/>.
17 /**
18  * Privacy Subsystem implementation for core_ratings.
19  *
20  * @package    core_rating
21  * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace core_rating\privacy;
27 use \core_privacy\local\metadata\collection;
28 use \core_privacy\local\request\userlist;
30 defined('MOODLE_INTERNAL') || die();
32 require_once($CFG->dirroot . '/rating/lib.php');
34 /**
35  * Privacy Subsystem implementation for core_ratings.
36  *
37  * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class provider implements
41         // The ratings subsystem contains data.
42         \core_privacy\local\metadata\provider,
44         // The ratings subsystem is only ever used to store data for other components.
45         // It does not store any data of its own and does not need to implement the \core_privacy\local\request\subsystem\provider
46         // as a result.
48         // The ratings subsystem provides a data service to other components.
49         \core_privacy\local\request\subsystem\plugin_provider {
51     /**
52      * Returns metadata about the ratings subsystem.
53      *
54      * @param   collection     $collection The initialised collection to add items to.
55      * @return  collection     A listing of user data stored through the subsystem.
56      */
57     public static function get_metadata(collection $collection) : collection {
58         // The table 'rating' cotains data that a user has entered.
59         // It stores the user-entered rating alongside a mapping to describe what was mapped.
60         $collection->add_database_table('rating', [
61                 'rating' => 'privacy:metadata:rating:rating',
62                 'userid' => 'privacy:metadata:rating:userid',
63                 'timecreated' => 'privacy:metadata:rating:timecreated',
64                 'timemodified' => 'privacy:metadata:rating:timemodified',
65             ], 'privacy:metadata:rating');
67         return $collection;
68     }
70     /**
71      * Export all ratings which match the specified component, areaid, and itemid.
72      *
73      * If requesting ratings for a users own content, and you wish to include all ratings of that content, specify
74      * $onlyuser as false.
75      *
76      * When requesting ratings for another users content, you should only export the ratings that the specified user
77      * made themselves.
78      *
79      * @param   int         $userid The user whose information is to be exported
80      * @param   \context    $context The context being stored.
81      * @param   array       $subcontext The subcontext within the context to export this information
82      * @param   string      $component The component to fetch data from
83      * @param   string      $ratingarea The ratingarea that the data was stored in within the component
84      * @param   int         $itemid The itemid within that ratingarea
85      * @param   bool        $onlyuser Whether to only export ratings that the current user has made, or all ratings
86      */
87     public static function export_area_ratings(
88         int $userid,
89         \context $context,
90         array $subcontext,
91         string $component,
92         string $ratingarea,
93         int $itemid,
94         bool $onlyuser = true
95     ) {
96         global $DB;
98         $rm = new \rating_manager();
99         $ratings = $rm->get_all_ratings_for_item((object) [
100             'context' => $context,
101             'component' => $component,
102             'ratingarea' => $ratingarea,
103             'itemid' => $itemid,
104         ]);
106         if ($onlyuser) {
107             $ratings = array_filter($ratings, function($rating) use ($userid){
108                 return ($rating->userid == $userid);
109             });
110         }
112         if (empty($ratings)) {
113             return;
114         }
116         $toexport = array_map(function($rating) {
117             return (object) [
118                 'rating' => $rating->rating,
119                 'author' => $rating->userid,
120             ];
121         }, $ratings);
123         $writer = \core_privacy\local\request\writer::with_context($context)
124             ->export_related_data($subcontext, 'rating', $toexport);
125     }
127     /**
128      * Get the SQL required to find all submission items where this user has had any involvements.
129      *
130      * @param   string          $alias      The name of the table alias to use.
131      * @param   string          $component  The na eof the component to fetch ratings for.
132      * @param   string          $ratingarea The rating area to fetch results for.
133      * @param   string          $itemidjoin The right-hand-side of the JOIN ON clause.
134      * @param   int             $userid     The ID of the user being stored.
135      * @return  \stdClass
136      */
137     public static function get_sql_join($alias, $component, $ratingarea, $itemidjoin, $userid) {
138         static $count = 0;
139         $count++;
141         // Join the rating table with the specified alias and the relevant join params.
142         $join = "LEFT JOIN {rating} {$alias} ON ";
143         $join .= "{$alias}.userid = :ratinguserid{$count} AND ";
144         $join .= "{$alias}.component = :ratingcomponent{$count} AND ";
145         $join .= "{$alias}.ratingarea = :ratingarea{$count} AND ";
146         $join .= "{$alias}.itemid = {$itemidjoin}";
148         // Match against the specified user.
149         $userwhere = "{$alias}.id IS NOT NULL";
151         $params = [
152             'ratingcomponent' . $count  => $component,
153             'ratingarea' . $count       => $ratingarea,
154             'ratinguserid' . $count     => $userid,
155         ];
157         $return = (object) [
158             'join' => $join,
159             'params' => $params,
160             'userwhere' => $userwhere,
161         ];
162         return $return;
163     }
165     /**
166      * Deletes all ratings for a specified context, component, ratingarea and itemid.
167      *
168      * Only delete ratings when the item itself was deleted.
169      *
170      * We never delete ratings for one user but not others - this may affect grades, therefore ratings
171      * made by particular user are not considered personal information.
172      *
173      * @param  \context $context Details about which context to delete ratings for.
174      * @param  string $component Component to delete.
175      * @param  string $ratingarea Rating area to delete.
176      * @param  int $itemid The item ID for use with deletion.
177      */
178     public static function delete_ratings(\context $context, string $component = null,
179             string $ratingarea = null, int $itemid = null) {
180         global $DB;
182         $options = ['contextid' => $context->id];
183         if ($component) {
184             $options['component'] = $component;
185         }
186         if ($ratingarea) {
187             $options['ratingarea'] = $ratingarea;
188         }
189         if ($itemid) {
190             $options['itemid'] = $itemid;
191         }
193         $DB->delete_records('rating', $options);
194     }
196     /**
197      * Deletes all tag instances for given context, component, itemtype using subquery for itemids
198      *
199      * In most situations you will want to specify $userid as null. Per-user tag instances
200      * are possible in Tags API, however there are no components or standard plugins that actually use them.
201      *
202      * @param  \context $context Details about which context to delete ratings for.
203      * @param  string $component Component to delete.
204      * @param  string $ratingarea Rating area to delete.
205      * @param  string $itemidstest an SQL fragment that the itemid must match. Used
206      *      in the query like WHERE itemid $itemidstest. Must use named parameters,
207      *      and may not use named parameters called contextid, component or ratingarea.
208      * @param array $params any query params used by $itemidstest.
209      */
210     public static function delete_ratings_select(\context $context, string $component,
211              string $ratingarea, $itemidstest, $params = []) {
212         global $DB;
213         $params += ['contextid' => $context->id, 'component' => $component, 'ratingarea' => $ratingarea];
214         $DB->delete_records_select('rating',
215             'contextid = :contextid AND component = :component AND ratingarea = :ratingarea AND itemid ' . $itemidstest,
216             $params);
217     }
219     /**
220      * Add the list of users who have rated in the specified constraints.
221      *
222      * @param   userlist    $userlist The userlist to add the users to.
223      * @param   string      $alias An alias prefix to use for rating selects to avoid interference with your own sql.
224      * @param   string      $component The component to check.
225      * @param   string      $area The rating area to check.
226      * @param   string      $insql The SQL to use in a sub-select for the itemid query.
227      * @param   array       $params The params required for the insql.
228      */
229     public static function get_users_in_context_from_sql(
230             userlist $userlist, string $alias, string $component, string $area, string $insql, $params) {
231         // Discussion authors.
232         $sql = "SELECT {$alias}.userid
233                   FROM {rating} {$alias}
234                  WHERE {$alias}.component = :{$alias}component
235                    AND {$alias}.ratingarea = :{$alias}ratingarea
236                    AND {$alias}.itemid IN ({$insql})";
238         $params["{$alias}component"] = $component;
239         $params["{$alias}ratingarea"] = $area;
241         $userlist->add_from_sql('userid', $sql, $params);
242     }