Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * 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;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
use core_course\local\exporters\course_content_items_exporter;
30
use core_course\local\repository\content_item_readonly_repository_interface;
31
 
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 {
39
 
40
    /** @var content_item_readonly_repository_interface $repository a repository for content items. */
41
    private $repository;
42
 
43
    /** string the component for this favourite. */
44
    public const COMPONENT = 'core_course';
45
    /** string the favourite prefix itemtype in the favourites table. */
46
    public const FAVOURITE_PREFIX = 'contentitem_';
47
    /** string the recommendation prefix itemtype in the favourites table. */
48
    public const RECOMMENDATION_PREFIX = 'recommend_';
49
    /** string the cache name for recommendations. */
50
    public const RECOMMENDATION_CACHE = 'recommendation_favourite_course_content_items';
51
 
52
    /**
53
     * The content_item_service constructor.
54
     *
55
     * @param content_item_readonly_repository_interface $repository a content item repository.
56
     */
57
    public function __construct(content_item_readonly_repository_interface $repository) {
58
        $this->repository = $repository;
59
    }
60
 
61
    /**
62
     * Returns an array of objects representing favourited content items.
63
     *
64
     * Each object contains the following properties:
65
     * itemtype: a string containing the 'itemtype' key used by the favourites subsystem.
66
     * ids[]: an array of ids, representing the content items within a component.
67
     *
68
     * Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness.
69
     *
70
     * @param \stdClass $user
71
     * @return array
72
     */
73
    private function get_favourite_content_items_for_user(\stdClass $user): array {
74
        $favcache = \cache::make('core', 'user_favourite_course_content_items');
75
        $key = $user->id;
76
        $favmods = $favcache->get($key);
77
        if ($favmods !== false) {
78
            return $favmods;
79
        }
80
 
81
        $favourites = $this->get_content_favourites(self::FAVOURITE_PREFIX, \context_user::instance($user->id));
82
 
83
        $favcache->set($key, $favourites);
84
        return $favourites;
85
    }
86
 
87
    /**
88
     * Returns an array of objects representing recommended content items.
89
     *
90
     * Each object contains the following properties:
91
     * itemtype: a string containing the 'itemtype' key used by the favourites subsystem.
92
     * ids[]: an array of ids, representing the content items within a component.
93
     *
94
     * Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness.
95
     *
96
     * @return array
97
     */
98
    private function get_recommendations(): array {
99
        global $CFG;
100
 
101
        $recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE);
102
        $key = $CFG->siteguest;
103
        $favmods = $recommendationcache->get($key);
104
        if ($favmods !== false) {
105
            return $favmods;
106
        }
107
 
108
        // Make sure the guest user exists in the database.
109
        if (!\core_user::get_user($CFG->siteguest)) {
110
            throw new \coding_exception('The guest user does not exist in the database.');
111
        }
112
 
113
        // Make sure the guest user context exists.
114
        if (!$guestusercontext = \context_user::instance($CFG->siteguest, false)) {
115
            throw new \coding_exception('The guest user context does not exist.');
116
        }
117
 
118
        $favourites = $this->get_content_favourites(self::RECOMMENDATION_PREFIX, $guestusercontext);
119
 
120
        $recommendationcache->set($CFG->siteguest, $favourites);
121
        return $favourites;
122
    }
123
 
124
    /**
125
     * Gets content favourites from the favourites system depending on the area.
126
     *
127
     * @param  string        $prefix      Prefix for the item type.
128
     * @param  \context_user $usercontext User context for the favourite
129
     * @return array An array of favourite objects.
130
     */
131
    private function get_content_favourites(string $prefix, \context_user $usercontext): array {
132
        // Get all modules and any submodules which implement get_course_content_items() hook.
133
        // This gives us the set of all itemtypes which we'll use to register favourite content items.
134
        // The ids that each plugin returns will be used together with the itemtype to uniquely identify
135
        // each content item for favouriting.
136
        $pluginmanager = \core_plugin_manager::instance();
137
        $plugins = $pluginmanager->get_plugins_of_type('mod');
138
        $itemtypes = [];
139
        foreach ($plugins as $plugin) {
140
            // Add the mod itself.
141
            $itemtypes[] = $prefix . 'mod_' . $plugin->name;
142
 
143
            // Add any subplugins to the list of item types.
144
            $subplugins = $pluginmanager->get_subplugins_of_plugin('mod_' . $plugin->name);
145
            foreach ($subplugins as $subpluginname => $subplugininfo) {
146
                try {
147
                    if (component_callback_exists($subpluginname, 'get_course_content_items')) {
148
                        $itemtypes[] = $prefix . $subpluginname;
149
                    }
150
                } catch (\moodle_exception $e) {
151
                    debugging('Cannot get_course_content_items: ' . $e->getMessage(), DEBUG_DEVELOPER);
152
                }
153
            }
154
        }
155
 
156
        $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
157
        $favourites = [];
158
        $favs = $ufservice->find_all_favourites(self::COMPONENT, $itemtypes);
159
        $favsreduced = array_reduce($favs, function($carry, $item) {
160
            $carry[$item->itemtype][$item->itemid] = 0;
161
            return $carry;
162
        }, []);
163
 
164
        foreach ($itemtypes as $type) {
165
            $favourites[] = (object) [
166
                'itemtype' => $type,
167
                'ids' => isset($favsreduced[$type]) ? array_keys($favsreduced[$type]) : []
168
            ];
169
        }
170
        return $favourites;
171
    }
