Proyectos de Subversion Moodle

Rev

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