mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 12:40:01 +01:00
MDL-67585 core_course: add favouriting to the content item service class
This commit is contained in:
parent
57dfcf951d
commit
f09e9b887e
@ -49,7 +49,7 @@ class course_content_item_exporter extends exporter {
|
||||
public function __construct(content_item $contentitem, array $related = []) {
|
||||
$this->contentitem = $contentitem;
|
||||
|
||||
return parent::__construct($contentitem, $related);
|
||||
return parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,7 +77,7 @@ class course_content_item_exporter extends exporter {
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
// This will hold user-dependant properties such as whether the item is starred or recommended.
|
||||
return [];
|
||||
return ['favourite' => ['type' => PARAM_BOOL, 'description' => 'Has the user favourited the content item']];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,6 +96,17 @@ class course_content_item_exporter extends exporter {
|
||||
* @return array The array of property values, indexed by name.
|
||||
*/
|
||||
protected function get_other_values(\renderer_base $output) {
|
||||
|
||||
$favourite = false;
|
||||
$itemtype = 'contentitem_' . $this->contentitem->get_component_name();
|
||||
if (isset($this->related['favouriteitems'])) {
|
||||
foreach ($this->related['favouriteitems'] as $favobj) {
|
||||
if ($favobj->itemtype === $itemtype && in_array($this->contentitem->get_id(), $favobj->ids)) {
|
||||
$favourite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$properties = [
|
||||
'id' => $this->contentitem->get_id(),
|
||||
'name' => $this->contentitem->get_name(),
|
||||
@ -105,6 +116,7 @@ class course_content_item_exporter extends exporter {
|
||||
'help' => $this->contentitem->get_help(),
|
||||
'archetype' => $this->contentitem->get_archetype(),
|
||||
'componentname' => $this->contentitem->get_component_name(),
|
||||
'favourite' => $favourite
|
||||
];
|
||||
|
||||
return $properties;
|
||||
@ -117,7 +129,8 @@ class course_content_item_exporter extends exporter {
|
||||
*/
|
||||
protected static function define_related(): array {
|
||||
return [
|
||||
'context' => 'context'
|
||||
'context' => '\context',
|
||||
'favouriteitems' => '\stdClass[]?'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class course_content_items_exporter extends exporter {
|
||||
public function __construct(array $contentitems, array $related) {
|
||||
$this->contentitems = $contentitems;
|
||||
|
||||
parent::__construct($contentitems, $related);
|
||||
parent::__construct([], $related);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,7 +78,8 @@ class course_content_items_exporter extends exporter {
|
||||
$exporter = new course_content_item_exporter(
|
||||
$contentitem,
|
||||
[
|
||||
'context' => $this->related['context']
|
||||
'context' => $this->related['context'],
|
||||
'favouriteitems' => $this->related['favouriteitems'],
|
||||
]
|
||||
);
|
||||
return $exporter->export($output);
|
||||
@ -98,7 +99,8 @@ class course_content_items_exporter extends exporter {
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'context' => '\context'
|
||||
'context' => '\context',
|
||||
'favouriteitems' => '\stdClass[]?'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -49,19 +49,75 @@ class content_item_service {
|
||||
$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;
|
||||
}
|
||||
|
||||
// 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[] = 'contentitem_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) {
|
||||
if (component_callback_exists($subpluginname, 'get_course_content_items')) {
|
||||
$itemtypes[] = 'contentitem_' . $subpluginname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($user->id));
|
||||
$favourites = [];
|
||||
foreach ($itemtypes as $itemtype) {
|
||||
$favs = $ufservice->find_favourites_by_type('core_course', $itemtype);
|
||||
$favobj = (object) ['itemtype' => $itemtype, 'ids' => array_column($favs, 'itemid')];
|
||||
$favourites[] = $favobj;
|
||||
}
|
||||
$favcache->set($key, $favourites);
|
||||
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(): array {
|
||||
public function get_all_content_items(\stdClass $user): array {
|
||||
global $PAGE;
|
||||
$allcontentitems = $this->repository->find_all();
|
||||
|
||||
// Export the objects to get the formatted objects for transfer/display.
|
||||
$favourites = $this->get_favourite_content_items_for_user($user);
|
||||
$ciexporter = new course_content_items_exporter(
|
||||
$allcontentitems,
|
||||
['context' => \context_system::instance()]
|
||||
[
|
||||
'context' => \context_system::instance(),
|
||||
'favouriteitems' => $favourites
|
||||
]
|
||||
);
|
||||
$exported = $ciexporter->export($PAGE->get_renderer('core'));
|
||||
|
||||
@ -129,9 +185,13 @@ class content_item_service {
|
||||
}
|
||||
|
||||
// Export the objects to get the formatted objects for transfer/display.
|
||||
$favourites = $this->get_favourite_content_items_for_user($user);
|
||||
$ciexporter = new course_content_items_exporter(
|
||||
$availablecontentitems,
|
||||
['context' => \context_course::instance($course->id)]
|
||||
[
|
||||
'context' => \context_course::instance($course->id),
|
||||
'favouriteitems' => $favourites
|
||||
]
|
||||
);
|
||||
$exported = $ciexporter->export($PAGE->get_renderer('course'));
|
||||
|
||||
@ -142,4 +202,54 @@ class content_item_service {
|
||||
|
||||
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 = 'contentitem_' . $componentname;
|
||||
|
||||
$ufservice->create_favourite('core_course', $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 = 'contentitem_' . $componentname;
|
||||
|
||||
$ufservice->delete_favourite('core_course', $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'))];
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ class services_content_item_service_testcase extends \advanced_testcase {
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
$allcontentitems = $cis->get_all_content_items();
|
||||
$allcontentitems = $cis->get_all_content_items($user);
|
||||
$coursecontentitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
|
||||
// The call to get_all_content_items() should return the same items as for the course,
|
||||
@ -128,9 +128,71 @@ class services_content_item_service_testcase extends \advanced_testcase {
|
||||
assign_capability('mod/lti:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
|
||||
|
||||
// Verify that all items, including lti, are still returned by the get_all_content_items() call.
|
||||
$allcontentitems = $cis->get_all_content_items();
|
||||
$allcontentitems = $cis->get_all_content_items($user);
|
||||
$coursecontentitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$this->assertNotContains('lti', array_column($coursecontentitems, 'name'));
|
||||
$this->assertContains('lti', array_column($allcontentitems, 'name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test confirming that a content item can be added to a user's favourites.
|
||||
*/
|
||||
public function test_add_to_user_favourites() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
|
||||
// Grab a the assign content item, which we'll favourite for the user.
|
||||
$items = $cis->get_all_content_items($user);
|
||||
$assign = $items[array_search('assign', array_column($items, 'name'))];
|
||||
$contentitem = $cis->add_to_user_favourites($user, 'mod_assign', $assign->id);
|
||||
|
||||
// Verify the exported result is marked as a favourite.
|
||||
$this->assertEquals('assign', $contentitem->name);
|
||||
$this->assertTrue($contentitem->favourite);
|
||||
|
||||
// Verify the item is marked as a favourite when returned from the other service methods.
|
||||
$allitems = $cis->get_all_content_items($user);
|
||||
$allitemsassign = $allitems[array_search('assign', array_column($allitems, 'name'))];
|
||||
|
||||
$courseitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$courseitemsassign = $courseitems[array_search('assign', array_column($courseitems, 'name'))];
|
||||
$this->assertTrue($allitemsassign->favourite);
|
||||
$this->assertTrue($courseitemsassign->favourite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test verifying that content items can be removed from a user's favourites.
|
||||
*/
|
||||
public function test_remove_from_user_favourites() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a user in a course.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
||||
$cis = new content_item_service(new content_item_readonly_repository());
|
||||
|
||||
// Grab a the assign content item, which we'll favourite for the user.
|
||||
$items = $cis->get_all_content_items($user);
|
||||
$assign = $items[array_search('assign', array_column($items, 'name'))];
|
||||
$cis->add_to_user_favourites($user, 'mod_assign', $assign->id);
|
||||
|
||||
// Now, remove the favourite, and verify it.
|
||||
$contentitem = $cis->remove_from_user_favourites($user, 'mod_assign', $assign->id);
|
||||
|
||||
// Verify the exported result is not marked as a favourite.
|
||||
$this->assertEquals('assign', $contentitem->name);
|
||||
$this->assertFalse($contentitem->favourite);
|
||||
|
||||
// Verify the item is not marked as a favourite when returned from the other service methods.
|
||||
$allitems = $cis->get_all_content_items($user);
|
||||
$allitemsassign = $allitems[array_search('assign', array_column($allitems, 'name'))];
|
||||
$courseitems = $cis->get_content_items_for_user_in_course($user, $course);
|
||||
$courseitemsassign = $courseitems[array_search('assign', array_column($courseitems, 'name'))];
|
||||
$this->assertFalse($allitemsassign->favourite);
|
||||
$this->assertFalse($courseitemsassign->favourite);
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ $string['cachedef_string'] = 'Language string cache';
|
||||
$string['cachedef_tags'] = 'Tags collections and areas';
|
||||
$string['cachedef_temp_tables'] = 'Temporary tables cache';
|
||||
$string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
|
||||
$string['cachedef_user_favourite_course_content_items'] = 'User\'s favourite content items (activities, resources and their subtypes)';
|
||||
$string['cachedef_user_group_groupings'] = 'User\'s groupings and groups per course';
|
||||
$string['cachedef_user_course_content_items'] = 'User\'s content items (activities, resources and their subtypes) per course';
|
||||
$string['cachedef_yuimodules'] = 'YUI Module definitions';
|
||||
|
@ -421,4 +421,10 @@ $definitions = array(
|
||||
'mode' => cache_store::MODE_REQUEST,
|
||||
'simplekeys' => true,
|
||||
],
|
||||
|
||||
// The list of favourited content items (activities, resources and their subtypes) for a user.
|
||||
'user_favourite_course_content_items' => [
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true,
|
||||
],
|
||||
);
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2020021400.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2020021400.02; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '3.9dev (Build: 20200214)'; // Human-friendly version name
|
||||
|
Loading…
x
Reference in New Issue
Block a user