MDL-67062 core_h5p: delete libraries
[moodle.git] / course / classes / local / service / content_item_service.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  * Contains the content_item_service class.
19  *
20  * @package    core
21  * @subpackage course
22  * @copyright  2020 Jake Dallimore <jrhdallimore@gmail.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
25 namespace core_course\local\service;
27 defined('MOODLE_INTERNAL') || die();
29 use core_course\local\exporters\course_content_items_exporter;
30 use core_course\local\repository\content_item_readonly_repository_interface;
32 /**
33  * The content_item_service class, providing the api for interacting with content items.
34  *
35  * @copyright  2020 Jake Dallimore <jrhdallimore@gmail.com>
36  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class content_item_service {
40     /** @var content_item_readonly_repository_interface $repository a repository for content items. */
41     private $repository;
43     /**
44      * The content_item_service constructor.
45      *
46      * @param content_item_readonly_repository_interface $repository a content item repository.
47      */
48     public function __construct(content_item_readonly_repository_interface $repository) {
49         $this->repository = $repository;
50     }
52     /**
53      * Returns an array of objects representing favourited content items.
54      *
55      * Each object contains the following properties:
56      * itemtype: a string containing the 'itemtype' key used by the favourites subsystem.
57      * ids[]: an array of ids, representing the content items within a component.
58      *
59      * Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness.
60      *
61      * @param \stdClass $user
62      * @return array
63      */
64     private function get_favourite_content_items_for_user(\stdClass $user): array {
65         $favcache = \cache::make('core', 'user_favourite_course_content_items');
66         $key = $user->id;
67         $favmods = $favcache->get($key);
68         if ($favmods !== false) {
69             return $favmods;
70         }
72         // Get all modules and any submodules which implement get_course_content_items() hook.
73         // This gives us the set of all itemtypes which we'll use to register favourite content items.
74         // The ids that each plugin returns will be used together with the itemtype to uniquely identify
75         // each content item for favouriting.
76         $pluginmanager = \core_plugin_manager::instance();
77         $plugins = $pluginmanager->get_plugins_of_type('mod');
78         $itemtypes = [];
79         foreach ($plugins as $plugin) {
80             // Add the mod itself.
81             $itemtypes[] = 'contentitem_mod_' . $plugin->name;
83             // Add any subplugins to the list of item types.
84             $subplugins = $pluginmanager->get_subplugins_of_plugin('mod_' . $plugin->name);
85             foreach ($subplugins as $subpluginname => $subplugininfo) {
86                 if (component_callback_exists($subpluginname, 'get_course_content_items')) {
87                     $itemtypes[] = 'contentitem_' . $subpluginname;
88                 }
89             }
90         }
92         $ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($user->id));
93         $favourites = [];
94         foreach ($itemtypes as $itemtype) {
95             $favs = $ufservice->find_favourites_by_type('core_course', $itemtype);
96             $favobj = (object) ['itemtype' => $itemtype, 'ids' => array_column($favs, 'itemid')];
97             $favourites[] = $favobj;
98         }
99         $favcache->set($key, $favourites);
100         return $favourites;
101     }
103     /**
104      * Get all content items which may be added to courses, irrespective of course caps, for site admin views, etc.
105      *
106      * @param \stdClass $user the user object.
107      * @return array the array of exported content items.
108      */
109     public function get_all_content_items(\stdClass $user): array {
110         global $PAGE;
111         $allcontentitems = $this->repository->find_all();
113         // Export the objects to get the formatted objects for transfer/display.
114         $favourites = $this->get_favourite_content_items_for_user($user);
115         $ciexporter = new course_content_items_exporter(
116             $allcontentitems,
117             [
118                 'context' => \context_system::instance(),
119                 'favouriteitems' => $favourites
120             ]
121         );
122         $exported = $ciexporter->export($PAGE->get_renderer('core'));
124         // Sort by title for return.
125         usort($exported->content_items, function($a, $b) {
126             return $a->title > $b->title;
127         });
129         return $exported->content_items;
130     }
132     /**
133      * Return a representation of the available content items, for a user in a course.
134      *
135      * @param \stdClass $user the user to check access for.
136      * @param \stdClass $course the course to scope the content items to.
137      * @param array $linkparams the desired section to return to.
138      * @return \stdClass[] the content items, scoped to a course.
139      */
140     public function get_content_items_for_user_in_course(\stdClass $user, \stdClass $course, array $linkparams = []): array {
141         global $PAGE;
143         if (!has_capability('moodle/course:manageactivities', \context_course::instance($course->id), $user)) {
144             return [];
145         }
147         // Get all the visible content items.
148         $allcontentitems = $this->repository->find_all_for_course($course, $user);
150         // Content items can only originate from modules or submodules.
151         $pluginmanager = \core_plugin_manager::instance();
152         $components = \core_component::get_component_list();
153         $parents = [];
154         foreach ($allcontentitems as $contentitem) {
155             if (!in_array($contentitem->get_component_name(), array_keys($components['mod']))) {
156                 // It could be a subplugin.
157                 $info = $pluginmanager->get_plugin_info($contentitem->get_component_name());
158                 if (!is_null($info)) {
159                     $parent = $info->get_parent_plugin();
160                     if ($parent != false) {
161                         if (in_array($parent, array_keys($components['mod']))) {
162                             $parents[$contentitem->get_component_name()] = $parent;
163                             continue;
164                         }
165                     }
166                 }
167                 throw new \moodle_exception('Only modules and submodules can generate content items. \''
168                     . $contentitem->get_component_name() . '\' is neither.');
169             }
170             $parents[$contentitem->get_component_name()] = $contentitem->get_component_name();
171         }
173         // Now, check access to these items for the user.
174         $availablecontentitems = array_filter($allcontentitems, function($contentitem) use ($course, $user, $parents) {
175             // Check the parent module access for the user.
176             return course_allowed_module($course, explode('_', $parents[$contentitem->get_component_name()])[1], $user);
177         });
179         // Add the link params to the link, if any have been provided.
180         if (!empty($linkparams)) {
181             $availablecontentitems = array_map(function ($item) use ($linkparams) {
182                 $item->get_link()->params($linkparams);
183                 return $item;
184             }, $availablecontentitems);
185         }
187         // Export the objects to get the formatted objects for transfer/display.
188         $favourites = $this->get_favourite_content_items_for_user($user);
189         $ciexporter = new course_content_items_exporter(
190             $availablecontentitems,
191             [
192                 'context' => \context_course::instance($course->id),
193                 'favouriteitems' => $favourites
194             ]
195         );
196         $exported = $ciexporter->export($PAGE->get_renderer('course'));
198         // Sort by title for return.
199         usort($exported->content_items, function($a, $b) {
200             return $a->title > $b->title;
201         });
203         return $exported->content_items;
204     }
206     /**
207      * Add a content item to a user's favourites.
208      *
209      * @param \stdClass $user the user whose favourite this is.
210      * @param string $componentname the name of the component from which the content item originates.
211      * @param int $contentitemid the id of the content item.
212      * @return \stdClass the exported content item.
213      */
214     public function add_to_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
215         $usercontext = \context_user::instance($user->id);
216         $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
218         // Because each plugin decides its own ids for content items, a combination of
219         // itemtype and id is used to guarantee uniqueness across all content items.
220         $itemtype = 'contentitem_' . $componentname;
222         $ufservice->create_favourite('core_course', $itemtype, $contentitemid, $usercontext);
224         $favcache = \cache::make('core', 'user_favourite_course_content_items');
225         $favcache->delete($user->id);
227         $items = $this->get_all_content_items($user);
228         return $items[array_search($contentitemid, array_column($items, 'id'))];
229     }
231     /**
232      * Remove the content item from a user's favourites.
233      *
234      * @param \stdClass $user the user whose favourite this is.
235      * @param string $componentname the name of the component from which the content item originates.
236      * @param int $contentitemid the id of the content item.
237      * @return \stdClass the exported content item.
238      */
239     public function remove_from_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
240         $usercontext = \context_user::instance($user->id);
241         $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
243         // Because each plugin decides its own ids for content items, a combination of
244         // itemtype and id is used to guarantee uniqueness across all content items.
245         $itemtype = 'contentitem_' . $componentname;
247         $ufservice->delete_favourite('core_course', $itemtype, $contentitemid, $usercontext);
249         $favcache = \cache::make('core', 'user_favourite_course_content_items');
250         $favcache->delete($user->id);
252         $items = $this->get_all_content_items($user);
253         return $items[array_search($contentitemid, array_column($items, 'id'))];
254     }