Skip to content

Commit

Permalink
MDL-53363 tags: helper to search for tagged items; search course modules
Browse files Browse the repository at this point in the history
  • Loading branch information
marinaglancy committed Apr 11, 2016
1 parent b611ade commit 4218ad9
Show file tree
Hide file tree
Showing 6 changed files with 609 additions and 1 deletion.
106 changes: 106 additions & 0 deletions course/lib.php
Expand Up @@ -3917,3 +3917,109 @@ function core_course_inplace_editable($itemtype, $itemid, $newvalue) {
return \core_course\output\course_module_name::update($itemid, $newvalue);
}
}

/**
* Returns course modules tagged with a specified tag ready for output on tag/index.php page
*
* This is a callback used by the tag area core/course_modules to search for course modules
* tagged with a specific tag.
*
* @param core_tag_tag $tag
* @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
* are displayed on the page and the per-page limit may be bigger
* @param int $fromcontextid context id where the link was displayed, may be used by callbacks
* to display items in the same context first
* @param int $contextid context id where to search for records
* @param bool $recursivecontext search in subcontexts as well
* @param int $page 0-based number of page being displayed
* @return \core_tag\output\tagindex
*/
function course_get_tagged_course_modules($tag, $exclusivemode = false, $fromcontextid = 0, $contextid = 0,
$recursivecontext = 1, $page = 0) {
global $OUTPUT;
$perpage = $exclusivemode ? 20 : 5;

// Build select query.
$ctxselect = context_helper::get_preload_record_columns_sql('ctx');
$query = "SELECT cm.id AS cmid, c.id AS courseid, $ctxselect
FROM {course_modules} cm
JOIN {tag_instance} tt ON cm.id = tt.itemid
JOIN {course} c ON cm.course = c.id
JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel
WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid AND tt.component = :component
AND c.id %COURSEFILTER% AND cm.id %ITEMFILTER%";

$params = array('itemtype' => 'course_modules', 'tagid' => $tag->id, 'component' => 'core',
'coursemodulecontextlevel' => CONTEXT_MODULE);
if ($contextid) {
$context = context::instance_by_id($contextid);
$query .= $recursivecontext ? ' AND (ctx.id = :contextid OR ctx.path LIKE :path)' : ' AND ctx.id = :contextid';
$params['contextid'] = $context->id;
$params['path'] = $context->path.'/%';
}

$query .= ' ORDER BY';
if ($fromcontextid) {
// In order-clause specify that modules from inside "fromctx" context should be returned first.
$fromcontext = context::instance_by_id($fromcontextid);
$query .= ' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),';
$params['fromcontextid'] = $fromcontext->id;
$params['frompath'] = $fromcontext->path.'/%';
}
$query .= ' c.sortorder, cm.id';
$totalpages = $page + 1;

// Use core_tag_index_builder to build and filter the list of items.
// Request one item more than we need so we know if next page exists.
$builder = new core_tag_index_builder('core', 'course_modules', $query, $params, $page * $perpage, $perpage + 1);
while ($item = $builder->has_item_that_needs_access_check()) {
context_helper::preload_from_record($item);
$courseid = $item->courseid;
if (!$builder->can_access_course($courseid)) {
$builder->set_accessible($item, false);
continue;
}
$modinfo = get_fast_modinfo($builder->get_course($courseid));
// Set accessibility of this item and all other items in the same course.
$builder->walk(function ($taggeditem) use ($courseid, $modinfo, $builder) {
if ($taggeditem->courseid == $courseid) {
$cm = $modinfo->get_cm($taggeditem->cmid);
$builder->set_accessible($taggeditem, $cm->uservisible);
}
});
}

$items = $builder->get_items();
if (count($items) > $perpage) {
$totalpages = $page + 2; // We don't need exact page count, just indicate that the next page exists.
array_pop($items);
}

// Build the display contents.
if ($items) {
$tagfeed = new core_tag\output\tagfeed();
foreach ($items as $item) {
context_helper::preload_from_record($item);
$course = $builder->get_course($item->courseid);
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->get_cm($item->cmid);
$courseurl = course_get_url($item->courseid, $cm->sectionnum);
$cmname = $cm->get_formatted_name();
if (!$exclusivemode) {
$cmname = shorten_text($cmname, 100);
}
$cmname = html_writer::link($cm->url?:$courseurl, $cmname);
$coursename = format_string($course->fullname, true,
array('context' => context_course::instance($item->courseid)));
$coursename = html_writer::link($courseurl, $coursename);
$icon = html_writer::empty_tag('img', array('src' => $cm->get_icon_url()));
$tagfeed->add($icon, $cmname, $coursename);
}

$content = $OUTPUT->render_from_template('core_tag/tagfeed',
$tagfeed->export_for_template($OUTPUT));

return new core_tag\output\tagindex($tag, 'core', 'course_modules', $content,
$exclusivemode, $fromcontextid, $contextid, $recursivecontext, $page, $totalpages);
}
}
113 changes: 113 additions & 0 deletions course/tests/courselib_test.php
Expand Up @@ -2809,4 +2809,117 @@ public function test_update_module_name_inplace() {
$this->assertEquals('New forum name', $res['value']);
$this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id)));
}

