Skip to content

Commit

Permalink
MDL-67585 core_course: add hook get_all_content_items
Browse files Browse the repository at this point in the history
Returns all content items which are provided by the plugin, irrespective
of whether or not a user can see an item in a particular course. This is
used to generate a global list of content items, allowing for admin
level features to be added.
  • Loading branch information
snake committed Feb 20, 2020
1 parent 520add1 commit 57dfcf9
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,13 @@ public function find_all_for_course(\stdClass $course, \stdClass $user): array {
$this->cachestore->set($key, $contentitems);
return $contentitems;
}

/**
* Find all the content items made available by core and plugins.
*
* @return array
*/
public function find_all(): array {
return $this->contentitemrepository->find_all();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,28 @@ private function get_subplugin_course_content_items(string $parentpluginname, co
return $contentitems;
}

/**
* Get all the content items for a subplugin.
*
* @param string $parentpluginname
* @param content_item $modulecontentitem
* @return array
*/
private function get_subplugin_all_content_items(string $parentpluginname, content_item $modulecontentitem): array {
$contentitems = [];
$pluginmanager = \core_plugin_manager::instance();
foreach ($pluginmanager->get_subplugins_of_plugin($parentpluginname) as $subpluginname => $subplugin) {
// Call the hook, but with a copy of the module content item data.
$spcontentitems = component_callback($subpluginname, 'get_all_content_items', [$modulecontentitem], null);
if (!is_null($spcontentitems)) {
foreach ($spcontentitems as $spcontentitem) {
$contentitems[] = $spcontentitem;
}
}
}
return $contentitems;
}

/**
* Helper to make sure any legacy items have certain properties, which, if missing are inherited from the parent module item.
*
Expand All @@ -155,6 +177,60 @@ private function legacy_item_inherit_missing(\stdClass $legacyitem, content_item
return $legacyitem;
}

/**
* Find all the available content items, not restricted to course or user.
*
* @return array the array of content items.
*/
public function find_all(): array {
global $OUTPUT, $DB;

// Get all modules so we know which plugins are enabled and able to add content.
// Only module plugins may add content items.
$modules = $DB->get_records('modules', ['visible' => 1]);
$return = [];

// Now, generate the content_items.
foreach ($modules as $modid => $mod) {
// Create the content item for the module itself.
// If the module chooses to implement the hook, this may be thrown away.
$help = $this->get_core_module_help_string($mod->name);
$archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);

$contentitem = new content_item(
$mod->id,
$mod->name,
new lang_string_title("modulename", $mod->name),
new \moodle_url(''), // No course scope, so just an empty link.
$OUTPUT->pix_icon('icon', '', $mod->name, ['class' => 'icon']),
$help,
$archetype,
'mod_' . $mod->name
);

$modcontentitemreference = clone($contentitem);

if (component_callback_exists('mod_' . $mod->name, 'get_all_content_items')) {
// Call the module hooks for this module.
$plugincontentitems = component_callback('mod_' . $mod->name, 'get_all_content_items',
[$modcontentitemreference], []);
if (!empty($plugincontentitems)) {
array_push($return, ...$plugincontentitems);
}

// Now, get those for subplugins of the module.
$subplugincontentitems = $this->get_subplugin_all_content_items('mod_' . $mod->name, $modcontentitemreference);
if (!empty($subplugincontentitems)) {
array_push($return, ...$subplugincontentitems);
}
} else {
// Neither callback was found, so just use the default module content item.
$return[] = $contentitem;
}
}
return $return;
}

/**
* Get the list of potential content items for the given course.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ interface content_item_readonly_repository_interface {
* @return array the array of content items.
*/
public function find_all_for_course(\stdClass $course, \stdClass $user): array;

/**
* Find all content items that can be presented, irrespective of course.
*
* @return array the array of content items.
*/
public function find_all(): array;
}
24 changes: 24 additions & 0 deletions course/classes/local/service/content_item_service.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ public function __construct(content_item_readonly_repository_interface $reposito
$this->repository = $repository;
}

/**
* Get all content items which may be added to courses, irrespective of course caps, for site admin views, etc.
*
* @return array the array of exported content items.
*/
public function get_all_content_items(): array {
global $PAGE;
$allcontentitems = $this->repository->find_all();

// Export the objects to get the formatted objects for transfer/display.
$ciexporter = new course_content_items_exporter(
$allcontentitems,
['context' => \context_system::instance()]
);
$exported = $ciexporter->export($PAGE->get_renderer('core'));

// Sort by title for return.
usort($exported->content_items, function($a, $b) {
return $a->title > $b->title;
});

return $exported->content_items;
}

