Skip to content

Commit

Permalink
MDL-61363 tag: allow tagging in different context to item
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanwyllie committed Mar 7, 2018
1 parent 8376557 commit 5436b0e
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 9 deletions.
5 changes: 3 additions & 2 deletions lib/db/install.xml
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20171222" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20180222" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
Expand Down Expand Up @@ -2019,6 +2019,7 @@
<FIELD NAME="callback" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="callbackfile" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="showstandard" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="multiplecontexts" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether the tag area allows tag instances to be created in multiple contexts."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down Expand Up @@ -2081,7 +2082,7 @@
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="taggeditem" UNIQUE="true" FIELDS="component, itemtype, itemid, tiuserid, tagid"/>
<INDEX NAME="taggeditem" UNIQUE="true" FIELDS="component, itemtype, itemid, contextid, tiuserid, tagid"/>
<INDEX NAME="taglookup" UNIQUE="false" FIELDS="itemtype, component, tagid, contextid"/>
</INDEXES>
</TABLE>
Expand Down
1 change: 1 addition & 0 deletions lib/db/tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
array(
'itemtype' => 'question', // Questions.
'component' => 'core_question',
'multiplecontexts' => true,
),
array(
'itemtype' => 'post', // Blog posts.
Expand Down
40 changes: 39 additions & 1 deletion lib/db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -1974,13 +1974,51 @@ function xmldb_main_upgrade($oldversion) {
}

if ($oldversion < 2018022800.01) {

// Fix old block configurations that use the deprecated (and now removed) object class.
upgrade_fix_block_instance_configuration();

// Main savepoint reached.
upgrade_main_savepoint(true, 2018022800.01);
}

if ($oldversion < 2018022800.02) {
// Define index taggeditem (unique) to be dropped form tag_instance.
$table = new xmldb_table('tag_instance');
$index = new xmldb_index('taggeditem', XMLDB_INDEX_UNIQUE, array('component',
'itemtype', 'itemid', 'tiuserid', 'tagid'));

// Conditionally launch drop index taggeditem.
if ($dbman->index_exists($table, $index)) {
$dbman->drop_index($table, $index);
}

$index = new xmldb_index('taggeditem', XMLDB_INDEX_UNIQUE, array('component',
'itemtype', 'itemid', 'contextid', 'tiuserid', 'tagid'));

// Conditionally launch add index taggeditem.
if (!$dbman->index_exists($table, $index)) {
$dbman->add_index($table, $index);
}

// Main savepoint reached.
upgrade_main_savepoint(true, 2018022800.02);
}

if ($oldversion < 2018022800.03) {

// Define field multiplecontexts to be added to tag_area.
$table = new xmldb_table('tag_area');
$field = new xmldb_field('multiplecontexts', XMLDB_TYPE_INTEGER, '1', null,
XMLDB_NOTNULL, null, '0', 'showstandard');

// Conditionally launch add field multiplecontexts.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Main savepoint reached.
upgrade_main_savepoint(true, 2018022800.03);
}

return true;
}
35 changes: 33 additions & 2 deletions tag/classes/area.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,32 @@ public static function is_enabled($component, $itemtype) {
return null;
}

/**
* Checks if the tag area allows items to be tagged in multiple different contexts.
*
* If true then it indicates that not all tag instance contexts must match the
* context of the item they are tagging. If false then all tag instance should
* match the context of the item they are tagging.
*
* Example use case for multi-context tagging:
* A question that exists in a course category context may be used by multiple
* child courses. The question tag area can allow tag instances to be created in
* multiple contexts which allows the tag API to tag the question at the course
* category context and then seperately in each of the child course contexts.
*
* @param string $component component responsible for tagging
* @param string $itemtype what is being tagged, for example, 'post', 'course', 'user', etc.
* @return bool
*/
public static function allows_tagging_in_multiple_contexts($component, $itemtype) {
$itemtypes = self::get_areas();
if (isset($itemtypes[$itemtype][$component])) {
$config = $itemtypes[$itemtype][$component];
return isset($config->multiplecontexts) ? $config->multiplecontexts : false;
}
return false;
}

