Commit c026a28d authored by Marina Glancy's avatar Marina Glancy
Browse files

MDL-50851 core_tag: introduce tag collections

parent e65dfd9f
......@@ -1648,7 +1648,6 @@ function course_delete_module($cmid) {
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/blog/lib.php');
require_once($CFG->dirroot.'/calendar/lib.php');
require_once($CFG->dirroot.'/tag/lib.php');
// Get the course module.
if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
......@@ -1717,7 +1716,7 @@ function course_delete_module($cmid) {
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
// Delete all tag instances associated with the instance of this module.
tag_delete_instances('mod_' . $modulename, $modcontext->id);
core_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
// Delete the context.
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
......
......@@ -60,6 +60,7 @@ $string['cachedef_plugin_manager'] = 'Plugin info manager';
$string['cachedef_questiondata'] = 'Question definitions';
$string['cachedef_repositories'] = 'Repositories instances data';
$string['cachedef_string'] = 'Language string cache';
$string['cachedef_tags'] = 'Tags collections and areas';
$string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
$string['cachedef_yuimodules'] = 'YUI Module definitions';
$string['cachelock_file_default'] = 'Default file locking';
......
......@@ -20,3 +20,4 @@ updated,core_tag
withselectedtags,core_tag
tag:create,core_role
categoriesanditems,core_grades
taggedwith,core_tag
......@@ -421,6 +421,7 @@ $string['submissionoutofsequencefriendlymessage'] = "You have entered data outsi
$string['submit'] = 'Submit';
$string['submitandfinish'] = 'Submit and finish';
$string['submitted'] = 'Submit: {$a}';
$string['tagarea_question'] = 'Questions';
$string['technicalinfo'] = 'Technical information';
$string['technicalinfo_help'] = 'This technical information is probably only useful for developers working on new question types. It may also be helpful when trying to diagnose problems with questions.';
$string['technicalinfominfraction'] = 'Minimum fraction: {$a}';
......
......@@ -24,12 +24,19 @@
$string['added'] = 'Official tag(s) added';
$string['addotags'] = 'Add official tags';
$string['addtagcoll'] = 'Add tag collection';
$string['addtagtomyinterests'] = 'Add "{$a}" to my interests';
$string['alltagpages'] = 'All tag pages';
$string['backtoallitems'] = 'Back to all items tagged with "{$a}"';
$string['changessaved'] = 'Changes saved';
$string['changetagcoll'] = 'Change tag collection of area {$a}';
$string['collnameexplained'] = 'Leave the field empty to use the default value: {$a}';
$string['component'] = 'Component';
$string['confirmdeletetag'] = 'Are you sure you want to delete this tag?';
$string['confirmdeletetags'] = 'Are you sure you want to delete selected tags?';
$string['count'] = 'Count';
$string['coursetags'] = 'Course tags';
$string['defautltagcoll'] = 'Default collection';
$string['delete'] = 'Delete';
$string['deleteselected'] = 'Delete selected';
$string['deleted'] = 'Tag(s) deleted';
......@@ -38,15 +45,20 @@ $string['description'] = 'Description';
$string['editname'] = 'Edit tag name';
$string['edittag'] = 'Edit this tag';
$string['entertags'] = 'Enter tags...';
$string['edittagcoll'] = 'Edit tag collection {$a}';
$string['errortagfrontpage'] = 'Tagging the site main page is not allowed';
$string['errorupdatingrecord'] = 'Error updating tag record';
$string['eventtagadded'] = 'Tag added to an item';
$string['eventtagcolldeleted'] = 'Tag collection deleted';
$string['eventtagcollcreated'] = 'Tag collection created';
$string['eventtagcollupdated'] = 'Tag collection updatedhp ';
$string['eventtagcreated'] = 'Tag created';
$string['eventtagdeleted'] = 'Tag deleted';
$string['eventtagflagged'] = 'Tag flagged';
$string['eventtagremoved'] = 'Tag removed from an item';
$string['eventtagunflagged'] = 'Tag unflagged';
$string['eventtagupdated'] = 'Tag updated';
$string['exclusivemode'] = 'Show only tagged {$a->tagarea}';
$string['flag'] = 'Flag';
$string['flagged'] = 'Tag flagged';
$string['flagasinappropriate'] = 'Flag as inappropriate';
......@@ -54,17 +66,25 @@ $string['helprelatedtags'] = 'Comma separated related tags';
$string['changename'] = 'Change tag name';
$string['changetype'] = 'Change tag type';
$string['id'] = 'id';
$string['inalltagcoll'] = 'Everywhere';
$string['itemstaggedwith'] = '{$a->tagarea} tagged with "{$a->tag}"';
$string['lesstags'] = 'less...';
$string['manageofficialtags'] = 'Manage official tags';
$string['managetags'] = 'Manage tags';
$string['managetagcolls'] = 'Manage tag collections';
$string['moretags'] = 'more...';
$string['name'] = 'Tag name';
$string['namesalreadybeeingused'] = 'Tag names already being used';
$string['newnamefor'] = 'New name for tag {$a}';
$string['nextpage'] = 'More';
$string['notagsfound'] = 'No tags matching "{$a}" found';
$string['noresultsfor'] = 'No results for "{$a}"';
$string['nothingtoupdate'] = 'Nothing to update';
$string['officialtag'] = 'Official';
$string['otags'] = 'Official tags';
$string['othertags'] = 'Other tags';
$string['owner'] = 'Owner';
$string['prevpage'] = 'Back';
$string['ptags'] = 'User defined tags (Comma separated)';
$string['relatedblogs'] = 'Most recent blog entries';
$string['relatedtags'] = 'Related tags';
......@@ -75,16 +95,29 @@ $string['responsiblewillbenotified'] = 'The person responsible will be notified'
$string['rssdesc'] = 'This RSS feed was automatically generated by Moodle and contains user generated tags for courses.';
$string['rsstitle'] = 'Course tags RSS feed for user: {$a}';
$string['search'] = 'Search';
$string['searchable'] = 'Searchable';
$string['searchable_help'] = 'Tags in this tag collection can be searched for on "Search tags" page. If unchecked, tags can still be accessed by clicking on them or via different search interfaces.';
$string['searchresultsfor'] = 'Search results for "{$a}"';
$string['searchtags'] = 'Search tags';
$string['seeallblogs'] = 'See all blog entries tagged with "{$a}"...';
$string['seeallblogs'] = 'See all blog entries tagged with "{$a}"';
$string['select'] = 'Select';
$string['selectcoll'] = 'Select tag collection';
$string['selecttag'] = 'Select tag {$a}';
$string['settypedefault'] = 'Remove from official tags';
$string['settypeofficial'] = 'Make official';
$string['showingfirsttags'] = 'Showing {$a} most popular tags';
$string['suredeletecoll'] = 'Are you sure you want to delete tag collection "{$a}"?';
$string['tag'] = 'Tag';
$string['tagarea_blog_external'] = 'External blog posts';
$string['tagarea_post'] = 'Blog posts';
$string['tagarea_user'] = 'User interests';
$string['tagarea_course'] = 'Courses';
$string['tagareaenabled'] = 'Enabled';
$string['tagareaname'] = 'Name';
$string['tagareas'] = 'Tag areas';
$string['tagcollection'] = 'Tag collection';
$string['tagcollections'] = 'Tag collections';
$string['tagdescription'] = 'Tag description';
$string['taggedwith'] = 'tagged with "{$a}"';
$string['tags'] = 'Tags';
$string['tagsaredisabled'] = 'Tags are disabled';
$string['tagtype'] = 'Tag type';
......@@ -107,3 +140,7 @@ $string['tagtype_official'] = 'Official';
$string['thistaghasnodesc'] = 'This tag currently has no description.';
$string['updated'] = 'Updated';
$string['withselectedtags'] = 'With selected tags...';
// Deprecated since 3.1 .
$string['taggedwith'] = 'tagged with "{$a}"';
......@@ -166,9 +166,8 @@ function uninstall_plugin($type, $name) {
echo $OUTPUT->heading($pluginname);
// Delete all tag instances associated with this plugin.
require_once($CFG->dirroot . '/tag/lib.php');
tag_delete_instances($component);
// Delete all tag areas, collections and instances associated with this plugin.
core_tag_area::uninstall($component);
// Custom plugin uninstall.
$plugindirectory = core_component::get_plugin_directory($type, $name);
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -27,9 +27,37 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
return /** @alias module:core/tag */ {
/**
* Initialises handlers for AJAX methods.
* Initialises tag index page.
*
* @method init
* @method init_tagindex_page
*/
init_tagindex_page: function() {
// Click handler for changing tag type.
$('body').delegate('.tagarea[data-ta] a[data-quickload=1]', 'click', function(e) {
e.preventDefault();
var target = $( this ),
query = target.context.search.replace(/^\?/, ''),
tagarea = target.closest('.tagarea[data-ta]'),
args = query.split('&').reduce(function(s,c){var t=c.split('=');s[t[0]]=decodeURIComponent(t[1]);return s;},{});
var promises = ajax.call([{
methodname: 'core_tag_get_tagindex',
args: { tagindex: args }
}], true);
$.when.apply($, promises)
.done( function(data) {
templates.render('core_tag/index', data).done(function(html) {
tagarea.replaceWith(html);
});
});
});
},
/**
* Initialises tag management page.
*
* @method init_manage_page
*/
init_manage_page: function() {
......
......@@ -1497,10 +1497,10 @@ class block_manager {
if ($bits[0] == 'tag' && !empty($this->page->subpage)) {
// better navbar for tag pages
$editpage->navbar->add(get_string('tags'), new moodle_url('/tag/'));
$tag = tag_get('id', $this->page->subpage, '*');
$tag = core_tag_tag::get($this->page->subpage);
// tag search page doesn't have subpageid
if ($tag) {
$editpage->navbar->add($tag->name, new moodle_url('/tag/index.php', array('id'=>$tag->id)));
$editpage->navbar->add($tag->get_display_name(), $tag->get_view_url());
}
}
$editpage->navbar->add($block->get_title());
......
......@@ -74,6 +74,34 @@ class tag_added extends base {
s($this->other['itemtype']) . "' with id '{$this->other['itemid']}'.";
}
/**
* Creates an event from taginstance object
*
* @since Moodle 3.1
* @param stdClass $taginstance
* @param string $tagname
* @param string $tagrawname
* @param bool $addsnapshot trust that $taginstance has all necessary fields and add it as a record snapshot
* @return tag_added
*/
public static function create_from_tag_instance($taginstance, $tagname, $tagrawname, $addsnapshot = false) {
$event = self::create(array(
'objectid' => $taginstance->id,
'contextid' => $taginstance->contextid,
'other' => array(
'tagid' => $taginstance->tagid,
'tagname' => $tagname,
'tagrawname' => $tagrawname,
'itemid' => $taginstance->itemid,
'itemtype' => $taginstance->itemtype
)
));
if ($addsnapshot) {
$event->add_record_snapshot('tag_instance', $taginstance);
}
return $event;
}
/**
* Return legacy data for add_to_log().
*
......
<?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/>.
/**
* Tag collection created event.
*
* @package core
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Tag collection created event class.
*
* @package core
* @since Moodle 3.0
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_collection_created extends base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['objecttable'] = 'tag_coll';
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Utility method to create new event.
*
* @param object $tagcoll
* @return user_graded
*/
public static function create_from_record($tagcoll) {
$event = self::create(array(
'objectid' => $tagcoll->id,
'context' => \context_system::instance(),
));
$event->add_record_snapshot('tag_coll', $tagcoll);
return $event;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventtagcollcreated', 'core_tag');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' created the tag collection with id '$this->objectid'";
}
}
<?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/>.
/**
* Tag collection deleted event.
*
* @package core
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Tag collection deleted event class.
*
* @package core
* @since Moodle 3.0
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_collection_deleted extends base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['objecttable'] = 'tag_coll';
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Utility method to create new event.
*
* @param object $tagcoll
* @return user_graded
*/
public static function create_from_record($tagcoll) {
$event = self::create(array(
'objectid' => $tagcoll->id,
'context' => \context_system::instance(),
));
$event->add_record_snapshot('tag_coll', $tagcoll);
return $event;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventtagcolldeleted', 'core_tag');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the tag collection with id '$this->objectid'";
}
}
<?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/>.
/**
* Tag collection updated event.
*
* @package core
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Tag collection updated event class.
*
* @package core
* @since Moodle 3.0
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_collection_updated extends base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['objecttable'] = 'tag_coll';
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Utility method to create new event.
*
* @param object $tagcoll
* @return user_graded
*/
public static function create_from_record($tagcoll) {
$event = self::create(array(
'objectid' => $tagcoll->id,
'context' => \context_system::instance(),
));
$event->add_record_snapshot('tag_coll', $tagcoll);
return $event;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventtagcollupdated', 'core_tag');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' updated the tag collection with id '$this->objectid'";
}
}
......@@ -52,6 +52,26 @@ class tag_created extends base {
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an event from tag object
*
* @since Moodle 3.1
* @param \core_tag_tag|\stdClass $tag
* @return tag_created
*/
public static function create_from_tag($tag) {
$event = self::create(array(
'objectid' => $tag->id,
'relateduserid' => $tag->userid,
'context' => \context_system::instance(),
'other' => array(
'name' => $tag->name,
'rawname' => $tag->rawname
)
));
return $event;
}
/**
* Returns localised general event name.
*
......
......@@ -74,6 +74,34 @@ class tag_removed extends base {
s($this->other['itemtype']) . "' with id '{$this->other['itemid']}'.";
}
/**
* Creates an event from taginstance object
*
* @since Moodle 3.1
* @param stdClass $taginstance
* @param string $tagname
* @param string $tagrawname
* @param bool $addsnapshot trust that $taginstance has all necessary fields and add it as a record snapshot
* @return tag_removed
*/
public static function create_from_tag_instance($taginstance, $tagname, $tagrawname, $addsnapshot = false) {
$event = self::create(array(
'objectid' => $taginstance->id,
'contextid' => $taginstance->contextid,
'other' => array(
'tagid' => $taginstance->tagid,
'tagname' => $tagname,
'tagrawname' => $tagrawname,
'itemid' => $taginstance->itemid,
'itemtype' => $taginstance->itemtype
)
));
if ($addsnapshot) {
$event->add_record_snapshot('tag_instance', $taginstance);
}
return $event;
}
/**
* Custom validation.
*
......
......@@ -23,6 +23,8 @@
*/
namespace core\task;
use core_tag_collection, core_tag_tag, core_tag_area, stdClass;
/**
* Simple task to run the tag cron.
*/
......@@ -45,9 +47,224 @@ class tag_cron_task extends scheduled_task {
global $CFG;
if (!empty($CFG->usetags)) {
require_once($CFG->dirroot.'/tag/lib.php');
tag_cron();
$this->compute_correlations();
$this->cleanup();
}
}
/**
* Calculates and stores the correlated tags of all tags.
*
* The correlations are stored in the 'tag_correlation' table.
*
* Two tags are correlated if they appear together a lot. Ex.: Users tagged with "computers"
* will probably also be tagged with "algorithms".
*
* The rationale for the 'tag_correlation' table is performance. It works as a cache
* for a potentially heavy load query done at the 'tag_instance' table. So, the
* 'tag_correlation' table stores redundant information derived from the 'tag_instance' table.
*
* @param int $mincorrelation Only tags with more than $mincorrelation correlations will be identified.
*/
public function compute_correlations($mincorrelation = 2) {
global $DB;
// This mighty one line query fetches a row from the database for every
// individual tag correlation. We then need to process the rows collecting
// the correlations for each tag id.
// The fields used by this query are as follows:
// tagid : This is the tag id, there should be at least $mincorrelation
// rows for each tag id.
// correlation : This is the tag id that correlates to the above tagid field.
// correlationid : This is the id of the row in the tag_correlation table that
// relates to the tagid field and will be NULL if there are no
// existing correlations.
$sql = 'SELECT pairs.tagid, pairs.correlation, pairs.ocurrences, co.id AS correlationid
FROM (
SELECT ta.tagid, tb.tagid AS correlation, COUNT(*) AS ocurrences
FROM {tag_instance} ta
JOIN {tag} tga ON ta.tagid = tga.id