MDL-63658 core_favourites: adding paging support to the service layer
[moodle.git] / favourites / classes / local / repository / favourites_repository.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/>.
16 /**
17  * Contains the user_favourites_repository class, responsible for CRUD operations for user favourites.
18  *
19  * @package   core_favourites
20  * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
23 namespace core_favourites\local\repository;
25 defined('MOODLE_INTERNAL') || die();
27 /**
28  * Class favourites_repository.
29  *
30  * This class handles persistence of favourites. Favourites from all areas are supported by this repository.
31  *
32  * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
33  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class favourites_repository implements ifavourites_repository {
37     /**
38      * @var string the name of the table which favourites are stored in.
39      */
40     protected $favouritetable = 'favourite';
42     /**
43      * The favourites_repository constructor.
44      */
45     public function __construct() {
46     }
48     /**
49      * Add a favourite to the repository.
50      *
51      * @param \stdClass $favourite the favourite to add.
52      * @return \stdClass the favourite which has been stored.
53      * @throws \dml_exception if any database errors are encountered.
54      * @throws \moodle_exception if the favourite has missing or invalid properties.
55      */
56     public function add($favourite) : \stdClass {
57         global $DB;
58         $this->validate($favourite);
59         $favourite = (array)$favourite;
60         $time = time();
61         $favourite['timecreated'] = $time;
62         $favourite['timemodified'] = $time;
63         $id = $DB->insert_record($this->favouritetable, $favourite);
64         return $this->find($id);
65     }
67     /**
68      * Add a collection of favourites to the repository.
69      *
70      * @param array $items the list of favourites to add.
71      * @return array the list of favourites which have been stored.
72      * @throws \dml_exception if any database errors are encountered.
73      * @throws \moodle_exception if any of the favourites have missing or invalid properties.
74      */
75     public function add_all(array $items) : array {
76         global $DB;
77         $time = time();
78         foreach ($items as $item) {
79             $this->validate($item);
80             $favourite = (array)$item;
81             $favourite['timecreated'] = $time;
82             $favourite['timemodified'] = $time;
83             $ids[] = $DB->insert_record($this->favouritetable, $favourite);
84         }
85         list($insql, $params) = $DB->get_in_or_equal($ids);
86         return $DB->get_records_select($this->favouritetable, "id $insql", $params);
87     }
89     /**
90      * Find a favourite by id.
91      *
92      * @param int $id the id of the favourite.
93      * @return \stdClass the favourite.
94      * @throws \dml_exception if any database errors are encountered.
95      */
96     public function find(int $id) : \stdClass {
97         global $DB;
98         return $DB->get_record($this->favouritetable, ['id' => $id], '*', MUST_EXIST);
99     }
101     /**
102      * Return all items matching the supplied criteria (a [key => value,..] list).
103      *
104      * @param array $criteria the list of key/value criteria pairs.
105      * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
106      * @param int $limitnum optional pagination control for returning a subset comprising this many records.
107      * @return array the list of favourites matching the criteria.
108      * @throws \dml_exception if any database errors are encountered.
109      */
110     public function find_by(array $criteria, int $limitfrom = 0, int $limitnum = 0) : array {
111         global $DB;
112         return $DB->get_records($this->favouritetable, $criteria, '', '*', $limitfrom, $limitnum);
113     }
115     /**
116      * Return all items in this repository, as an array, indexed by id.
117      *
118      * @param int $limitfrom optional pagination control for returning a subset of records, starting at this point.
119      * @param int $limitnum optional pagination control for returning a subset comprising this many records.
120      * @return array the list of all favourites stored within this repository.
121      * @throws \dml_exception if any database errors are encountered.
122      */
123     public function find_all(int $limitfrom = 0, int $limitnum = 0) : array {
124         global $DB;
125         return $DB->get_records($this->favouritetable, null, '', '*', $limitfrom, $limitnum);
126     }
128     /**
129      * Find a specific favourite, based on the properties known to identify it.
130      *
131      * Used if we don't know its id.
132      *
133      * @param int $userid the id of the user to which the favourite belongs.
134      * @param string $component the frankenstyle component name.
135      * @param string $itemtype the type of the favourited item.
136      * @param int $itemid the id of the item which was favourited (not the favourite's id).
137      * @param int $contextid the contextid of the item which was favourited.
138      * @return \stdClass the favourite.
139      * @throws \dml_exception if any database errors are encountered or if the record could not be found.
140      */
141     public function find_favourite(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : \stdClass {
142         global $DB;
143         // Favourites model: We know that only one favourite can exist based on these properties.
144         return $DB->get_record($this->favouritetable, [
145             'userid' => $userid,
146             'component' => $component,
147             'itemtype' => $itemtype,
148             'itemid' => $itemid,
149             'contextid' => $contextid
150         ], '*', MUST_EXIST);
151     }
153     /**
154      * Check whether a favourite exists in this repository, based on its id.
155      *
156      * @param int $id the id to search for.
157      * @return bool true if the favourite exists, false otherwise.
158      * @throws \dml_exception if any database errors are encountered.
159      */
160     public function exists(int $id) : bool {
161         global $DB;
162         return $DB->record_exists($this->favouritetable, ['id' => $id]);
163     }
165     /**
166      * Update a favourite.
167      *
168      * @param \stdClass $favourite the favourite to update.
169      * @return \stdClass the updated favourite.
170      * @throws \dml_exception if any database errors are encountered.
171      */
172     public function update($favourite) : \stdClass {
173         global $DB;
174         $time = time();
175         $favourite->timemodified = $time;
176         $DB->update_record($this->favouritetable, $favourite);
177         return $this->find($favourite->id);
178     }
180     /**
181      * Delete a favourite, by id.
182      *
183      * @param int $id the id of the favourite to delete.
184      * @throws \dml_exception if any database errors are encountered.
185      */
186     public function delete(int $id) {
187         global $DB;
188         $DB->delete_records($this->favouritetable, ['id' => $id]);
189     }
191     /**
192      * Return the total number of favourites in this repository.
193      *
194      * @return int the total number of items.
195      * @throws \dml_exception if any database errors are encountered.
196      */
197     public function count() : int {
198         global $DB;
199         return $DB->count_records($this->favouritetable);
200     }
202     /**
203      * Check for the existence of a favourite item in the specified area.
204      *
205      * A favourite item is identified by the itemid/contextid pair.
206      * An area is identified by the component/itemtype pair.
207      *
208      * @param int $userid the id of user to whom the favourite belongs.
209      * @param string $component the frankenstyle component name.
210      * @param string $itemtype the type of the favourited item.
211      * @param int $itemid the id of the item which was favourited (not the favourite's id).
212      * @param int $contextid the contextid of the item which was favourited.
213      * @return bool true if the favourited item exists, false otherwise.
214      * @throws \dml_exception if any database errors are encountered.
215      */
216     public function exists_by_area(int $userid, string $component, string $itemtype, int $itemid, int $contextid) : bool {
217         global $DB;
218         return $DB->record_exists($this->favouritetable,
219             [
220                 'userid' => $userid,
221                 'component' => $component,
222                 'itemtype' => $itemtype,
223                 'itemid' => $itemid,
224                 'contextid' => $contextid
225             ]
226         );
227     }
229     /**
230      * Delete all favourites within the component/itemtype.
231      *
232      * @param int $userid the id of the user to whom the favourite belongs.
233      * @param string $component the frankenstyle component name.
234      * @param string $itemtype the type of the favourited item.
235      * @throws \dml_exception if any database errors are encountered.
236      */
237     public function delete_by_area(int $userid, string $component, string $itemtype) {
238         global $DB;
239         $DB->delete_records($this->favouritetable,
240             [
241                 'userid' => $userid,
242                 'component' => $component,
243                 'itemtype' => $itemtype
244             ]
245         );
246     }
248     /**
249      * Return the number of user favourites matching the specified criteria.
250      *
251      * @param array $criteria the list of key/value criteria pairs.
252      * @return int the number of favourites matching the criteria.
253      * @throws \dml_exception if any database errors are encountered.
254      */
255     public function count_by(array $criteria) : int {
256         global $DB;
257         return $DB->count_records($this->favouritetable, $criteria);
258     }
260     /**
261      * Basic validation, confirming we have the minimum field set needed to save a record to the store.
262      *
263      * @param \stdClass $favourite the favourite record to validate.
264      * @throws \moodle_exception if the supplied favourite has missing or unsupported fields.
265      */
266     protected function validate(\stdClass $favourite) {
268         $favourite = (array)$favourite;
270         // The allowed fields, and whether or not each is required.
271         // The timecreated field is generated during create/update, and cannot be specified either.
272         $allowedfields = [
273             'userid' => true,
274             'component' => true,
275             'itemtype' => true,
276             'itemid' => true,
277             'contextid' => true,
278             'ordering' => false
279         ];
281         $requiredfields = array_filter($allowedfields, function($field) {
282             return $field;
283         });
285         if ($missingfields = array_keys(array_diff_key($requiredfields, $favourite))) {
286             throw new \moodle_exception("Missing object property(s) '" . join(', ', $missingfields) . "'.");
287         }
289         // If the record contains fields we don't allow, throw an exception.
290         if ($unsupportedfields = array_keys(array_diff_key($favourite, $allowedfields))) {
291             throw new \moodle_exception("Unexpected object property(s) '" . join(', ', $unsupportedfields) . "'.");
292         }
293     }