/**
* Return a representation of the available content items, for a user in a course.
*
Expand Down
29 changes: 29 additions & 0 deletions course/tests/content_item_readonly_repository_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,33 @@ public function test_find_all_for_course_hidden_module() {
$items = $cir->find_all_for_course($course, $user);
$this->assertArrayNotHasKey($module->name, $items);
}

/**
* Test confirming that all content items can be fetched, even those which require certain caps when in a course.
*/
public function test_find_all() {
$this->resetAfterTest();
global $DB;

// We'll compare our results to those which are course-specific.
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
assign_capability('mod/lti:addmanualinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
$cir = new content_item_readonly_repository();

// Course specific - lti won't be returned as the user doesn't have the required cap.
$forcourse = $cir->find_all_for_course($course, $user);
$forcourse = array_filter($forcourse, function($contentitem) {
return $contentitem->get_name() === 'lti';
});
$this->assertEmpty($forcourse);

// All - all items are returned, including lti.
$all = $cir->find_all();
$all = array_filter($all, function($contentitem) {
return $contentitem->get_name() === 'lti';
});
$this->assertCount(1, $all);
}
}
30 changes: 30 additions & 0 deletions course/tests/services_content_item_service_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,34 @@ public function test_get_content_item_for_user_in_course_link_params() {
$this->assertStringContainsString('sr=7', $item->link);
}
}

/**
* Test confirming that all content items can be fetched irrespective of permissions.
*/
public function test_get_all_content_items() {
$this->resetAfterTest();
global $DB;

// 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());
$allcontentitems = $cis->get_all_content_items();
$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,
// given the user in an editing teacher and can add manual lti instances.
$this->assertEquals(array_column($allcontentitems, 'name'), array_column($coursecontentitems, 'name'));

// Now removing the cap 'mod/lti:addinstance'. This will restrict those items returned by the course-specific method.
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
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();
$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'));
}
}
61 changes: 61 additions & 0 deletions mod/lti/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,67 @@ function lti_get_course_content_items(\core_course\local\entity\content_item $de
return $types;
}

/**
* Return all content items which can be added to any course.
*
* @param \core_course\local\entity\content_item $defaultmodulecontentitem
* @return array the array of content items.
*/
function mod_lti_get_all_content_items(\core_course\local\entity\content_item $defaultmodulecontentitem): array {
global $OUTPUT, $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php'); // For access to constants.

// The 'External tool' entry (the main module content item), should always take the id of 1.
$types = [new \core_course\local\entity\content_item(
1,
$defaultmodulecontentitem->get_name(),
$defaultmodulecontentitem->get_title(),
$defaultmodulecontentitem->get_link(),
$defaultmodulecontentitem->get_icon(),
$defaultmodulecontentitem->get_help(),
$defaultmodulecontentitem->get_archetype(),
$defaultmodulecontentitem->get_component_name()
)];

foreach (lti_get_lti_types() as $ltitype) {
if ($ltitype->coursevisible != LTI_COURSEVISIBLE_ACTIVITYCHOOSER) {
continue;
}
$type = new stdClass();
$type->id = $ltitype->id;
$type->modclass = MOD_CLASS_ACTIVITY;
$type->name = 'lti_type_' . $ltitype->id;
// Clean the name. We don't want tags here.
$type->title = clean_param($ltitype->name, PARAM_NOTAGS);
$trimmeddescription = trim($ltitype->description);
$type->help = '';
if ($trimmeddescription != '') {
// Clean the description. We don't want tags here.
$type->help = clean_param($trimmeddescription, PARAM_NOTAGS);
$type->helplink = get_string('modulename_shortcut_link', 'lti');
}
if (empty($ltitype->icon)) {
$type->icon = $OUTPUT->pix_icon('icon', '', 'lti', array('class' => 'icon'));
} else {
$type->icon = html_writer::empty_tag('img', array('src' => $ltitype->icon, 'alt' => $ltitype->name, 'class' => 'icon'));
}
$type->link = new moodle_url('/course/modedit.php', array('add' => 'lti', 'return' => 0, 'typeid' => $ltitype->id));

$types[] = new \core_course\local\entity\content_item(
$type->id + 1,
$type->name,
new \core_course\local\entity\string_title($type->title),
$type->link,
$type->icon,
$type->help,
$defaultmodulecontentitem->get_archetype(),
$defaultmodulecontentitem->get_component_name()
);
}

return $types;
}

/**
* Given a coursemodule object, this function returns the extra
* information needed to print this activity in various places.
Expand Down
15 changes: 15 additions & 0 deletions mod/lti/tests/lib_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -437,5 +437,20 @@ public function test_content_item_callbacks() {
$this->assertContains($sitetoolrecord->id + 1, $ids);
$this->assertContains($course2toolrecord->id + 1, $ids);
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);

// When fetching all content items, we expect to see all items available in activity choosers (in any course),
// plus the default module content item ('external tool').
$this->setAdminUser();
$allitems = mod_lti_get_all_content_items($defaultmodulecontentitem);
$this->assertCount(4, $allitems);
$ids = [];
foreach ($allitems as $item) {
$ids[] = $item->get_id();
}
$this->assertContains(1, $ids);
$this->assertContains($sitetoolrecord->id + 1, $ids);
$this->assertContains($course1toolrecord->id + 1, $ids);
$this->assertContains($course2toolrecord->id + 1, $ids);
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
}
}

0 comments on commit 57dfcf9

Please sign in to comment.