/**
* Returns the id of the tag collection that should be used for storing tags of this itemtype
*
Expand Down Expand Up @@ -217,7 +243,8 @@ protected static function create($record) {
'tagcollid' => $record->tagcollid,
'callback' => $record->callback,
'callbackfile' => $record->callbackfile,
'showstandard' => isset($record->showstandard) ? $record->showstandard : core_tag_tag::BOTH_STANDARD_AND_NOT));
'showstandard' => isset($record->showstandard) ? $record->showstandard : core_tag_tag::BOTH_STANDARD_AND_NOT,
'multiplecontexts' => isset($record->multiplecontexts) ? $record->multiplecontexts : 0));

// Reset cache.
cache::make('core', 'tags')->delete('tag_area');
Expand All @@ -233,7 +260,8 @@ public static function update($existing, $data) {
global $DB;
$data = array_intersect_key((array)$data,
array('enabled' => 1, 'tagcollid' => 1,
'callback' => 1, 'callbackfile' => 1, 'showstandard' => 1));
'callback' => 1, 'callbackfile' => 1, 'showstandard' => 1,
'multiplecontexts' => 1));
foreach ($data as $key => $value) {
if ($existing->$key == $value) {
unset($data[$key]);
Expand Down Expand Up @@ -310,6 +338,9 @@ public static function reset_definitions_for_component($componentname) {
if (!isset($record->callbackfile)) {
$record->callbackfile = null;
}
if (!isset($record->multiplecontexts)) {
$record->multiplecontexts = false;
}
$itemtypes[$record->itemtype . ':' . $record->component] = $record;
}
}
Expand Down
48 changes: 45 additions & 3 deletions tag/classes/tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ protected function add_instance($component, $itemtype, $itemid, context $context
global $DB;
$this->ensure_fields_exist(array('name', 'rawname'), 'add_instance');

$taginstance = new StdClass;
$taginstance = new stdClass;
$taginstance->tagid = $this->id;
$taginstance->component = $component ? $component : '';
$taginstance->itemid = $itemid;
Expand Down Expand Up @@ -773,24 +773,66 @@ public static function set_item_tags($component, $itemtype, $itemid, context $co
$tagobjects = array();
}

$allowmultiplecontexts = core_tag_area::allows_tagging_in_multiple_contexts($component, $itemtype);
$currenttags = static::get_item_tags($component, $itemtype, $itemid, self::BOTH_STANDARD_AND_NOT, $tiuserid);
$taginstanceidstomovecontext = [];

// For data coherence reasons, it's better to remove deleted tags
// before adding new data: ordering could be duplicated.
foreach ($currenttags as $currenttag) {
if (!array_key_exists($currenttag->name, $tagobjects)) {
$hasbeenrequested = array_key_exists($currenttag->name, $tagobjects);
$issamecontext = $currenttag->taginstancecontextid == $context->id;

if ($allowmultiplecontexts) {
// If the tag area allows multiple contexts then we should only be
// managing tags in the given $context. All other tags can be ignored.
$shoulddelete = $issamecontext && !$hasbeenrequested;
} else {
// If the tag area only allows tag instances in a single context then
// all tags that aren't in the requested tags should be deleted, regardless
// of their context, if they are not part of the new set of tags.
$shoulddelete = !$hasbeenrequested;
// If the tag instance isn't in the correct context (legacy data)
// then we should take this opportunity to update it with the correct
// context id.
if (!$shoulddelete && !$issamecontext) {
$currenttag->taginstancecontextid = $context->id;
$taginstanceidstomovecontext[] = $currenttag->taginstanceid;
}
}

if ($shoulddelete) {
$taginstance = (object)array('id' => $currenttag->taginstanceid,
'itemtype' => $itemtype, 'itemid' => $itemid,
'contextid' => $currenttag->taginstancecontextid, 'tiuserid' => $tiuserid);
$currenttag->delete_instance_as_record($taginstance, false);
}
}

if (!empty($taginstanceidstomovecontext)) {
static::change_instances_context($taginstanceidstomovecontext, $context);
}

$ordering = -1;
foreach ($tagobjects as $name => $tag) {
$ordering++;
foreach ($currenttags as $currenttag) {
if (strval($currenttag->name) === strval($name)) {
$namesmatch = strval($currenttag->name) === strval($name);

if ($allowmultiplecontexts) {
// If the tag area allows multiple contexts then we should only
// skip adding a new instance if the existing one is in the correct
// context.
$contextsmatch = $currenttag->taginstancecontextid == $context->id;
$shouldskipinstance = $namesmatch && $contextsmatch;
} else {
// The existing behaviour for single context tag areas is to
// skip adding a new instance regardless of whether the existing
// instance is in the same context as the provided $context.
$shouldskipinstance = $namesmatch;
}

if ($shouldskipinstance) {
if ($currenttag->ordering != $ordering) {
$currenttag->update_instance_ordering($currenttag->taginstanceid, $ordering);
}
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

defined('MOODLE_INTERNAL') || die();

$version = 2018022800.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2018022800.03; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.

Expand Down

0 comments on commit 5436b0e

Please sign in to comment.