/**
* Testing function course_get_tagged_course_modules - search tagged course modules
*/
public function test_course_get_tagged_course_modules() {
global $DB;
$this->resetAfterTest();
$course3 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course1 = $this->getDataGenerator()->create_course();
$cm11 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
'tags' => 'Cat, Dog'));
$cm12 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
'tags' => 'Cat, Mouse', 'visible' => 0));
$cm13 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
'tags' => 'Cat, Mouse, Dog'));
$cm21 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id,
'tags' => 'Cat, Mouse'));
$cm31 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id,
'tags' => 'Cat, Mouse'));

// Admin is able to view everything.
$this->setAdminUser();
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
$this->assertRegExp('/'.$cm11->name.'/', $res->content);
$this->assertRegExp('/'.$cm12->name.'/', $res->content);
$this->assertRegExp('/'.$cm13->name.'/', $res->content);
$this->assertRegExp('/'.$cm21->name.'/', $res->content);
$this->assertRegExp('/'.$cm31->name.'/', $res->content);
// Results from course1 are returned before results from course2.
$this->assertTrue(strpos($res->content, $cm11->name) < strpos($res->content, $cm21->name));

// Ordinary user is not able to see anything.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);

$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
$this->assertNull($res);

// Enrol user as student in course1 and course2.
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
$this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['student']);
$this->getDataGenerator()->enrol_user($user->id, $course2->id, $roleids['student']);
core_tag_index_builder::reset_caches();

// Searching in the course context returns visible modules in this course.
$context = context_course::instance($course1->id);
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
$this->assertRegExp('/'.$cm11->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm12->name.'/', $res->content);
$this->assertRegExp('/'.$cm13->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm21->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm31->name.'/', $res->content);

// Searching FROM the course context returns visible modules in all courses.
$context = context_course::instance($course2->id);
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
/*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
$this->assertRegExp('/'.$cm11->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm12->name.'/', $res->content);
$this->assertRegExp('/'.$cm13->name.'/', $res->content);
$this->assertRegExp('/'.$cm21->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm31->name.'/', $res->content); // No access to course3.
// Results from course2 are returned before results from course1.
$this->assertTrue(strpos($res->content, $cm21->name) < strpos($res->content, $cm11->name));

// Enrol user in course1 as a teacher - now he should be able to see hidden module.
$this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['editingteacher']);
get_fast_modinfo(0,0,true);

$context = context_course::instance($course1->id);
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
/*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
$this->assertRegExp('/'.$cm12->name.'/', $res->content);

// Create more modules and try pagination.
$cm14 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
'tags' => 'Cat, Dog'));
$cm15 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
'tags' => 'Cat, Mouse', 'visible' => 0));
$cm16 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
'tags' => 'Cat, Mouse, Dog'));

$context = context_course::instance($course1->id);
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
$this->assertRegExp('/'.$cm11->name.'/', $res->content);
$this->assertRegExp('/'.$cm12->name.'/', $res->content);
$this->assertRegExp('/'.$cm13->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm21->name.'/', $res->content);
$this->assertRegExp('/'.$cm14->name.'/', $res->content);
$this->assertRegExp('/'.$cm15->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm16->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm31->name.'/', $res->content); // No access to course3.
$this->assertEmpty($res->prevpageurl);
$this->assertNotEmpty($res->nextpageurl);

$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */1);
$this->assertNotRegExp('/'.$cm11->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm12->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm13->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm21->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm14->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm15->name.'/', $res->content);
$this->assertRegExp('/'.$cm16->name.'/', $res->content);
$this->assertNotRegExp('/'.$cm31->name.'/', $res->content); // No access to course3.
$this->assertNotEmpty($res->prevpageurl);
$this->assertEmpty($res->nextpageurl);
}
}
1 change: 1 addition & 0 deletions lang/en/cache.php
Expand Up @@ -57,6 +57,7 @@
$string['cachedef_observers'] = 'Event observers';
$string['cachedef_plugin_functions'] = 'Plugins available callbacks';
$string['cachedef_plugin_manager'] = 'Plugin info manager';
$string['cachedef_tagindexbuilder'] = 'Search results for tagged items';
$string['cachedef_questiondata'] = 'Question definitions';
$string['cachedef_repositories'] = 'Repositories instances data';
$string['cachedef_grade_categories'] = 'Grade category queries';
Expand Down
15 changes: 14 additions & 1 deletion lib/db/caches.php
Expand Up @@ -277,5 +277,18 @@
'mode' => cache_store::MODE_REQUEST,
'simplekeys' => true,
'simpledata' => true
)
),

// Caches tag index builder results.
'tagindexbuilder' => array(
'mode' => cache_store::MODE_SESSION,
'simplekeys' => true,
'simplevalues' => true,
'staticacceleration' => true,
'staticaccelerationsize' => 10,
'ttl' => 900, // 15 minutes.
'invalidationevents' => array(
'resettagindexbuilder',
),
),
);
2 changes: 2 additions & 0 deletions lib/db/tag.php
Expand Up @@ -83,5 +83,7 @@
array(
'itemtype' => 'course_modules', // Course modules.
'component' => 'core',
'callback' => 'course_get_tagged_course_modules',
'callbackfile' => '/course/lib.php',
),
);

0 comments on commit 4218ad9

Please sign in to comment.