172
 
173
    /**
174
     * Get all content items which may be added to courses, irrespective of course caps, for site admin views, etc.
175
     *
176
     * @param \stdClass $user the user object.
177
     * @return array the array of exported content items.
178
     */
179
    public function get_all_content_items(\stdClass $user): array {
180
        $allcontentitems = $this->repository->find_all();
181
 
182
        return $this->export_content_items($user, $allcontentitems);
183
    }
184
 
185
    /**
186
     * Get content items which name matches a certain pattern and may be added to courses,
187
     * irrespective of course caps, for site admin views, etc.
188
     *
189
     * @param \stdClass $user The user object.
190
     * @param string $pattern The search pattern.
191
     * @return array The array of exported content items.
192
     */
193
    public function get_content_items_by_name_pattern(\stdClass $user, string $pattern): array {
194
        $allcontentitems = $this->repository->find_all();
195
 
196
        $filteredcontentitems = array_filter($allcontentitems, function($contentitem) use ($pattern) {
197
            return preg_match("/$pattern/i", $contentitem->get_title()->get_value());
198
        });
199
 
200
        return $this->export_content_items($user, $filteredcontentitems);
201
    }
202
 
203
    /**
204
     * Export content items.
205
     *
206
     * @param \stdClass $user The user object.
207
     * @param array $contentitems The content items array.
208
     * @return array The array of exported content items.
209
     */
210
    private function export_content_items(\stdClass $user, $contentitems) {
211
        global $PAGE;
212
 
213
        // Export the objects to get the formatted objects for transfer/display.
214
        $favourites = $this->get_favourite_content_items_for_user($user);
215
        $recommendations = $this->get_recommendations();
216
        $ciexporter = new course_content_items_exporter(
217
            $contentitems,
218
            [
219
                'context' => \context_system::instance(),
220
                'favouriteitems' => $favourites,
221
                'recommended' => $recommendations
222
            ]
223
        );
224
        $exported = $ciexporter->export($PAGE->get_renderer('core'));
225
 
226
        // Sort by title for return.
227
        \core_collator::asort_objects_by_property($exported->content_items, 'title');
228
        return array_values($exported->content_items);
229
    }
230
 
231
    /**
232
     * Return a representation of the available content items, for a user in a course.
233
     *
234
     * @param \stdClass $user the user to check access for.
235
     * @param \stdClass $course the course to scope the content items to.
236
     * @param array $linkparams the desired section to return to.
237
     * @return \stdClass[] the content items, scoped to a course.
238
     */
239
    public function get_content_items_for_user_in_course(\stdClass $user, \stdClass $course, array $linkparams = []): array {
240
        global $PAGE;
241
 
242
        if (!has_capability('moodle/course:manageactivities', \context_course::instance($course->id), $user)) {
243
            return [];
244
        }
245
 
246
        // Get all the visible content items.
247
        $allcontentitems = $this->repository->find_all_for_course($course, $user);
248
 
249
        // Content items can only originate from modules or submodules.
250
        $pluginmanager = \core_plugin_manager::instance();
251
        $components = \core_component::get_component_list();
252
        $parents = [];
253
        foreach ($allcontentitems as $contentitem) {
254
            if (!in_array($contentitem->get_component_name(), array_keys($components['mod']))) {
255
                // It could be a subplugin.
256
                $info = $pluginmanager->get_plugin_info($contentitem->get_component_name());
257
                if (!is_null($info)) {
258
                    $parent = $info->get_parent_plugin();
259
                    if ($parent != false) {
260
                        if (in_array($parent, array_keys($components['mod']))) {
261
                            $parents[$contentitem->get_component_name()] = $parent;
262
                            continue;
263
                        }
264
                    }
265
                }
266
                throw new \moodle_exception('Only modules and submodules can generate content items. \''
267
                    . $contentitem->get_component_name() . '\' is neither.');
268
            }
269
            $parents[$contentitem->get_component_name()] = $contentitem->get_component_name();
270
        }
271
 
272
        // Now, check access to these items for the user.
273
        $availablecontentitems = array_filter($allcontentitems, function($contentitem) use ($course, $user, $parents) {
274
            // Check the parent module access for the user.
275
            return course_allowed_module($course, explode('_', $parents[$contentitem->get_component_name()])[1], $user);
276
        });
277
 
278
        // Add the link params to the link, if any have been provided.
279
        if (!empty($linkparams)) {
280
            $availablecontentitems = array_map(function ($item) use ($linkparams) {
281
                $item->get_link()->params($linkparams);
282
                return $item;
283
            }, $availablecontentitems);
284
        }
285
 
286
        // Export the objects to get the formatted objects for transfer/display.
287
        $favourites = $this->get_favourite_content_items_for_user($user);
288
        $recommended = $this->get_recommendations();
289
        $ciexporter = new course_content_items_exporter(
290
            $availablecontentitems,
291
            [
292
                'context' => \context_course::instance($course->id),
293
                'favouriteitems' => $favourites,
294
                'recommended' => $recommended
295
            ]
296
        );
297
        $exported = $ciexporter->export($PAGE->get_renderer('course'));
298
 
299
        // Sort by title for return.
300
        \core_collator::asort_objects_by_property($exported->content_items, 'title');
301
 
302
        return array_values($exported->content_items);
303
    }
