/course/classes/local/service/content_item_service.php
PHP | 391 lines | 193 code | 54 blank | 144 comment | 15 complexity | 7ab3a337b73b2d48cf4e3c02b0159186 MD5 | raw file
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * Contains the content_item_service class.
- *
- * @package core
- * @subpackage course
- * @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- namespace core_course\local\service;
- defined('MOODLE_INTERNAL') || die();
- use core_course\local\exporters\course_content_items_exporter;
- use core_course\local\repository\content_item_readonly_repository_interface;
- /**
- * The content_item_service class, providing the api for interacting with content items.
- *
- * @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class content_item_service {
- /** @var content_item_readonly_repository_interface $repository a repository for content items. */
- private $repository;
- /** string the component for this favourite. */
- public const COMPONENT = 'core_course';
- /** string the favourite prefix itemtype in the favourites table. */
- public const FAVOURITE_PREFIX = 'contentitem_';
- /** string the recommendation prefix itemtype in the favourites table. */
- public const RECOMMENDATION_PREFIX = 'recommend_';
- /** string the cache name for recommendations. */
- public const RECOMMENDATION_CACHE = 'recommendation_favourite_course_content_items';
- /**
- * The content_item_service constructor.
- *
- * @param content_item_readonly_repository_interface $repository a content item repository.
- */
- public function __construct(content_item_readonly_repository_interface $repository) {
- $this->repository = $repository;
- }
- /**
- * Returns an array of objects representing favourited content items.
- *
- * Each object contains the following properties:
- * itemtype: a string containing the 'itemtype' key used by the favourites subsystem.
- * ids[]: an array of ids, representing the content items within a component.
- *
- * Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness.
- *
- * @param \stdClass $user
- * @return array
- */
- private function get_favourite_content_items_for_user(\stdClass $user): array {
- $favcache = \cache::make('core', 'user_favourite_course_content_items');
- $key = $user->id;
- $favmods = $favcache->get($key);
- if ($favmods !== false) {
- return $favmods;
- }
- $favourites = $this->get_content_favourites(self::FAVOURITE_PREFIX, \context_user::instance($user->id));
- $favcache->set($key, $favourites);
- return $favourites;
- }
- /**
- * Returns an array of objects representing recommended content items.
- *
- * Each object contains the following properties:
- * itemtype: a string containing the 'itemtype' key used by the favourites subsystem.
- * ids[]: an array of ids, representing the content items within a component.
- *
- * Since two components can return (via their hook implementation) the same id, the itemtype is used for uniqueness.
- *
- * @return array
- */
- private function get_recommendations(): array {
- global $CFG;
- $recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE);
- $key = $CFG->siteguest;
- $favmods = $recommendationcache->get($key);
- if ($favmods !== false) {
- return $favmods;
- }
- // Make sure the guest user exists in the database.
- if (!\core_user::get_user($CFG->siteguest)) {
- throw new \coding_exception('The guest user does not exist in the database.');
- }
- // Make sure the guest user context exists.
- if (!$guestusercontext = \context_user::instance($CFG->siteguest, false)) {
- throw new \coding_exception('The guest user context does not exist.');
- }
- $favourites = $this->get_content_favourites(self::RECOMMENDATION_PREFIX, $guestusercontext);
- $recommendationcache->set($CFG->siteguest, $favourites);
- return $favourites;
- }
- /**
- * Gets content favourites from the favourites system depending on the area.
- *
- * @param string $prefix Prefix for the item type.
- * @param \context_user $usercontext User context for the favourite
- * @return array An array of favourite objects.
- */
- private function get_content_favourites(string $prefix, \context_user $usercontext): array {
- // Get all modules and any submodules which implement get_course_content_items() hook.
- // This gives us the set of all itemtypes which we'll use to register favourite content items.
- // The ids that each plugin returns will be used together with the itemtype to uniquely identify
- // each content item for favouriting.
- $pluginmanager = \core_plugin_manager::instance();
- $plugins = $pluginmanager->get_plugins_of_type('mod');
- $itemtypes = [];
- foreach ($plugins as $plugin) {
- // Add the mod itself.
- $itemtypes[] = $prefix . 'mod_' . $plugin->name;
- // Add any subplugins to the list of item types.
- $subplugins = $pluginmanager->get_subplugins_of_plugin('mod_' . $plugin->name);
- foreach ($subplugins as $subpluginname => $subplugininfo) {
- try {
- if (component_callback_exists($subpluginname, 'get_course_content_items')) {
- $itemtypes[] = $prefix . $subpluginname;
- }
- } catch (\moodle_exception $e) {
- debugging('Cannot get_course_content_items: ' . $e->getMessage(), DEBUG_DEVELOPER);
- }
- }
- }
- $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- $favourites = [];
- $favs = $ufservice->find_all_favourites(self::COMPONENT, $itemtypes);
- $favsreduced = array_reduce($favs, function($carry, $item) {
- $carry[$item->itemtype][$item->itemid] = 0;
- return $carry;
- }, []);
- foreach ($itemtypes as $type) {
- $favourites[] = (object) [
- 'itemtype' => $type,
- 'ids' => isset($favsreduced[$type]) ? array_keys($favsreduced[$type]) : []
- ];
- }
- return $favourites;
- }
- /**
- * Get all content items which may be added to courses, irrespective of course caps, for site admin views, etc.
- *
- * @param \stdClass $user the user object.
- * @return array the array of exported content items.
- */
- public function get_all_content_items(\stdClass $user): array {
- $allcontentitems = $this->repository->find_all();
- return $this->export_content_items($user, $allcontentitems);
- }
- /**
- * Get content items which name matches a certain pattern and may be added to courses,
- * irrespective of course caps, for site admin views, etc.
- *
- * @param \stdClass $user The user object.
- * @param string $pattern The search pattern.
- * @return array The array of exported content items.
- */
- public function get_content_items_by_name_pattern(\stdClass $user, string $pattern): array {
- $allcontentitems = $this->repository->find_all();
- $filteredcontentitems = array_filter($allcontentitems, function($contentitem) use ($pattern) {
- return preg_match("/$pattern/i", $contentitem->get_title()->get_value());
- });
- return $this->export_content_items($user, $filteredcontentitems);
- }
- /**
- * Export content items.
- *
- * @param \stdClass $user The user object.
- * @param array $contentitems The content items array.
- * @return array The array of exported content items.
- */
- private function export_content_items(\stdClass $user, $contentitems) {
- global $PAGE;
- // Export the objects to get the formatted objects for transfer/display.
- $favourites = $this->get_favourite_content_items_for_user($user);
- $recommendations = $this->get_recommendations();
- $ciexporter = new course_content_items_exporter(
- $contentitems,
- [
- 'context' => \context_system::instance(),
- 'favouriteitems' => $favourites,
- 'recommended' => $recommendations
- ]
- );
- $exported = $ciexporter->export($PAGE->get_renderer('core'));
- // Sort by title for return.
- usort($exported->content_items, function($a, $b) {
- return strcmp($a->title, $b->title);
- });
- return $exported->content_items;
- }
- /**
- * Return a representation of the available content items, for a user in a course.
- *
- * @param \stdClass $user the user to check access for.
- * @param \stdClass $course the course to scope the content items to.
- * @param array $linkparams the desired section to return to.
- * @return \stdClass[] the content items, scoped to a course.
- */
- public function get_content_items_for_user_in_course(\stdClass $user, \stdClass $course, array $linkparams = []): array {
- global $PAGE;
- if (!has_capability('moodle/course:manageactivities', \context_course::instance($course->id), $user)) {
- return [];
- }
- // Get all the visible content items.
- $allcontentitems = $this->repository->find_all_for_course($course, $user);
- // Content items can only originate from modules or submodules.
- $pluginmanager = \core_plugin_manager::instance();
- $components = \core_component::get_component_list();
- $parents = [];
- foreach ($allcontentitems as $contentitem) {
- if (!in_array($contentitem->get_component_name(), array_keys($components['mod']))) {
- // It could be a subplugin.
- $info = $pluginmanager->get_plugin_info($contentitem->get_component_name());
- if (!is_null($info)) {
- $parent = $info->get_parent_plugin();
- if ($parent != false) {
- if (in_array($parent, array_keys($components['mod']))) {
- $parents[$contentitem->get_component_name()] = $parent;
- continue;
- }
- }
- }
- throw new \moodle_exception('Only modules and submodules can generate content items. \''
- . $contentitem->get_component_name() . '\' is neither.');
- }
- $parents[$contentitem->get_component_name()] = $contentitem->get_component_name();
- }
- // Now, check access to these items for the user.
- $availablecontentitems = array_filter($allcontentitems, function($contentitem) use ($course, $user, $parents) {
- // Check the parent module access for the user.
- return course_allowed_module($course, explode('_', $parents[$contentitem->get_component_name()])[1], $user);
- });
- // Add the link params to the link, if any have been provided.
- if (!empty($linkparams)) {
- $availablecontentitems = array_map(function ($item) use ($linkparams) {
- $item->get_link()->params($linkparams);
- return $item;
- }, $availablecontentitems);
- }
- // Export the objects to get the formatted objects for transfer/display.
- $favourites = $this->get_favourite_content_items_for_user($user);
- $recommended = $this->get_recommendations();
- $ciexporter = new course_content_items_exporter(
- $availablecontentitems,
- [
- 'context' => \context_course::instance($course->id),
- 'favouriteitems' => $favourites,
- 'recommended' => $recommended
- ]
- );
- $exported = $ciexporter->export($PAGE->get_renderer('course'));
- // Sort by title for return.
- usort($exported->content_items, function($a, $b) {
- return strcmp($a->title, $b->title);
- });
- return $exported->content_items;
- }
- /**
- * Add a content item to a user's favourites.
- *
- * @param \stdClass $user the user whose favourite this is.
- * @param string $componentname the name of the component from which the content item originates.
- * @param int $contentitemid the id of the content item.
- * @return \stdClass the exported content item.
- */
- public function add_to_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
- $usercontext = \context_user::instance($user->id);
- $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- // Because each plugin decides its own ids for content items, a combination of
- // itemtype and id is used to guarantee uniqueness across all content items.
- $itemtype = self::FAVOURITE_PREFIX . $componentname;
- $ufservice->create_favourite(self::COMPONENT, $itemtype, $contentitemid, $usercontext);
- $favcache = \cache::make('core', 'user_favourite_course_content_items');
- $favcache->delete($user->id);
- $items = $this->get_all_content_items($user);
- return $items[array_search($contentitemid, array_column($items, 'id'))];
- }
- /**
- * Remove the content item from a user's favourites.
- *
- * @param \stdClass $user the user whose favourite this is.
- * @param string $componentname the name of the component from which the content item originates.
- * @param int $contentitemid the id of the content item.
- * @return \stdClass the exported content item.
- */
- public function remove_from_user_favourites(\stdClass $user, string $componentname, int $contentitemid): \stdClass {
- $usercontext = \context_user::instance($user->id);
- $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- // Because each plugin decides its own ids for content items, a combination of
- // itemtype and id is used to guarantee uniqueness across all content items.
- $itemtype = self::FAVOURITE_PREFIX . $componentname;
- $ufservice->delete_favourite(self::COMPONENT, $itemtype, $contentitemid, $usercontext);
- $favcache = \cache::make('core', 'user_favourite_course_content_items');
- $favcache->delete($user->id);
- $items = $this->get_all_content_items($user);
- return $items[array_search($contentitemid, array_column($items, 'id'))];
- }
- /**
- * Toggle an activity to being recommended or not.
- *
- * @param string $itemtype The component such as mod_assign, or assignsubmission_file
- * @param int $itemid The id related to this component item.
- * @return bool True on creating a favourite, false on deleting it.
- */
- public function toggle_recommendation(string $itemtype, int $itemid): bool {
- global $CFG;
- $context = \context_system::instance();
- $itemtype = self::RECOMMENDATION_PREFIX . $itemtype;
- // Favourites are created using a user context. We'll use the site guest user ID as that should not change and there
- // can be only one.
- $usercontext = \context_user::instance($CFG->siteguest);
- $recommendationcache = \cache::make('core', self::RECOMMENDATION_CACHE);
- $favouritefactory = \core_favourites\service_factory::get_service_for_user_context($usercontext);
- if ($favouritefactory->favourite_exists(self::COMPONENT, $itemtype, $itemid, $context)) {
- $favouritefactory->delete_favourite(self::COMPONENT, $itemtype, $itemid, $context);
- $result = $recommendationcache->delete($CFG->siteguest);
- return false;
- } else {
- $favouritefactory->create_favourite(self::COMPONENT, $itemtype, $itemid, $context);
- $result = $recommendationcache->delete($CFG->siteguest);
- return true;
- }
- }
- }