304
 
305
    /**
306
     * Add a content item to a user's favourites.
307
     *
308
     * @param \stdClass $user the user whose favourite this is.
309
     * @param string $componentname the name of the component from which the content item originates.
310
     * @param int $contentitemid the id of the content item.
311
     * @return \stdClass the exported content item.
312
     */
313
    public function add_to_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
314
        $usercontext = \context_user::instance($user->id);
315
        $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
316
 
317
        // Because each plugin decides its own ids for content items, a combination of
318
        // itemtype and id is used to guarantee uniqueness across all content items.
319
        $itemtype = self::FAVOURITE_PREFIX . $componentname;
320
 
321
        $ufservice->create_favourite(self::COMPONENT, $itemtype, $contentitemid, $usercontext);
322
 
323
        $favcache = \cache::make('core', 'user_favourite_course_content_items');
324
        $favcache->delete($user->id);
325
 
326
        $items = $this->get_all_content_items($user);
327
        return $items[array_search($contentitemid, array_column($items, 'id'))];
328
    }
329
 
330
    /**
331
     * Remove the content item from a user's favourites.
332
     *
333
     * @param \stdClass $user the user whose favourite this is.
334
     * @param string $componentname the name of the component from which the content item originates.
335
     * @param int $contentitemid the id of the content item.
336
     * @return \stdClass the exported content item.
337
     */
338
    public function remove_from_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
339
        $usercontext = \context_user::instance($user->id);
340
        $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
341
 
342
        // Because each plugin decides its own ids for content items, a combination of
343
        // itemtype and id is used to guarantee uniqueness across all content items.
344
        $itemtype = self::FAVOURITE_PREFIX . $componentname;
345
 
346
        $ufservice->delete_favourite(self::COMPONENT, $itemtype, $contentitemid, $usercontext);
347
 
348
        $favcache = \cache::make('core', 'user_favourite_course_content_items');
349
        $favcache->delete($user->id);
350
 
351
        $items = $this->get_all_content_items($user);
352
        return $items[array_search($contentitemid, array_column($items, 'id'))];
353
    }
354
 
355
    /**
356
     * Toggle an activity to being recommended or not.
357
     *
358
     * @param  string $itemtype The component such as mod_assign, or assignsubmission_file
359
     * @param  int    $itemid   The id related to this component item.
360
     * @return bool True on creating a favourite, false on deleting it.
361
     */
362
    public function toggle_recommendation(string $itemtype, int $itemid): bool {
363
        global $CFG;
364
 
365
        $context = \context_system::instance();
366
 
367
        $itemtype = self::RECOMMENDATION_PREFIX . $itemtype;
368
 
369
        // Favourites are created using a user context. We'll use the site guest user ID as that should not change and there
370
        // can be only one.
371
        $usercontext = \context_user::instance($CFG->siteguest);
372
 
373
        $recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE);
374
 
375
        $favouritefactory = \core_favourites\service_factory::get_service_for_user_context($usercontext);
376
        if ($favouritefactory->favourite_exists(self::COMPONENT, $itemtype, $itemid, $context)) {
377
            $favouritefactory->delete_favourite(self::COMPONENT, $itemtype, $itemid, $context);
378
            $result = $recommendationcache->delete($CFG->siteguest);
379
            return false;
380
        } else {
381
            $favouritefactory->create_favourite(self::COMPONENT, $itemtype, $itemid, $context);
382
            $result = $recommendationcache->delete($CFG->siteguest);
383
            return true;
384
        }
385
    }
386
}