diff --git a/admin/tool/uploadcourse/classes/course.php b/admin/tool/uploadcourse/classes/course.php
index ff2283304d0..4486cdebb5c 100644
--- a/admin/tool/uploadcourse/classes/course.php
+++ b/admin/tool/uploadcourse/classes/course.php
@@ -659,6 +659,10 @@ class tool_uploadcourse_course {
$this->data = $coursedata;
$this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata);
+ if (isset($this->rawdata['tags']) && strval($this->rawdata['tags']) !== '') {
+ $this->data['tags'] = preg_split('/\s*,\s*/', trim($this->rawdata['tags']), -1, PREG_SPLIT_NO_EMPTY);
+ }
+
// Restore data.
// TODO Speed up things by not really extracting the backup just yet, but checking that
// the backup file or shortname passed are valid. Extraction should happen in proceed().
diff --git a/admin/tool/uploadcourse/tests/course_test.php b/admin/tool/uploadcourse/tests/course_test.php
index a173e721811..20bbaa352df 100644
--- a/admin/tool/uploadcourse/tests/course_test.php
+++ b/admin/tool/uploadcourse/tests/course_test.php
@@ -261,6 +261,7 @@ class tool_uploadcourse_course_testcase extends advanced_testcase {
'groupmode' => '2',
'groupmodeforce' => '1',
'enablecompletion' => '1',
+ 'tags' => 'Cat, Dog',
'role_teacher' => 'Knight',
'role_manager' => 'Jedi',
@@ -297,6 +298,7 @@ class tool_uploadcourse_course_testcase extends advanced_testcase {
$this->assertEquals($data['groupmode'], $course->groupmode);
$this->assertEquals($data['groupmodeforce'], $course->groupmodeforce);
$this->assertEquals($data['enablecompletion'], $course->enablecompletion);
+ $this->assertEquals($data['tags'], join(', ', core_tag_tag::get_item_tags_array('core', 'course', $course->id)));
// Roles.
$roleids = array();
diff --git a/admin/tool/uploaduser/index.php b/admin/tool/uploaduser/index.php
index e687fe32214..2b3f43eaee0 100644
--- a/admin/tool/uploaduser/index.php
+++ b/admin/tool/uploaduser/index.php
@@ -96,6 +96,7 @@ $STD_FIELDS = array('id', 'username', 'email',
'suspended', // 1 means suspend user account, 0 means activate user account, nothing means keep as is for existing users
'deleted', // 1 means delete user
'mnethostid', // Can not be used for adding, updating or deleting of users - only for enrolments, groups, cohorts and suspending.
+ 'interests',
);
// Include all name fields.
$STD_FIELDS = array_merge($STD_FIELDS, get_all_user_name_fields());
@@ -836,6 +837,10 @@ if ($formdata = $mform2->is_cancelled()) {
}
}
+ // Update user interests.
+ if (isset($user->interests) && strval($user->interests) !== '') {
+ useredit_update_interests($user, preg_split('/\s*,\s*/', $user->interests, -1, PREG_SPLIT_NO_EMPTY));
+ }
// add to cohort first, it might trigger enrolments indirectly - do NOT create cohorts here!
foreach ($filecolumns as $column) {
diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php
index a41292dffdf..caf5f071b10 100644
--- a/backup/moodle2/restore_stepslib.php
+++ b/backup/moodle2/restore_stepslib.php
@@ -1778,22 +1778,8 @@ class restore_course_structure_step extends restore_structure_step {
$data = (object)$data;
- if (!empty($CFG->usetags)) { // if enabled in server
- // TODO: This is highly inneficient. Each time we add one tag
- // we fetch all the existing because tag_set() deletes them
- // so everything must be reinserted on each call
- $tags = array();
- $existingtags = tag_get_tags('course', $this->get_courseid());
- // Re-add all the existitng tags
- foreach ($existingtags as $existingtag) {
- $tags[] = $existingtag->rawname;
- }
- // Add the one being restored
- $tags[] = $data->rawname;
- // Send all the tags back to the course
- tag_set('course', $this->get_courseid(), $tags, 'core',
- context_course::instance($this->get_courseid())->id);
- }
+ core_tag_tag::add_item_tag('core', 'course', $this->get_courseid(),
+ context_course::instance($this->get_courseid()), $data->rawname);
}
public function process_allowed_module($data) {
@@ -4078,25 +4064,17 @@ class restore_create_categories_and_questions extends restore_structure_step {
return;
}
- if (!empty($CFG->usetags)) { // if enabled in server
- // TODO: This is highly inefficient. Each time we add one tag
- // we fetch all the existing because tag_set() deletes them
- // so everything must be reinserted on each call
- $tags = array();
- $existingtags = tag_get_tags('question', $newquestion);
- // Re-add all the existitng tags
- foreach ($existingtags as $existingtag) {
- $tags[] = $existingtag->rawname;
- }
- // Add the one being restored
- $tags[] = $data->rawname;
+ if (core_tag_tag::is_enabled('core_question', 'question')) {
+ $tagname = $data->rawname;
// Get the category, so we can then later get the context.
$categoryid = $this->get_new_parentid('question_category');
if (empty($this->cachedcategory) || $this->cachedcategory->id != $categoryid) {
$this->cachedcategory = $DB->get_record('question_categories', array('id' => $categoryid));
}
- // Send all the tags back to the question
- tag_set('question', $newquestion, $tags, 'core_question', $this->cachedcategory->contextid);
+ // Add the tag to the question.
+ core_tag_tag::add_item_tag('core_question', 'question', $newquestion,
+ context::instance_by_id($this->cachedcategory->contextid),
+ $tagname);
}
}
diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php
index 430e8bb0e46..120ea6c7d4e 100644
--- a/backup/util/dbops/restore_dbops.class.php
+++ b/backup/util/dbops/restore_dbops.class.php
@@ -1209,16 +1209,14 @@ abstract class restore_dbops {
}
// Process tags
- if (!empty($CFG->usetags) && isset($user->tags)) { // if enabled in server and present in backup
+ if (core_tag_tag::is_enabled('core', 'user') && isset($user->tags)) { // If enabled in server and present in backup.
$tags = array();
foreach($user->tags['tag'] as $usertag) {
$usertag = (object)$usertag;
$tags[] = $usertag->rawname;
}
- if (empty($newuserctxid)) {
- $newuserctxid = null; // Tag apis expect a null contextid not 0.
- }
- tag_set('user', $newuserid, $tags, 'core', $newuserctxid);
+ core_tag_tag::set_item_tags('core', 'user', $newuserid,
+ context_user::instance($newuserid), $tags);
}
// Process preferences
diff --git a/blocks/blog_tags/block_blog_tags.php b/blocks/blog_tags/block_blog_tags.php
index de32984c8ae..6edc7b5dff4 100644
--- a/blocks/blog_tags/block_blog_tags.php
+++ b/blocks/blog_tags/block_blog_tags.php
@@ -75,7 +75,7 @@ class block_blog_tags extends block_base {
}
return $this->content;
- } else if (empty($CFG->usetags)) {
+ } else if (!core_tag_tag::is_enabled('core', 'post')) {
$this->content = new stdClass();
$this->content->text = '';
if ($this->page->user_is_editing()) {
@@ -126,6 +126,7 @@ class block_blog_tags extends block_base {
WHERE t.id = ti.tagid AND p.id = ti.itemid
$type
AND ti.itemtype = 'post'
+ AND ti.component = 'core'
AND ti.timemodified > $timewithin";
if ($context->contextlevel == CONTEXT_MODULE) {
@@ -195,7 +196,9 @@ class block_blog_tags extends block_base {
}
$blogurl->param('tagid', $tag->id);
- $link = html_writer::link($blogurl, tag_display_name($tag), array('class'=>$tag->class, 'title'=>get_string('numberofentries','blog',$tag->ct)));
+ $link = html_writer::link($blogurl, core_tag_tag::make_display_name($tag),
+ array('class' => $tag->class,
+ 'title' => get_string('numberofentries', 'blog', $tag->ct)));
$this->content->text .= '
' . $link . ' ';
}
$this->content->text .= "\n\n";
diff --git a/blocks/tag_flickr/block_tag_flickr.php b/blocks/tag_flickr/block_tag_flickr.php
index 06f691d388d..22471f38eb1 100644
--- a/blocks/tag_flickr/block_tag_flickr.php
+++ b/blocks/tag_flickr/block_tag_flickr.php
@@ -47,7 +47,6 @@ class block_tag_flickr extends block_base {
global $CFG, $USER;
//note: do NOT include files at the top of this file
- require_once($CFG->dirroot.'/tag/lib.php');
require_once($CFG->libdir . '/filelib.php');
if ($this->content !== NULL) {
@@ -56,11 +55,12 @@ class block_tag_flickr extends block_base {
$tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility
$tag = optional_param('tag', '', PARAM_TAG); // tag
+ $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
- if ($tag) {
- $tagobject = tag_get('name', $tag);
- } else if ($tagid) {
- $tagobject = tag_get('id', $tagid);
+ if ($tagid) {
+ $tagobject = core_tag_tag::get($tagid);
+ } else if ($tag) {
+ $tagobject = core_tag_tag::get_by_name($tc, $tag);
}
if (empty($tagobject)) {
@@ -73,7 +73,9 @@ class block_tag_flickr extends block_base {
//include related tags in the photo query ?
$tagscsv = $tagobject->name;
if (!empty($this->config->includerelatedtags)) {
- $tagscsv .= ',' . tag_get_related_tags_csv(tag_get_related_tags($tagobject->id), TAG_RETURN_TEXT);
+ foreach ($tagobject->get_related_tags() as $t) {
+ $tagscsv .= ',' . $t->get_display_name(false);
+ }
}
$tagscsv = urlencode($tagscsv);
diff --git a/blocks/tag_flickr/edit_form.php b/blocks/tag_flickr/edit_form.php
index e67e039a7a2..da0a23f5f8d 100644
--- a/blocks/tag_flickr/edit_form.php
+++ b/blocks/tag_flickr/edit_form.php
@@ -36,7 +36,7 @@ class block_tag_flickr_edit_form extends block_edit_form {
$mform->setType('config_title', PARAM_TEXT);
$mform->addElement('text', 'config_numberofphotos', get_string('numberofphotos', 'block_tag_flickr'), array('size' => 5));
- $mform->setType('config_numberofvideos', PARAM_INT);
+ $mform->setType('config_numberofphotos', PARAM_INT);
$mform->addElement('selectyesno', 'config_includerelatedtags', get_string('includerelatedtags', 'block_tag_flickr'));
$mform->setDefault('config_includerelatedtags', 0);
diff --git a/blocks/tag_youtube/block_tag_youtube.php b/blocks/tag_youtube/block_tag_youtube.php
index 7ab13be342c..9c79fe8d982 100644
--- a/blocks/tag_youtube/block_tag_youtube.php
+++ b/blocks/tag_youtube/block_tag_youtube.php
@@ -64,7 +64,6 @@ class block_tag_youtube extends block_base {
global $CFG;
//note: do NOT include files at the top of this file
- require_once($CFG->dirroot.'/tag/lib.php');
require_once($CFG->libdir . '/filelib.php');
if ($this->content !== NULL) {
@@ -132,11 +131,12 @@ class block_tag_youtube extends block_base {
$tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility
$tag = optional_param('tag', '', PARAM_TAG); // tag
+ $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
- if ($tag) {
- $tagobject = tag_get('name', $tag);
- } else if ($tagid) {
- $tagobject = tag_get('id', $tagid);
+ if ($tagid) {
+ $tagobject = core_tag_tag::get($tagid);
+ } else if ($tag) {
+ $tagobject = core_tag_tag::get_by_name($tc, $tag);
}
if (empty($tagobject)) {
@@ -172,11 +172,12 @@ class block_tag_youtube extends block_base {
$tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility
$tag = optional_param('tag', '', PARAM_TAG); // tag
+ $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
- if ($tag) {
- $tagobject = tag_get('name', $tag);
- } else if ($tagid) {
- $tagobject = tag_get('id', $tagid);
+ if ($tagid) {
+ $tagobject = core_tag_tag::get($tagid);
+ } else if ($tag) {
+ $tagobject = core_tag_tag::get_by_name($tc, $tag);
}
if (empty($tagobject)) {
diff --git a/blocks/tags/block_tags.php b/blocks/tags/block_tags.php
index 329eb3f9cf8..31ced5814a5 100644
--- a/blocks/tags/block_tags.php
+++ b/blocks/tags/block_tags.php
@@ -70,6 +70,22 @@ class block_tags extends block_base {
$this->config->numberoftags = 80;
}
+ if (empty($this->config->tagtype)) {
+ $this->config->tagtype = '';
+ }
+
+ if (empty($this->config->ctx)) {
+ $this->config->ctx = 0;
+ }
+
+ if (empty($this->config->rec)) {
+ $this->config->rec = 1;
+ }
+
+ if (empty($this->config->tagcoll)) {
+ $this->config->tagcoll = 0;
+ }
+
if ($this->content !== NULL) {
return $this->content;
}
@@ -85,9 +101,11 @@ class block_tags extends block_base {
// Get a list of tags.
- require_once($CFG->dirroot.'/tag/locallib.php');
-
- $this->content->text = tag_print_cloud(null, $this->config->numberoftags, true);
+ $tagcloud = core_tag_collection::get_tag_cloud($this->config->tagcoll,
+ $this->config->tagtype,
+ $this->config->numberoftags,
+ 'name', '', $this->page->context->id, $this->config->ctx, $this->config->rec);
+ $this->content->text = $OUTPUT->render_from_template('core_tag/tagcloud', $tagcloud->export_for_template($OUTPUT));
return $this->content;
}
diff --git a/blocks/tags/edit_form.php b/blocks/tags/edit_form.php
index e6c80f12a20..89742c59a55 100644
--- a/blocks/tags/edit_form.php
+++ b/blocks/tags/edit_form.php
@@ -30,6 +30,7 @@
*/
class block_tags_edit_form extends block_edit_form {
protected function specific_definition($mform) {
+ global $CFG;
// Fields for editing HTML block title and contents.
$mform->addElement('header', 'configheader', get_string('blocksettings', 'block'));
@@ -37,11 +38,63 @@ class block_tags_edit_form extends block_edit_form {
$mform->setType('config_title', PARAM_TEXT);
$mform->setDefault('config_title', get_string('pluginname', 'block_tags'));
+ $this->add_collection_selector($mform);
+
$numberoftags = array();
for ($i = 1; $i <= 200; $i++) {
$numberoftags[$i] = $i;
}
$mform->addElement('select', 'config_numberoftags', get_string('numberoftags', 'blog'), $numberoftags);
$mform->setDefault('config_numberoftags', 80);
+
+ $defaults = array(
+ 'official' => get_string('officialonly', 'block_tags'),
+ '' => get_string('anytype', 'block_tags'));
+ $mform->addElement('select', 'config_tagtype', get_string('defaultdisplay', 'block_tags'), $defaults);
+ $mform->setDefault('config_tagtype', '');
+
+ $defaults = array(0 => context_system::instance()->get_context_name());
+ $parentcontext = context::instance_by_id($this->block->instance->parentcontextid);
+ if ($parentcontext->contextlevel > CONTEXT_COURSE) {
+ $coursecontext = $parentcontext->get_course_context();
+ $defaults[$coursecontext->id] = $coursecontext->get_context_name();
+ }
+ if ($parentcontext->contextlevel != CONTEXT_SYSTEM) {
+ $defaults[$parentcontext->id] = $parentcontext->get_context_name();
+ }
+ $mform->addElement('select', 'config_ctx', get_string('taggeditemscontext', 'block_tags'), $defaults);
+ $mform->addHelpButton('config_ctx', 'taggeditemscontext', 'block_tags');
+ $mform->setDefault('config_ctx', 0);
+
+ $mform->addElement('advcheckbox', 'config_rec', get_string('recursivecontext', 'block_tags'));
+ $mform->addHelpButton('config_rec', 'recursivecontext', 'block_tags');
+ $mform->setDefault('config_rec', 1);
+ }
+
+ /**
+ * Add the tag collection selector
+ *
+ * @param object $mform the form being built.
+ */
+ protected function add_collection_selector($mform) {
+ $tagcolls = core_tag_collection::get_collections_menu(false, false, get_string('anycollection', 'block_tags'));
+ if (count($tagcolls) <= 1) {
+ return;
+ }
+
+ $tagcollssearchable = core_tag_collection::get_collections_menu(false, true);
+ $hasunsearchable = false;
+ foreach ($tagcolls as $id => $name) {
+ if ($id && !array_key_exists($id, $tagcollssearchable)) {
+ $hasunsearchable = true;
+ $tagcolls[$id] = $name . '*';
+ }
+ }
+
+ $mform->addElement('select', 'config_tagcoll', get_string('tagcollection', 'block_tags'), $tagcolls);
+ if ($hasunsearchable) {
+ $mform->addHelpButton('config_tagcoll', 'tagcollection', 'block_tags');
+ }
+ $mform->setDefault('config_tagcoll', 0);
}
}
diff --git a/blocks/tags/lang/en/block_tags.php b/blocks/tags/lang/en/block_tags.php
index edf96699495..02dd7a1cb0a 100644
--- a/blocks/tags/lang/en/block_tags.php
+++ b/blocks/tags/lang/en/block_tags.php
@@ -22,14 +22,24 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['anycollection'] = 'Any';
+$string['anytype'] = 'All';
$string['configtitle'] = 'Block title';
$string['disabledtags'] = 'Tags are disabled';
$string['defaultdisplay'] = 'Tag type to display';
+$string['officialonly'] = 'Only official';
$string['pluginname'] = 'Tags';
+$string['recursivecontext'] = 'Include child contexts';
+$string['recursivecontext_help'] = 'If unchecked, tags of items in the context specified above will be displayed excluding underlying contexts, for example, you can search on course level only without searching inside course activities';
+$string['tagcollection'] = 'Tag collection';
+$string['tagcollection_help'] = 'Select tag collection to display tags from. If you choose "Any" '
+ . 'the tags from all collections except for those marked with * will be displayed';
+$string['taggeditemscontext'] = 'Tagged items context';
+$string['taggeditemscontext_help'] = 'You can limit the tag cloud to the tags that are present in the current course category, course or module';
$string['tags:addinstance'] = 'Add a new tags block';
$string['tags:myaddinstance'] = 'Add a new tags block to Dashboard';
-// Deprecated since 3.0
+// Deprecated since 3.0.
$string['add'] = 'Add';
$string['alltags'] = 'All tags:';
diff --git a/blocks/tags/tests/behat/tagcloud.feature b/blocks/tags/tests/behat/tagcloud.feature
index ed80f7b03a4..7a254906516 100644
--- a/blocks/tags/tests/behat/tagcloud.feature
+++ b/blocks/tags/tests/behat/tagcloud.feature
@@ -45,6 +45,6 @@ Feature: Block tags displaying tag cloud
And I should see "Cats" in the "Tags" "block"
And I should not see "Neverusedtag" in the "Tags" "block"
And I click on "Dogs" "link" in the "Tags" "block"
- And I should see "Users tagged with \"Dogs\": 1"
+ And I should see "User interests" in the ".tag-index-items h3" "css_element"
And I should see "Teacher 1"
And I log out
diff --git a/blog/edit.php b/blog/edit.php
index 634c511377c..82480390c3c 100644
--- a/blog/edit.php
+++ b/blog/edit.php
@@ -24,9 +24,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(dirname(__FILE__)).'/config.php');
-require_once('lib.php');
-require_once('locallib.php');
-require_once($CFG->dirroot .'/comment/lib.php');
+require_once($CFG->dirroot . '/blog/lib.php');
+require_once($CFG->dirroot . '/blog/locallib.php');
+require_once($CFG->dirroot . '/comment/lib.php');
+require_once($CFG->dirroot . '/blog/edit_form.php');
$action = required_param('action', PARAM_ALPHA);
$id = optional_param('entryid', 0, PARAM_INT);
@@ -185,7 +186,6 @@ if (!empty($entry->id)) {
}
}
-require_once('edit_form.php');
$summaryoptions = array('maxfiles' => 99, 'maxbytes' => $CFG->maxbytes, 'trusttext' => true, 'context' => $sitecontext,
'subdirs' => file_area_contains_subdirs($sitecontext, 'blog', 'post', $entry->id));
$attachmentoptions = array('subdirs' => false, 'maxfiles' => 99, 'maxbytes' => $CFG->maxbytes);
@@ -206,9 +206,8 @@ $entry = file_prepare_standard_filemanager($entry,
'attachment',
$entry->id);
-if (!empty($CFG->usetags) && !empty($entry->id)) {
- include_once($CFG->dirroot.'/tag/lib.php');
- $entry->tags = tag_get_tags_array('post', $entry->id);
+if (!empty($entry->id)) {
+ $entry->tags = core_tag_tag::get_item_tags_array('core', 'post', $entry->id);
}
$entry->action = $action;
@@ -271,7 +270,6 @@ switch ($action) {
if (empty($entry->id)) {
print_error('wrongentryid', 'blog');
}
- $entry->tags = tag_get_tags_array('post', $entry->id);
$strformheading = get_string('updateentrywithid', 'blog');
break;
diff --git a/blog/edit_form.php b/blog/edit_form.php
index 773f1533e11..7060c932b5a 100644
--- a/blog/edit_form.php
+++ b/blog/edit_form.php
@@ -65,10 +65,11 @@ class blog_edit_form extends moodleform {
$mform->addHelpButton('publishstate', 'publishto', 'blog');
$mform->setDefault('publishstate', 0);
- if (!empty($CFG->usetags)) {
+ if (core_tag_tag::is_enabled('core', 'post')) {
$mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
- $mform->addElement('tags', 'tags', get_string('tags'));
}
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'post', 'component' => 'core'));
$allmodnames = array();
diff --git a/blog/external_blog_edit.php b/blog/external_blog_edit.php
index 069ef9615f5..3f615bdd1b0 100644
--- a/blog/external_blog_edit.php
+++ b/blog/external_blog_edit.php
@@ -28,7 +28,6 @@ require_once('../config.php');
require_once('lib.php');
require_once('external_blog_edit_form.php');
require_once($CFG->libdir . '/simplepie/moodle_simplepie.php');
-require_once($CFG->dirroot.'/tag/lib.php');
require_login();
$context = context_system::instance();
@@ -58,6 +57,7 @@ if (!empty($id) && !$DB->record_exists('blog_external', array('id' => $id))) {
print_error('wrongexternalid', 'blog');
} else if (!empty($id)) {
$external = $DB->get_record('blog_external', array('id' => $id));
+ $external->autotags = core_tag_tag::get_item_tags_array('core', 'blog_external', $id);
}
$strformheading = ($action == 'edit') ? get_string('editexternalblog', 'blog') : get_string('addnewexternalblog', 'blog');
@@ -84,12 +84,9 @@ if ($externalblogform->is_cancelled()) {
$newexternal->timemodified = time();
$newexternal->id = $DB->insert_record('blog_external', $newexternal);
+ core_tag_tag::set_item_tags('core', 'blog_external', $newexternal->id,
+ context_user::instance($newexternal->userid), $data->autotags);
blog_sync_external_entries($newexternal);
- if ($CFG->usetags) {
- $autotags = (!empty($data->autotags)) ? $data->autotags : null;
- tag_set('blog_external', $newexternal->id, explode(',', $autotags), 'core',
- context_user::instance($newexternal->userid)->id);
- }
break;
@@ -107,11 +104,8 @@ if ($externalblogform->is_cancelled()) {
$external->timemodified = time();
$DB->update_record('blog_external', $external);
- if ($CFG->usetags) {
- $autotags = (!empty($data->autotags)) ? $data->autotags : null;
- tag_set('blog_external', $external->id, explode(',', $autotags), 'core',
- context_user::instance($external->userid)->id);
- }
+ core_tag_tag::set_item_tags('core', 'blog_external', $external->id,
+ context_user::instance($external->userid), $data->autotags);
} else {
print_error('wrongexternalid', 'blog');
}
@@ -125,6 +119,9 @@ if ($externalblogform->is_cancelled()) {
redirect($returnurl);
}
+navigation_node::override_active_url(new moodle_url('/blog/external_blogs.php'));
+$PAGE->navbar->add(get_string('addnewexternalblog', 'blog'));
+
$PAGE->set_heading(fullname($USER));
$PAGE->set_title("$SITE->shortname: $strblogs: $strexternalblogs");
diff --git a/blog/external_blog_edit_form.php b/blog/external_blog_edit_form.php
index fc28e9a406d..8de3bdcc58f 100644
--- a/blog/external_blog_edit_form.php
+++ b/blog/external_blog_edit_form.php
@@ -48,14 +48,14 @@ class blog_edit_external_form extends moodleform {
$mform->addElement('textarea', 'description', get_string('description', 'blog'), array('cols' => 50, 'rows' => 7));
$mform->addHelpButton('description', 'description', 'blog');
- if (!empty($CFG->usetags)) {
- $mform->addElement('text', 'filtertags', get_string('filtertags', 'blog'), array('size' => 50));
- $mform->setType('filtertags', PARAM_TAGLIST);
- $mform->addHelpButton('filtertags', 'filtertags', 'blog');
- $mform->addElement('text', 'autotags', get_string('autotags', 'blog'), array('size' => 50));
- $mform->setType('autotags', PARAM_TAGLIST);
- $mform->addHelpButton('autotags', 'autotags', 'blog');
- }
+ // To filter external blogs by their tags we do not need to check if tags in moodle are enabled.
+ $mform->addElement('text', 'filtertags', get_string('filtertags', 'blog'), array('size' => 50));
+ $mform->setType('filtertags', PARAM_TAGLIST);
+ $mform->addHelpButton('filtertags', 'filtertags', 'blog');
+
+ $mform->addElement('tags', 'autotags', get_string('autotags', 'blog'),
+ array('itemtype' => 'blog_external', 'component' => 'core'));
+ $mform->addHelpButton('autotags', 'autotags', 'blog');
$this->add_action_buttons();
@@ -115,7 +115,6 @@ class blog_edit_external_form extends moodleform {
}
if ($id = $mform->getElementValue('id')) {
- $mform->setDefault('autotags', implode(',', tag_get_tags_array('blog_external', $id)));
$mform->freeze('url');
if ($mform->elementExists('filtertags')) {
$mform->freeze('filtertags');
diff --git a/blog/index.php b/blog/index.php
index bc3dd1ed06b..1b64b42fe56 100644
--- a/blog/index.php
+++ b/blog/index.php
@@ -24,7 +24,6 @@ require_once(dirname(dirname(__FILE__)).'/config.php');
require_once($CFG->dirroot .'/blog/lib.php');
require_once($CFG->dirroot .'/blog/locallib.php');
require_once($CFG->dirroot .'/course/lib.php');
-require_once($CFG->dirroot .'/tag/lib.php');
require_once($CFG->dirroot .'/comment/lib.php');
$id = optional_param('id', null, PARAM_INT);
diff --git a/blog/lib.php b/blog/lib.php
index 00a1ffa6bf5..06b4af808ae 100644
--- a/blog/lib.php
+++ b/blog/lib.php
@@ -29,7 +29,6 @@ defined('MOODLE_INTERNAL') || die();
* Library of functions and constants for blog
*/
require_once($CFG->dirroot .'/blog/rsslib.php');
-require_once($CFG->dirroot.'/tag/lib.php');
/**
* User can edit a blog entry if this is their own blog entry and they have
@@ -254,8 +253,8 @@ function blog_sync_external_entries($externalblog) {
$id = $DB->insert_record('post', $newentry);
// Set tags.
- if ($tags = tag_get_tags_array('blog_external', $externalblog->id)) {
- tag_set('post', $id, $tags, 'core', context_user::instance($externalblog->userid)->id);
+ if ($tags = core_tag_tag::get_item_tags_array('core', 'blog_external', $externalblog->id)) {
+ core_tag_tag::set_item_tags('core', 'post', $id, context_user::instance($externalblog->userid), $tags);
}
} else {
$newentry->id = $postid;
@@ -1046,3 +1045,105 @@ function core_blog_myprofile_navigation(core_user\output\myprofile\tree $tree, $
$tree->add_node($blognode);
return true;
}
+
+/**
+ * Returns posts tagged with a specified 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 $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param int $page 0-based number of page being displayed
+ * @return \core_tag\output\tagindex
+ */
+function blog_get_tagged_posts($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = true, $page = 0) {
+ global $CFG, $OUTPUT;
+ require_once($CFG->dirroot.'/user/lib.php');
+
+ $systemcontext = context_system::instance();
+ $perpage = $exclusivemode ? 20 : 5;
+ $context = $ctx ? context::instance_by_id($ctx) : context_system::instance();
+
+ $content = '';
+ if (empty($CFG->enableblogs) || !has_capability('moodle/blog:view', $systemcontext)) {
+ // Blogs are not enabled or are not visible to the current user.
+ $totalpages = 0;
+ } else if ($context->contextlevel != CONTEXT_SYSTEM && empty($CFG->useblogassociations)) {
+ // No blog entries can be associated to the non-system context.
+ $totalpages = 0;
+ } else if (!$rec && $context->contextlevel != CONTEXT_COURSE && $context->contextlevel != CONTEXT_MODULE) {
+ // No blog entries can be associated with category or block context.
+ $totalpages = 0;
+ } else {
+ require_once($CFG->dirroot.'/blog/locallib.php');
+
+ $filters = array('tag' => $tag->id);
+ if ($rec) {
+ if ($context->contextlevel != CONTEXT_SYSTEM) {
+ $filters['context'] = $context->id;
+ }
+ } else if ($context->contextlevel == CONTEXT_COURSE) {
+ $filters['course'] = $context->instanceid;
+ } else if ($context->contextlevel == CONTEXT_MODULE) {
+ $filters['module'] = $context->instanceid;
+ }
+ $bloglisting = new blog_listing($filters);
+ $blogs = $bloglisting->get_entries($page * $perpage, $perpage);
+ $totalcount = $bloglisting->count_entries();
+ $totalpages = ceil($totalcount / $perpage);
+ if (!empty($blogs)) {
+ $tagfeed = new core_tag\output\tagfeed();
+ foreach ($blogs as $blog) {
+ $user = fullclone($blog);
+ $user->id = $blog->userid;
+ $user->deleted = 0;
+ $img = $OUTPUT->user_picture($user, array('size' => 35));
+ $subject = format_string($blog->subject);
+
+ if ($blog->publishstate == 'draft') {
+ $class = 'dimmed';
+ } else {
+ $class = '';
+ }
+
+ $url = new moodle_url('/blog/index.php', array('entryid' => $blog->id));
+ $subject = html_writer::link($url, $subject, array('class' => $class));
+
+ $fullname = fullname($user);
+ if (user_can_view_profile($user)) {
+ $profilelink = new moodle_url('/user/view.php', array('id' => $blog->userid));
+ $fullname = html_writer::link($profilelink, $fullname);
+ }
+ $details = $fullname . ', ' . userdate($blog->created);
+
+ $tagfeed->add($img, $subject, $details);
+ }
+
+ $items = $tagfeed->export_for_template($OUTPUT);
+ $content = $OUTPUT->render_from_template('core_tag/tagfeed', $items);
+
+ $urlparams = array('tagid' => $tag->id);
+ if ($context->contextlevel == CONTEXT_COURSE) {
+ $urlparams['courseid'] = $context->instanceid;
+ } else if ($context->contextlevel == CONTEXT_MODULE) {
+ $urlparams['modid'] = $context->instanceid;
+ }
+ $allblogsurl = new moodle_url('/blog/index.php', $urlparams);
+
+ $rv = new core_tag\output\tagindex($tag, 'core', 'post',
+ $content,
+ $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
+ $rv->exclusiveurl = $allblogsurl;
+ return $rv;
+ }
+ }
+
+ $rv = new core_tag\output\tagindex($tag, 'core', 'post',
+ $content,
+ $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
+ $rv->exclusiveurl = null;
+ return $rv;
+}
diff --git a/blog/locallib.php b/blog/locallib.php
index 7968483fb36..5e31f8e4401 100644
--- a/blog/locallib.php
+++ b/blog/locallib.php
@@ -256,14 +256,11 @@ class blog_entry implements renderable {
// Insert the new blog entry.
$this->id = $DB->insert_record('post', $this);
- // Update tags.
- $this->add_tags_info();
-
if (!empty($CFG->useblogassociations)) {
$this->add_associations();
}
- tag_set('post', $this->id, $this->tags, 'core', context_user::instance($this->userid)->id);
+ core_tag_tag::set_item_tags('core', 'post', $this->id, context_user::instance($this->userid), $this->tags);
// Trigger an event for the new entry.
$event = \core\event\blog_entry_created::create(array(
@@ -312,7 +309,7 @@ class blog_entry implements renderable {
// Update record.
$DB->update_record('post', $entry);
- tag_set('post', $entry->id, $entry->tags, 'core', context_user::instance($this->userid)->id);
+ core_tag_tag::set_item_tags('core', 'post', $entry->id, context_user::instance($this->userid), $entry->tags);
$event = \core\event\blog_entry_updated::create(array(
'objectid' => $entry->id,
@@ -336,7 +333,7 @@ class blog_entry implements renderable {
// Get record to pass onto the event.
$record = $DB->get_record('post', array('id' => $this->id));
$DB->delete_records('post', array('id' => $this->id));
- tag_set('post', $this->id, array(), 'core', context_user::instance($this->userid)->id);
+ core_tag_tag::remove_all_item_tags('core', 'post', $this->id);
$event = \core\event\blog_entry_deleted::create(array(
'objectid' => $this->id,
@@ -424,26 +421,6 @@ class blog_entry implements renderable {
$fs->delete_area_files(SYSCONTEXTID, 'blog', 'post', $this->id);
}
- /**
- * function to attach tags into an entry
- * @return void
- */
- public function add_tags_info() {
-
- $tags = array();
-
- if ($otags = optional_param('otags', '', PARAM_INT)) {
- foreach ($otags as $tagid) {
- // TODO : make this use the tag name in the form.
- if ($tag = tag_get('id', $tagid)) {
- $tags[] = $tag->name;
- }
- }
- }
-
- tag_set('post', $this->id, $tags, 'core', context_user::instance($this->userid)->id);
- }
-
/**
* User can edit a blog entry if this is their own blog entry and they have
* the capability moodle/blog:create, or if they have the capability
@@ -570,7 +547,13 @@ class blog_listing {
* Array of blog_entry objects.
* @var array $entries
*/
- public $entries = array();
+ public $entries = null;
+
+ /**
+ * Caches the total number of the entries.
+ * @var int
+ */
+ public $totalentries = null;
/**
* An array of blog_filter_* objects
@@ -608,9 +591,12 @@ class blog_listing {
public function get_entries($start=0, $limit=10) {
global $DB;
- if (empty($this->entries)) {
+ if ($this->entries === null) {
if ($sqlarray = $this->get_entry_fetch_sql(false, 'created DESC')) {
$this->entries = $DB->get_records_sql($sqlarray['sql'], $sqlarray['params'], $start, $limit);
+ if (!$start && count($this->entries) < $limit) {
+ $this->totalentries = count($this->entries);
+ }
} else {
return false;
}
@@ -619,6 +605,23 @@ class blog_listing {
return $this->entries;
}
+ /**
+ * Finds total number of blog entries
+ *
+ * @return int
+ */
+ public function count_entries() {
+ global $DB;
+ if ($this->totalentries === null) {
+ if ($sqlarray = $this->get_entry_fetch_sql(true)) {
+ $this->totalentries = $DB->count_records_sql($sqlarray['sql'], $sqlarray['params']);
+ } else {
+ $this->totalentries = 0;
+ }
+ }
+ return $this->totalentries;
+ }
+
public function get_entry_fetch_sql($count=false, $sort='lastmodified DESC', $userid = false) {
global $DB, $USER, $CFG;
@@ -626,7 +629,7 @@ class blog_listing {
$userid = $USER->id;
}
- $allnamefields = get_all_user_name_fields(true, 'u');
+ $allnamefields = \user_picture::fields('u', null, 'useridalias');
// The query used to locate blog entries is complicated. It will be built from the following components:
$requiredfields = "p.*, $allnamefields, u.email"; // The SELECT clause.
$tables = array('p' => 'post', 'u' => 'user'); // Components of the FROM clause (table_id => table_name).
@@ -707,13 +710,8 @@ class blog_listing {
$morelink = '
';
- if ($sqlarray = $this->get_entry_fetch_sql(true)) {
- $totalentries = $DB->count_records_sql($sqlarray['sql'], $sqlarray['params']);
- } else {
- $totalentries = 0;
- }
-
$entries = $this->get_entries($start, $limit);
+ $totalentries = $this->count_entries();
$pagingbar = new paging_bar($totalentries, $page, $limit, $this->get_baseurl());
$pagingbar->pagevar = 'blogpage';
$blogheaders = blog_get_headers();
@@ -909,13 +907,14 @@ class blog_filter_context extends blog_filter {
$this->availabletypes = array('site' => get_string('site'),
'course' => get_string('course'),
- 'module' => get_string('activity'));
+ 'module' => get_string('activity'),
+ 'context' => get_string('coresystem'));
switch ($this->type) {
case 'course': // Careful of site course!
// Ignore course filter if blog associations are not enabled.
if ($this->id != $SITE->id && !empty($CFG->useblogassociations)) {
- $this->overrides = array('site');
+ $this->overrides = array('site', 'context');
$context = context_course::instance($this->id);
$this->tables['ba'] = 'blog_association';
$this->conditions[] = 'p.id = ba.blogid';
@@ -930,7 +929,7 @@ class blog_filter_context extends blog_filter {
break;
case 'module':
if (!empty($CFG->useblogassociations)) {
- $this->overrides = array('course', 'site');
+ $this->overrides = array('course', 'site', 'context');
$context = context_module::instance($this->id);
$this->tables['ba'] = 'blog_association';
@@ -939,6 +938,19 @@ class blog_filter_context extends blog_filter {
$this->params = array($context->id);
}
break;
+ case 'context':
+ if ($id != context_system::instance()->id && !empty($CFG->useblogassociations)) {
+ $this->overrides = array('site');
+ $context = context::instance_by_id($this->id);
+ $this->tables['ba'] = 'blog_association';
+ $this->tables['ctx'] = 'context';
+ $this->conditions[] = 'p.id = ba.blogid';
+ $this->conditions[] = 'ctx.id = ba.contextid';
+ $this->conditions[] = 'ctx.path LIKE ?';
+ $this->params = array($context->path . '%');
+ }
+ break;
+
}
}
}
@@ -1012,6 +1024,7 @@ class blog_filter_tag extends blog_filter {
$this->conditions = array('ti.tagid = t.id',
"ti.itemtype = 'post'",
+ "ti.component = 'core'",
'ti.itemid = p.id',
't.id = ?');
$this->params = array($this->id);
diff --git a/blog/renderer.php b/blog/renderer.php
index 1045a898685..640686571c2 100644
--- a/blog/renderer.php
+++ b/blog/renderer.php
@@ -132,21 +132,7 @@ class core_blog_renderer extends plugin_renderer_base {
}
// Links to tags.
- $officialtags = tag_get_tags_csv('post', $entry->id, TAG_RETURN_HTML, 'official');
- $defaulttags = tag_get_tags_csv('post', $entry->id, TAG_RETURN_HTML, 'default');
-
- if (!empty($CFG->usetags) && ($officialtags || $defaulttags) ) {
- $o .= $this->output->container_start('tags');
-
- if ($officialtags) {
- $o .= get_string('tags', 'tag') .': '. $this->output->container($officialtags, 'officialblogtags');
- if ($defaulttags) {
- $o .= ', ';
- }
- }
- $o .= $defaulttags;
- $o .= $this->output->container_end();
- }
+ $o .= $this->output->tag_list(core_tag_tag::get_item_tags('core', 'post', $entry->id));
// Add associations.
if (!empty($CFG->useblogassociations) && !empty($entry->renderable->blogassociations)) {
diff --git a/blog/rsslib.php b/blog/rsslib.php
index cad38d88d50..366c5cd5654 100644
--- a/blog/rsslib.php
+++ b/blog/rsslib.php
@@ -217,10 +217,8 @@ function blog_rss_get_feed($context, $args) {
$summary = file_rewrite_pluginfile_urls($blogentry->summary, 'pluginfile.php',
$sitecontext->id, 'blog', 'post', $blogentry->id);
$item->description = format_text($summary, $blogentry->format);
- if ( !empty($CFG->usetags) && ($blogtags = tag_get_tags_array('post', $blogentry->id)) ) {
- if ($blogtags) {
- $item->tags = $blogtags;
- }
+ if ($blogtags = core_tag_tag::get_item_tags_array('core', 'post', $blogentry->id)) {
+ $item->tags = $blogtags;
$item->tagscheme = $CFG->wwwroot . '/tag';
}
$items[] = $item;
diff --git a/blog/tests/lib_test.php b/blog/tests/lib_test.php
index 1eb6583868f..81eac309c7e 100644
--- a/blog/tests/lib_test.php
+++ b/blog/tests/lib_test.php
@@ -65,18 +65,15 @@ class core_blog_lib_testcase extends advanced_testcase {
));
// Create default tag.
- $tag = new stdClass();
- $tag->userid = $user->id;
- $tag->name = 'testtagname';
- $tag->rawname = 'Testtagname';
- $tag->tagtype = 'official';
- $tag->id = $DB->insert_record('tag', $tag);
+ $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
+ 'rawname' => 'Testtagname', 'tagtype' => 'official'));
// Create default post.
$post = new stdClass();
$post->userid = $user->id;
$post->groupid = $group->id;
$post->content = 'test post content text';
+ $post->module = 'blog';
$post->id = $DB->insert_record('post', $post);
// Grab important ids.
@@ -544,5 +541,66 @@ class core_blog_lib_testcase extends advanced_testcase {
$nodes->setAccessible(true);
$this->assertArrayNotHasKey('blogs', $nodes->getValue($tree));
}
+
+ public function test_blog_get_listing_course() {
+ $this->setAdminUser();
+ $coursecontext = context_course::instance($this->courseid);
+ $anothercourse = $this->getDataGenerator()->create_course();
+
+ // Add blog associations with a course.
+ $blog = new blog_entry($this->postid);
+ $blog->add_association($coursecontext->id);
+
+ // There is one entry associated with a course.
+ $bloglisting = new blog_listing(array('course' => $this->courseid));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is no entry associated with a wrong course.
+ $bloglisting = new blog_listing(array('course' => $anothercourse->id));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is no entry associated with a module.
+ $bloglisting = new blog_listing(array('module' => $this->cmid));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is one entry associated with a site (id is ignored).
+ $bloglisting = new blog_listing(array('site' => 12345));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is one entry associated with course context.
+ $bloglisting = new blog_listing(array('context' => $coursecontext->id));
+ $this->assertCount(1, $bloglisting->get_entries());
+ }
+
+ public function test_blog_get_listing_module() {
+ $this->setAdminUser();
+ $coursecontext = context_course::instance($this->courseid);
+ $contextmodule = context_module::instance($this->cmid);
+ $anothermodule = $this->getDataGenerator()->create_module('page', array('course' => $this->courseid));
+
+ // Add blog associations with a course.
+ $blog = new blog_entry($this->postid);
+ $blog->add_association($contextmodule->id);
+
+ // There is no entry associated with a course.
+ $bloglisting = new blog_listing(array('course' => $this->courseid));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is one entry associated with a module.
+ $bloglisting = new blog_listing(array('module' => $this->cmid));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is no entry associated with a wrong module.
+ $bloglisting = new blog_listing(array('module' => $anothermodule->cmid));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is one entry associated with a site (id is ignored).
+ $bloglisting = new blog_listing(array('site' => 12345));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is one entry associated with course context (module is a subcontext of a course).
+ $bloglisting = new blog_listing(array('context' => $coursecontext->id));
+ $this->assertCount(1, $bloglisting->get_entries());
+ }
}
diff --git a/course/edit.php b/course/edit.php
index 0189fae101b..d79c0d6ae6f 100644
--- a/course/edit.php
+++ b/course/edit.php
@@ -123,10 +123,7 @@ if (!empty($course)) {
}
// Populate course tags.
- if (!empty($CFG->usetags)) {
- include_once($CFG->dirroot.'/tag/lib.php');
- $course->tags = tag_get_tags_array('course', $course->id);
- }
+ $course->tags = core_tag_tag::get_item_tags_array('core', 'course', $course->id);
} else {
// Editor should respect category context if course context is not set.
diff --git a/course/edit_form.php b/course/edit_form.php
index 94d260c2ab3..7c9f43edf99 100644
--- a/course/edit_form.php
+++ b/course/edit_form.php
@@ -304,11 +304,12 @@ class course_edit_form extends moodleform {
}
}
- if (!empty($CFG->usetags) &&
+ if (core_tag_tag::is_enabled('core', 'course') &&
((empty($course->id) && guess_if_creator_will_have_course_capability('moodle/course:tag', $categorycontext))
|| (!empty($course->id) && has_capability('moodle/course:tag', $coursecontext)))) {
$mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
- $mform->addElement('tags', 'tags', get_string('tags'));
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'course', 'component' => 'core'));
}
// When two elements we need a group.
diff --git a/course/lib.php b/course/lib.php
index 21c44813573..0c2acd3601b 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -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_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
// Delete the context.
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
@@ -2583,7 +2582,6 @@ function course_overviewfiles_options($course) {
*/
function create_course($data, $editoroptions = NULL) {
global $DB, $CFG;
- require_once($CFG->dirroot.'/tag/lib.php');
//check the categoryid - must be given for all new courses
$category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
@@ -2669,8 +2667,8 @@ function create_course($data, $editoroptions = NULL) {
enrol_course_updated(true, $course, $data);
// Update course tags.
- if ($CFG->usetags && isset($data->tags)) {
- tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+ if (isset($data->tags)) {
+ core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
}
return $course;
@@ -2688,7 +2686,6 @@ function create_course($data, $editoroptions = NULL) {
*/
function update_course($data, $editoroptions = NULL) {
global $DB, $CFG;
- require_once($CFG->dirroot.'/tag/lib.php');
$data->timemodified = time();
@@ -2776,8 +2773,8 @@ function update_course($data, $editoroptions = NULL) {
enrol_course_updated(false, $course, $data);
// Update course tags.
- if ($CFG->usetags && isset($data->tags)) {
- tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+ if (isset($data->tags)) {
+ core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
}
// Trigger a course updated event.
@@ -3829,3 +3826,36 @@ function course_view($context, $sectionnumber = 0) {
$event = \core\event\course_viewed::create($eventdata);
$event->trigger();
}
+
+/**
+ * Returns courses tagged with a specified 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 $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param int $page 0-based number of page being displayed
+ * @return \core_tag\output\tagindex
+ */
+function course_get_tagged_courses($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) {
+ global $CFG, $PAGE;
+ require_once($CFG->libdir . '/coursecatlib.php');
+
+ $perpage = $exclusivemode ? $CFG->coursesperpage : 5;
+ $displayoptions = array(
+ 'limit' => $perpage,
+ 'offset' => $page * $perpage,
+ 'viewmoreurl' => null,
+ );
+
+ $courserenderer = $PAGE->get_renderer('core', 'course');
+ $totalcount = coursecat::search_courses_count(array('tagid' => $tag->id, 'ctx' => $ctx, 'rec' => $rec));
+ $content = $courserenderer->tagged_courses($tag->id, $exclusivemode, $ctx, $rec, $displayoptions);
+ $totalpages = ceil($totalcount / $perpage);
+
+ return new core_tag\output\tagindex($tag, 'core', 'course', $content,
+ $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
+}
diff --git a/course/renderer.php b/course/renderer.php
index ec26762998e..b71d4882d41 100644
--- a/course/renderer.php
+++ b/course/renderer.php
@@ -1896,29 +1896,52 @@ class core_course_renderer extends plugin_renderer_base {
* Renders html to print list of courses tagged with particular tag
*
* @param int $tagid id of the 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 $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param array $displayoptions
* @return string empty string if no courses are marked with this tag or rendered list of courses
*/
- public function tagged_courses($tagid) {
+ public function tagged_courses($tagid, $exclusivemode = true, $ctx = 0, $rec = true, $displayoptions = null) {
global $CFG;
- require_once($CFG->libdir. '/coursecatlib.php');
- $displayoptions = array('limit' => $CFG->coursesperpage);
- $displayoptions['viewmoreurl'] = new moodle_url('/course/search.php',
- array('tagid' => $tagid, 'page' => 1, 'perpage' => $CFG->coursesperpage));
- $displayoptions['viewmoretext'] = new lang_string('findmorecourses');
+ require_once($CFG->libdir . '/coursecatlib.php');
+ if (empty($displayoptions)) {
+ $displayoptions = array();
+ }
+ $showcategories = coursecat::count_all() > 1;
+ $displayoptions += array('limit' => $CFG->coursesperpage, 'offset' => 0);
$chelper = new coursecat_helper();
- $searchcriteria = array('tagid' => $tagid);
- $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT)->
- set_search_criteria(array('tagid' => $tagid))->
+ $searchcriteria = array('tagid' => $tagid, 'ctx' => $ctx, 'rec' => $rec);
+ $chelper->set_show_courses($showcategories ? self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT :
+ self::COURSECAT_SHOW_COURSES_EXPANDED)->
+ set_search_criteria($searchcriteria)->
set_courses_display_options($displayoptions)->
set_attributes(array('class' => 'course-search-result course-search-result-tagid'));
// (we set the same css class as in search results by tagid)
- $courses = coursecat::search_courses($searchcriteria, $chelper->get_courses_display_options());
- $totalcount = coursecat::search_courses_count($searchcriteria);
- $content = $this->coursecat_courses($chelper, $courses, $totalcount);
- if ($totalcount) {
- require_once $CFG->dirroot.'/tag/lib.php';
- $heading = get_string('courses') . ' ' . get_string('taggedwith', 'tag', tag_get_name($tagid)) .': '. $totalcount;
- return $this->heading($heading, 3). $content;
+ if ($totalcount = coursecat::search_courses_count($searchcriteria)) {
+ $courses = coursecat::search_courses($searchcriteria, $chelper->get_courses_display_options());
+ if ($exclusivemode) {
+ return $this->coursecat_courses($chelper, $courses, $totalcount);
+ } else {
+ $tagfeed = new core_tag\output\tagfeed();
+ $img = $this->output->pix_icon('i/course', '');
+ foreach ($courses as $course) {
+ $url = course_get_url($course);
+ $imgwithlink = html_writer::link($url, $img);
+ $coursename = html_writer::link($url, $course->get_formatted_name());
+ $details = '';
+ if ($showcategories && ($cat = coursecat::get($course->category, IGNORE_MISSING))) {
+ $details = get_string('category').': '.
+ html_writer::link(new moodle_url('/course/index.php', array('categoryid' => $cat->id)),
+ $cat->get_formatted_name(), array('class' => $cat->visible ? '' : 'dimmed'));
+ }
+ $tagfeed->add($imgwithlink, $coursename, $details);
+ }
+ return $this->output->render_from_template('core_tag/tagfeed', $tagfeed->export_for_template($this->output));
+ }
}
return '';
}
diff --git a/course/tags.php b/course/tags.php
index 8e81fb1e79e..1c7ad66aedd 100644
--- a/course/tags.php
+++ b/course/tags.php
@@ -23,7 +23,6 @@
*/
require_once("../config.php");
-require_once($CFG->dirroot . '/tag/lib.php');
require_once($CFG->dirroot . '/course/tags_form.php');
$id = required_param('id', PARAM_INT); // Course id.
@@ -38,7 +37,7 @@ if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $co
print_error('coursehidden', '', $CFG->wwwroot .'/');
}
require_capability('moodle/course:tag', $context);
-if (empty($CFG->usetags)) {
+if (!core_tag_tag::is_enabled('core', 'course')) {
print_error('tagsaredisabled', 'tag');
}
@@ -49,14 +48,14 @@ $PAGE->set_title(get_string('coursetags', 'tag'));
$PAGE->set_heading($course->fullname);
$form = new coursetags_form();
-$data = array('id' => $course->id, 'tags' => tag_get_tags_array('course', $course->id));
+$data = array('id' => $course->id, 'tags' => core_tag_tag::get_item_tags_array('core', 'course', $course->id));
$form->set_data($data);
$redirecturl = $returnurl ? new moodle_url($returnurl) : course_get_url($course);
if ($form->is_cancelled()) {
redirect($redirecturl);
} else if ($data = $form->get_data()) {
- tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+ core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
redirect($redirecturl);
}
diff --git a/course/tags_form.php b/course/tags_form.php
index 08cacc02e4a..fc54c03f11e 100644
--- a/course/tags_form.php
+++ b/course/tags_form.php
@@ -41,7 +41,8 @@ class coursetags_form extends moodleform {
public function definition() {
$mform = $this->_form;
- $mform->addElement('tags', 'tags', get_string('tags'));
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'course', 'component' => 'core'));
$mform->addElement('hidden', 'id', null);
$mform->setType('id', PARAM_INT);
diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php
index 421b1e4c2eb..de740656f85 100644
--- a/course/tests/courselib_test.php
+++ b/course/tests/courselib_test.php
@@ -29,7 +29,6 @@ global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
-require_once($CFG->dirroot . '/tag/lib.php');
class core_course_courselib_testcase extends advanced_testcase {
@@ -1501,6 +1500,7 @@ class core_course_courselib_testcase extends advanced_testcase {
*/
public function test_course_delete_module($type, $options) {
global $DB;
+
$this->resetAfterTest(true);
$this->setAdminUser();
@@ -1521,7 +1521,7 @@ class core_course_courselib_testcase extends advanced_testcase {
switch ($type) {
case 'assign':
// Add some tags to this assignment.
- tag_set('assign', $module->id, array('Tag 1', 'Tag 2', 'Tag 3'), 'mod_assign', $modcontext->id);
+ core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
// Confirm the tag instances were added.
$criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php
index c5a6464edf3..10a84ba332d 100644
--- a/course/tests/externallib_test.php
+++ b/course/tests/externallib_test.php
@@ -605,8 +605,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
*/
public function test_search_courses () {
- global $DB, $CFG;
- require_once($CFG->dirroot . '/tag/lib.php');
+ global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
@@ -636,8 +635,10 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
// Enable coursetag option.
set_config('block_tags_showcoursetags', true);
// Add tag 'TAG-LABEL ON SECOND COURSE' to Course2.
- tag_set('course', $course2->id, array('TAG-LABEL ON SECOND COURSE'), 'core', context_course::instance($course2->id)->id);
- $taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST);
+ core_tag_tag::set_item_tags('core', 'course', $course2->id, context_course::instance($course2->id),
+ array('TAG-LABEL ON SECOND COURSE'));
+ $taginstance = $DB->get_record('tag_instance',
+ array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST);
// Search by tagid.
$results = core_course_external::search_courses('tagid', $taginstance->tagid);
$results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results);
diff --git a/lang/en/cache.php b/lang/en/cache.php
index a44742e19fa..3a2c783c28e 100644
--- a/lang/en/cache.php
+++ b/lang/en/cache.php
@@ -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';
diff --git a/lang/en/deprecated.txt b/lang/en/deprecated.txt
index ab59bdb7fc6..366e26bc5a2 100644
--- a/lang/en/deprecated.txt
+++ b/lang/en/deprecated.txt
@@ -20,3 +20,4 @@ updated,core_tag
withselectedtags,core_tag
tag:create,core_role
categoriesanditems,core_grades
+taggedwith,core_tag
diff --git a/lang/en/question.php b/lang/en/question.php
index 2ce0f2c175d..c4ea1b40553 100644
--- a/lang/en/question.php
+++ b/lang/en/question.php
@@ -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}';
diff --git a/lang/en/tag.php b/lang/en/tag.php
index 6065fb41322..e20a6031c95 100644
--- a/lang/en/tag.php
+++ b/lang/en/tag.php
@@ -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}"';
diff --git a/lib/adminlib.php b/lib/adminlib.php
index 3bcb96658b6..c0ca283b2e9 100644
--- a/lib/adminlib.php
+++ b/lib/adminlib.php
@@ -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);
diff --git a/lib/amd/build/tag.min.js b/lib/amd/build/tag.min.js
index 8d684ae99fe..7a4328f304b 100644
--- a/lib/amd/build/tag.min.js
+++ b/lib/amd/build/tag.min.js
@@ -1 +1 @@
-define(["jquery","core/ajax","core/templates","core/notification","core/str","core/config"],function(a,b,c,d,e,f){return{init_manage_page:function(){var g=function(b){var c=b.closest("tr").get(0);if(c){var d=a(c).find("td.col-timemodified").get(0);e.get_string("now").done(function(b){a(d).html(b)})}};a(".tag-management-table").delegate(".tagtype","click",function(d){d.preventDefault();var e=a(this),f=e.attr("data-id"),h=e.attr("data-value"),i="1"===h?0:1,j=b.call([{methodname:"core_tag_update_tags",args:{tags:[{id:f,official:i}]}},{methodname:"core_tag_get_tags",args:{tags:[{id:f}]}}],!0);a.when.apply(a,j).done(function(a,b){void 0===a.warnings[0]&&void 0!==b.tags[0]&&c.render("core_tag/tagtype",b.tags[0]).done(function(a){g(e);var b=e.parent();e.replaceWith(a),b.find(".tagtype").get(0).focus()})})}),a(".tag-management-table").delegate(".tagflag","click",function(d){d.preventDefault();var e=a(this),f=e.attr("data-id"),h=e.attr("data-value"),i="0"===h?1:0,j=b.call([{methodname:"core_tag_update_tags",args:{tags:[{id:f,flag:i}]}},{methodname:"core_tag_get_tags",args:{tags:[{id:f}]}}],!0);a.when.apply(a,j).done(function(b,d){if(void 0===b.warnings[0]&&void 0!==d.tags[0]){var f=e.closest("tr").get(0);f&&(d.tags[0].flag?a(f).addClass("flagged-tag"):a(f).removeClass("flagged-tag")),c.render("core_tag/tagflag",d.tags[0]).done(function(a){g(e);var b=e.parent();e.replaceWith(a),b.find(".tagflag").get(0).focus()})}})}),a(".tag-management-table").delegate("a.tagdelete","click",function(b){b.preventDefault();var c=a(this).attr("href");e.get_strings([{key:"delete"},{key:"confirmdeletetag",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){window.location.href=c})})}),a("#tag-management-delete").click(function(b){var c=a(this).closest("form").get(0),f=a(c).find("input[type=checkbox]:checked").length;return f?(b.preventDefault(),void e.get_strings([{key:"delete"},{key:"confirmdeletetags",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){c.submit()})})):!1}),a(".tag-management-table").delegate(".tagnameedit","click keypress",function(h){if("keypress"!==h.type||13===h.keyCode){h.stopImmediatePropagation(),h.preventDefault();var i=a(this),j=a(i.closest("td").get(0)),k=a(j.find("input").get(0)),l=i.attr("data-id"),m=function(f,h){var i=b.call([{methodname:"core_tag_update_tags",args:{tags:[{id:f,rawname:h}]}},{methodname:"core_tag_get_tags",args:{tags:[{id:f}]}}],!0);a.when.apply(a,i).done(function(b,f){void 0!==b.warnings[0]?e.get_string("error").done(function(a){d.alert(a,b.warnings[0].message)}):void 0!==f.tags[0]&&c.render("core_tag/tagname",f.tags[0]).done(function(b){g(j),j.html(b),a(j.find(".tagnameedit").get(0)).focus()})})},n=function(){a(".tag-management-table td.tageditingon").each(function(){var b=a(this),c=a(b.find("input").get(0));c.off(),b.removeClass("tageditingon"),c.val(b.attr("data-value"))})};n(),j.addClass("tageditingon"),j.attr("data-value",k.val()),k.select(),k.on("keypress focusout",function(a){f.behatsiterunning&&"focusout"===a.type||("keypress"===a.type&&13===a.keyCode&&(m(l,k.val()),n()),("keypress"===a.type&&27===a.keyCode||"focusout"===a.type)&&n())})}})}}});
\ No newline at end of file
+define(["jquery","core/ajax","core/templates","core/notification","core/str","core/config"],function(a,b,c,d,e,f){return{init_tagindex_page:function(){a("body").delegate(".tagarea[data-ta] a[data-quickload=1]","click",function(d){d.preventDefault();var e=a(this),f=e.context.search.replace(/^\?/,""),g=e.closest(".tagarea[data-ta]"),h=f.split("&").reduce(function(a,b){var c=b.split("=");return a[c[0]]=decodeURIComponent(c[1]),a},{}),i=b.call([{methodname:"core_tag_get_tagindex",args:{tagindex:h}}],!0);a.when.apply(a,i).done(function(a){c.render("core_tag/index",a).done(function(a){g.replaceWith(a)})})})},init_manage_page:function(){var g=function(b){var c=b.closest("tr").get(0);if(c){var d=a(c).find("td.col-timemodified").get(0);e.get_string("now").done(function(b){a(d).html(b)})}};a(".tag-management-table").delegate(".tagtype","click",function(d){d.preventDefault();var e=a(this),f=e.attr("data-id"),h=e.attr("data-value"),i="1"===h?0:1,j=b.call([{methodname:"core_tag_update_tags",args:{tags:[{id:f,official:i}]}},{methodname:"core_tag_get_tags",args:{tags:[{id:f}]}}],!0);a.when.apply(a,j).done(function(a,b){void 0===a.warnings[0]&&void 0!==b.tags[0]&&c.render("core_tag/tagtype",b.tags[0]).done(function(a){g(e);var b=e.parent();e.replaceWith(a),b.find(".tagtype").get(0).focus()})})}),a(".tag-management-table").delegate(".tagflag","click",function(d){d.preventDefault();var e=a(this),f=e.attr("data-id"),h=e.attr("data-value"),i="0"===h?1:0,j=b.call([{methodname:"core_tag_update_tags",args:{tags:[{id:f,flag:i}]}},{methodname:"core_tag_get_tags",args:{tags:[{id:f}]}}],!0);a.when.apply(a,j).done(function(b,d){if(void 0===b.warnings[0]&&void 0!==d.tags[0]){var f=e.closest("tr").get(0);f&&(d.tags[0].flag?a(f).addClass("flagged-tag"):a(f).removeClass("flagged-tag")),c.render("core_tag/tagflag",d.tags[0]).done(function(a){g(e);var b=e.parent();e.replaceWith(a),b.find(".tagflag").get(0).focus()})}})}),a(".tag-management-table").delegate("a.tagdelete","click",function(b){b.preventDefault();var c=a(this).attr("href");e.get_strings([{key:"delete"},{key:"confirmdeletetag",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){window.location.href=c})})}),a("#tag-management-delete").click(function(b){var c=a(this).closest("form").get(0),f=a(c).find("input[type=checkbox]:checked").length;return f?(b.preventDefault(),void e.get_strings([{key:"delete"},{key:"confirmdeletetags",component:"tag"},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],function(){c.submit()})})):!1}),a(".tag-management-table").delegate(".tagnameedit","click keypress",function(h){if("keypress"!==h.type||13===h.keyCode){h.stopImmediatePropagation(),h.preventDefault();var i=a(this),j=a(i.closest("td").get(0)),k=a(j.find("input").get(0)),l=i.attr("data-id"),m=function(f,h){var i=b.call([{methodname:"core_tag_update_tags",args:{tags:[{id:f,rawname:h}]}},{methodname:"core_tag_get_tags",args:{tags:[{id:f}]}}],!0);a.when.apply(a,i).done(function(b,f){void 0!==b.warnings[0]?e.get_string("error").done(function(a){d.alert(a,b.warnings[0].message)}):void 0!==f.tags[0]&&c.render("core_tag/tagname",f.tags[0]).done(function(b){g(j),j.html(b),a(j.find(".tagnameedit").get(0)).focus()})})},n=function(){a(".tag-management-table td.tageditingon").each(function(){var b=a(this),c=a(b.find("input").get(0));c.off(),b.removeClass("tageditingon"),c.val(b.attr("data-value"))})};n(),j.addClass("tageditingon"),j.attr("data-value",k.val()),k.select(),k.on("keypress focusout",function(a){f.behatsiterunning&&"focusout"===a.type||("keypress"===a.type&&13===a.keyCode&&(m(l,k.val()),n()),("keypress"===a.type&&27===a.keyCode||"focusout"===a.type)&&n())})}})}}});
\ No newline at end of file
diff --git a/lib/amd/src/tag.js b/lib/amd/src/tag.js
index 0edf6ef2aa4..01ef814a7ac 100644
--- a/lib/amd/src/tag.js
+++ b/lib/amd/src/tag.js
@@ -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() {
diff --git a/lib/blocklib.php b/lib/blocklib.php
index 56208b6fdc6..b045eb2e481 100644
--- a/lib/blocklib.php
+++ b/lib/blocklib.php
@@ -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());
diff --git a/lib/classes/event/tag_added.php b/lib/classes/event/tag_added.php
index 6316cc35012..fecf439bbd0 100644
--- a/lib/classes/event/tag_added.php
+++ b/lib/classes/event/tag_added.php
@@ -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().
*
diff --git a/lib/classes/event/tag_collection_created.php b/lib/classes/event/tag_collection_created.php
new file mode 100644
index 00000000000..57573d00cc5
--- /dev/null
+++ b/lib/classes/event/tag_collection_created.php
@@ -0,0 +1,82 @@
+.
+
+/**
+ * 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'";
+ }
+}
diff --git a/lib/classes/event/tag_collection_deleted.php b/lib/classes/event/tag_collection_deleted.php
new file mode 100644
index 00000000000..f9b332faa39
--- /dev/null
+++ b/lib/classes/event/tag_collection_deleted.php
@@ -0,0 +1,82 @@
+.
+
+/**
+ * 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'";
+ }
+}
diff --git a/lib/classes/event/tag_collection_updated.php b/lib/classes/event/tag_collection_updated.php
new file mode 100644
index 00000000000..0417b0f1a46
--- /dev/null
+++ b/lib/classes/event/tag_collection_updated.php
@@ -0,0 +1,82 @@
+.
+
+/**
+ * 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'";
+ }
+}
diff --git a/lib/classes/event/tag_created.php b/lib/classes/event/tag_created.php
index e239b392a74..9573a494f61 100644
--- a/lib/classes/event/tag_created.php
+++ b/lib/classes/event/tag_created.php
@@ -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.
*
diff --git a/lib/classes/event/tag_removed.php b/lib/classes/event/tag_removed.php
index 04558aca24e..faa36109b6d 100644
--- a/lib/classes/event/tag_removed.php
+++ b/lib/classes/event/tag_removed.php
@@ -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.
*
diff --git a/lib/classes/task/tag_cron_task.php b/lib/classes/task/tag_cron_task.php
index cdedf29f4a8..721edfbc95f 100644
--- a/lib/classes/task/tag_cron_task.php
+++ b/lib/classes/task/tag_cron_task.php
@@ -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
+ JOIN {tag_instance} tb ON (ta.itemtype = tb.itemtype AND ta.component = tb.component
+ AND ta.itemid = tb.itemid AND ta.tagid <> tb.tagid)
+ JOIN {tag} tgb ON tb.tagid = tgb.id AND tgb.tagcollid = tga.tagcollid
+ GROUP BY ta.tagid, tb.tagid
+ HAVING COUNT(*) > :mincorrelation
+ ) pairs
+ LEFT JOIN {tag_correlation} co ON co.tagid = pairs.tagid
+ ORDER BY pairs.tagid ASC, pairs.ocurrences DESC, pairs.correlation ASC';
+ $rs = $DB->get_recordset_sql($sql, array('mincorrelation' => $mincorrelation));
+
+ // Set up an empty tag correlation object.
+ $tagcorrelation = new stdClass;
+ $tagcorrelation->id = null;
+ $tagcorrelation->tagid = null;
+ $tagcorrelation->correlatedtags = array();
+
+ // We store each correlation id in this array so we can remove any correlations
+ // that no longer exist.
+ $correlations = array();
+
+ // Iterate each row of the result set and build them into tag correlations.
+ // We add all of a tag's correlations to $tagcorrelation->correlatedtags[]
+ // then save the $tagcorrelation object.
+ foreach ($rs as $row) {
+ if ($row->tagid != $tagcorrelation->tagid) {
+ // The tag id has changed so we have all of the correlations for this tag.
+ $tagcorrelationid = $this->process_computed_correlation($tagcorrelation);
+ if ($tagcorrelationid) {
+ $correlations[] = $tagcorrelationid;
+ }
+ // Now we reset the tag correlation object so we can reuse it and set it
+ // up for the current record.
+ $tagcorrelation = new stdClass;
+ $tagcorrelation->id = $row->correlationid;
+ $tagcorrelation->tagid = $row->tagid;
+ $tagcorrelation->correlatedtags = array();
+ }
+ // Save the correlation on the tag correlation object.
+ $tagcorrelation->correlatedtags[] = $row->correlation;
+ }
+ // Update the current correlation after the last record.
+ $tagcorrelationid = $this->process_computed_correlation($tagcorrelation);
+ if ($tagcorrelationid) {
+ $correlations[] = $tagcorrelationid;
+ }
+
+ // Close the recordset.
+ $rs->close();
+
+ // Remove any correlations that weren't just identified.
+ if (empty($correlations)) {
+ // There are no tag correlations.
+ $DB->delete_records('tag_correlation');
+ } else {
+ list($sql, $params) = $DB->get_in_or_equal($correlations,
+ SQL_PARAMS_NAMED, 'param0000', false);
+ $DB->delete_records_select('tag_correlation', 'id '.$sql, $params);
+ }
+ }
+
+ /**
+ * Clean up the tag tables, making sure all tagged object still exists.
+ *
+ * This method is called from cron.
+ *
+ * This should normally not be necessary, but in case related tags are not deleted
+ * when the tagged record is removed, this should be done once in a while, perhaps
+ * on an occasional cron run. On a site with lots of tags, this could become an
+ * expensive function to call.
+ */
+ public function cleanup() {
+ global $DB;
+
+ // Get ids to delete from instances where the tag has been deleted. This should never happen apparently.
+ $sql = "SELECT ti.id
+ FROM {tag_instance} ti
+ LEFT JOIN {tag} t ON t.id = ti.tagid
+ WHERE t.id IS null";
+ $tagids = $DB->get_records_sql($sql);
+ $tagarray = array();
+ foreach ($tagids as $tagid) {
+ $tagarray[] = $tagid->id;
+ }
+
+ // Next get ids from instances that have an owner that has been deleted.
+ $sql = "SELECT ti.id
+ FROM {tag_instance} ti, {user} u
+ WHERE ti.itemid = u.id
+ AND ti.itemtype = 'user'
+ AND ti.component = 'core'
+ AND u.deleted = 1";
+ $tagids = $DB->get_records_sql($sql);
+ foreach ($tagids as $tagid) {
+ $tagarray[] = $tagid->id;
+ }
+
+ // Get the other itemtypes.
+ $sql = "SELECT DISTINCT component, itemtype
+ FROM {tag_instance}
+ WHERE itemtype <> 'user' or component <> 'core'";
+ $tagareas = $DB->get_records_sql($sql);
+ foreach ($tagareas as $tagarea) {
+ $sql = 'SELECT ti.id
+ FROM {tag_instance} ti
+ LEFT JOIN {' . $tagarea->itemtype . '} it ON it.id = ti.itemid
+ WHERE it.id IS null
+ AND ti.itemtype = ? AND ti.component = ?';
+ $tagids = $DB->get_records_sql($sql, array($tagarea->itemtype, $tagarea->component));
+ foreach ($tagids as $tagid) {
+ $tagarray[] = $tagid->id;
+ }
+ }
+
+ // Get instances for each of the ids to be deleted.
+ if (count($tagarray) > 0) {
+ list($sqlin, $params) = $DB->get_in_or_equal($tagarray);
+ $sql = "SELECT ti.*, COALESCE(t.name, 'deleted') AS name, COALESCE(t.rawname, 'deleted') AS rawname
+ FROM {tag_instance} ti
+ LEFT JOIN {tag} t ON t.id = ti.tagid
+ WHERE ti.id $sqlin";
+ $instances = $DB->get_records_sql($sql, $params);
+ $this->bulk_delete_instances($instances);
+ }
+
+ core_tag_collection::cleanup_unused_tags();
+ }
+
+ /**
+ * This function processes a tag correlation and makes changes in the database as required.
+ *
+ * The tag correlation object needs have both a tagid property and a correlatedtags property that is an array.
+ *
+ * @param stdClass $tagcorrelation
+ * @return int/bool The id of the tag correlation that was just processed or false.
+ */
+ public function process_computed_correlation(stdClass $tagcorrelation) {
+ global $DB;
+
+ // You must provide a tagid and correlatedtags must be set and be an array.
+ if (empty($tagcorrelation->tagid) || !isset($tagcorrelation->correlatedtags) ||
+ !is_array($tagcorrelation->correlatedtags)) {
+ return false;
+ }
+
+ $tagcorrelation->correlatedtags = join(',', $tagcorrelation->correlatedtags);
+ if (!empty($tagcorrelation->id)) {
+ // The tag correlation already exists so update it.
+ $DB->update_record('tag_correlation', $tagcorrelation);
+ } else {
+ // This is a new correlation to insert.
+ $tagcorrelation->id = $DB->insert_record('tag_correlation', $tagcorrelation);
+ }
+ return $tagcorrelation->id;
+ }
+
+ /**
+ * This function will delete numerous tag instances efficiently.
+ * This removes tag instances only. It doesn't check to see if it is the last use of a tag.
+ *
+ * @param array $instances An array of tag instance objects with the addition of the tagname and tagrawname
+ * (used for recording a delete event).
+ */
+ public function bulk_delete_instances($instances) {
+ global $DB;
+
+ $instanceids = array();
+ foreach ($instances as $instance) {
+ $instanceids[] = $instance->id;
+ }
+
+ // This is a multi db compatible method of creating the correct sql when using the 'IN' value.
+ // $insql is the sql statement, $params are the id numbers.
+ list($insql, $params) = $DB->get_in_or_equal($instanceids);
+ $sql = 'id ' . $insql;
+ $DB->delete_records_select('tag_instance', $sql, $params);
+
+ // Now go through and record each tag individually with the event system.
+ foreach ($instances as $instance) {
+ // Trigger tag removed event (i.e. The tag instance has been removed).
+ \core\event\tag_removed::create_from_tag_instance($instance, $instance->name,
+ $instance->rawname, true)->trigger();
+ }
+ }
}
diff --git a/lib/coursecatlib.php b/lib/coursecatlib.php
index df83db66717..fc8811f8b54 100644
--- a/lib/coursecatlib.php
+++ b/lib/coursecatlib.php
@@ -1339,8 +1339,27 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
} else if (!empty($search['tagid'])) {
// Search courses that are tagged with the specified tag.
$where = "c.id IN (SELECT t.itemid ".
- "FROM {tag_instance} t WHERE t.tagid = :tagid AND t.itemtype = :itemtype)";
- $params = array('tagid' => $search['tagid'], 'itemtype' => 'course');
+ "FROM {tag_instance} t WHERE t.tagid = :tagid AND t.itemtype = :itemtype AND t.component = :component)";
+ $params = array('tagid' => $search['tagid'], 'itemtype' => 'course', 'component' => 'core');
+ if (!empty($search['ctx'])) {
+ $rec = isset($search['rec']) ? $search['rec'] : true;
+ $parentcontext = context::instance_by_id($search['ctx']);
+ if ($parentcontext->contextlevel == CONTEXT_SYSTEM && $rec) {
+ // Parent context is system context and recursive is set to yes.
+ // Nothing to filter - all courses fall into this condition.
+ } else if ($rec) {
+ // Filter all courses in the parent context at any level.
+ $where .= ' AND ctx.path LIKE :contextpath';
+ $params['contextpath'] = $parentcontext->path . '%';
+ } else if ($parentcontext->contextlevel == CONTEXT_COURSECAT) {
+ // All courses in the given course category.
+ $where .= ' AND c.category = :category';
+ $params['category'] = $parentcontext->instanceid;
+ } else {
+ // No courses will satisfy the context criterion, do not bother searching.
+ $where = '1=0';
+ }
+ }
} else {
debugging('No criteria is specified while searching courses', DEBUG_DEVELOPER);
return array();
diff --git a/lib/db/caches.php b/lib/db/caches.php
index 7d90e55efeb..dd8dc960aee 100644
--- a/lib/db/caches.php
+++ b/lib/db/caches.php
@@ -252,6 +252,12 @@ $definitions = array(
'simpledata' => true,
'staticacceleration' => true,
'staticaccelerationsize' => 5
+ ),
+
+ // Caches data about tag collections and areas.
+ 'tags' => array(
+ 'mode' => cache_store::MODE_REQUEST,
+ 'simplekeys' => true,
)
);
diff --git a/lib/db/install.xml b/lib/db/install.xml
index de8750aa922..3c9e4204511 100644
--- a/lib/db/install.xml
+++ b/lib/db/install.xml
@@ -1,5 +1,5 @@
-
@@ -1958,10 +1958,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1973,9 +2006,11 @@
+
-
+
+
@@ -1993,8 +2028,8 @@
-
-
+
+
@@ -2008,7 +2043,8 @@
-
+
+
diff --git a/lib/db/services.php b/lib/db/services.php
index d9867da6dce..bc3230f3f4d 100644
--- a/lib/db/services.php
+++ b/lib/db/services.php
@@ -1148,6 +1148,14 @@ $functions = array(
'ajax' => true
),
+ 'core_tag_get_tagindex' => array(
+ 'classname' => 'core_tag_external',
+ 'methodname' => 'get_tagindex',
+ 'description' => 'Gets tag index page for one tag and one tag area',
+ 'type' => 'read',
+ 'ajax' => true
+ ),
+
);
$services = array(
diff --git a/lib/db/tag.php b/lib/db/tag.php
new file mode 100644
index 00000000000..2923bb56fb5
--- /dev/null
+++ b/lib/db/tag.php
@@ -0,0 +1,80 @@
+.
+
+/**
+ * Tag area definitions
+ *
+ * File db/tag.php lists all available tag areas in core or a plugin.
+ *
+ * Each tag area may have the following attributes:
+ * - itemtype (required) - what is tagged. Must be name of the existing DB table
+ * - component - component responsible for tagging, if the tag area is inside a
+ * plugin the component must be the full frankenstyle name of the plugin
+ * - collection - name of the custom tag collection that will be used to store
+ * tags in this area. If specified aministrator will be able to neither add
+ * any other tag areas to this collection nor move this tag area elsewhere
+ * - searchable (only if collection is specified) - wether the tag collection
+ * should be searchable on /tag/search.php
+ * - customurl (only if collection is specified) - custom url to use instead of
+ * /tag/search.php to display information about one tag
+ * - callback - name of the function that returns items tagged with this tag,
+ * see core_tag_tag::get_tag_index() and existing callbacks for more details,
+ * callback should return instance of core_tag\output\tagindex
+ * - callbackfile - file where callback is located (if not an autoloaded location)
+ *
+ * Language file must contain the human-readable names of the tag areas and
+ * collections (either in plugin language file or in component language file or
+ * lang/en/tag.php in case of core):
+ * - for item type "user":
+ * $string['tagarea_user'] = 'Users';
+ * - for tag collection "mycollection":
+ * $string['tagcollection_mycollection'] = 'My tag collection';
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$tagareas = array(
+ array(
+ 'itemtype' => 'user', // Users.
+ 'component' => 'core',
+ 'callback' => 'user_get_tagged_users',
+ 'callbackfile' => '/user/lib.php',
+ ),
+ array(
+ 'itemtype' => 'course', // Courses.
+ 'component' => 'core',
+ 'callback' => 'course_get_tagged_courses',
+ 'callbackfile' => '/course/lib.php',
+ ),
+ array(
+ 'itemtype' => 'question', // Questions.
+ 'component' => 'core_question',
+ ),
+ array(
+ 'itemtype' => 'post', // Blog posts.
+ 'component' => 'core',
+ 'callback' => 'blog_get_tagged_posts',
+ 'callbackfile' => '/blog/lib.php',
+ ),
+ array(
+ 'itemtype' => 'blog_external', // External blogs.
+ 'component' => 'core',
+ ),
+);
diff --git a/lib/db/tasks.php b/lib/db/tasks.php
index 9723d334beb..41991f15071 100644
--- a/lib/db/tasks.php
+++ b/lib/db/tasks.php
@@ -71,8 +71,8 @@ $tasks = array(
array(
'classname' => 'core\task\tag_cron_task',
'blocking' => 0,
- 'minute' => '20',
- 'hour' => '*',
+ 'minute' => 'R',
+ 'hour' => '3',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index c5d6eea37dc..7a306e99d81 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -4620,5 +4620,191 @@ function xmldb_main_upgrade($oldversion) {
// Moodle v3.0.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2016011100.00) {
+
+ // This is a big upgrade script. We create new table tag_coll and the field
+ // tag.tagcollid pointing to it.
+
+ // Define table tag_coll to be created.
+ $table = new xmldb_table('tag_coll');
+
+ // Adding fields to table tagcloud.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+ $table->add_field('isdefault', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('searchable', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1');
+ $table->add_field('customurl', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+
+ // Adding keys to table tagcloud.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+ // Conditionally launch create table for tagcloud.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Table {tag}.
+ // Define index name (unique) to be dropped form tag - we will replace it with index on (tagcollid,name) later.
+ $table = new xmldb_table('tag');
+ $index = new xmldb_index('name', XMLDB_INDEX_UNIQUE, array('name'));
+
+ // Conditionally launch drop index name.
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+
+ // Define field tagcollid to be added to tag, we create it as null first and will change to notnull later.
+ $table = new xmldb_table('tag');
+ $field = new xmldb_field('tagcollid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'userid');
+
+ // Conditionally launch add field tagcloudid.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011100.00);
+ }
+
+ if ($oldversion < 2016011100.02) {
+ // Create a default tag collection if not exists and update the field tag.tagcollid to point to it.
+ if (!$tcid = $DB->get_field_sql('SELECT id FROM {tag_coll} ORDER BY isdefault DESC, sortorder, id', null,
+ IGNORE_MULTIPLE)) {
+ $tcid = $DB->insert_record('tag_coll', array('isdefault' => 1, 'sortorder' => 0));
+ }
+ $DB->execute('UPDATE {tag} SET tagcollid = ? WHERE tagcollid IS NULL', array($tcid));
+
+ // Define index tagcollname (unique) to be added to tag.
+ $table = new xmldb_table('tag');
+ $index = new xmldb_index('tagcollname', XMLDB_INDEX_UNIQUE, array('tagcollid', 'name'));
+ $field = new xmldb_field('tagcollid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'userid');
+
+ // Conditionally launch add index tagcollname.
+ if (!$dbman->index_exists($table, $index)) {
+ // Launch change of nullability for field tagcollid.
+ $dbman->change_field_notnull($table, $field);
+ $dbman->add_index($table, $index);
+ }
+
+ // Define key tagcollid (foreign) to be added to tag.
+ $table = new xmldb_table('tag');
+ $key = new xmldb_key('tagcollid', XMLDB_KEY_FOREIGN, array('tagcollid'), 'tag_coll', array('id'));
+
+ // Launch add key tagcloudid.
+ $dbman->add_key($table, $key);
+
+ // Define index tagcolltype (not unique) to be added to tag.
+ $table = new xmldb_table('tag');
+ $index = new xmldb_index('tagcolltype', XMLDB_INDEX_NOTUNIQUE, array('tagcollid', 'tagtype'));
+
+ // Conditionally launch add index tagcolltype.
+ if (!$dbman->index_exists($table, $index)) {
+ $dbman->add_index($table, $index);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011100.02);
+ }
+
+ if ($oldversion < 2016011100.03) {
+
+ // Define table tag_area to be created.
+ $table = new xmldb_table('tag_area');
+
+ // Adding fields to table tag_area.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('itemtype', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('enabled', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1');
+ $table->add_field('tagcollid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('callback', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('callbackfile', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+
+ // Adding keys to table tag_area.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('tagcollid', XMLDB_KEY_FOREIGN, array('tagcollid'), 'tag_coll', array('id'));
+
+ // Adding indexes to table tag_area.
+ $table->add_index('compitemtype', XMLDB_INDEX_UNIQUE, array('component', 'itemtype'));
+
+ // Conditionally launch create table for tag_area.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011100.03);
+ }
+
+ if ($oldversion < 2016011100.12) {
+
+ // Define index itemtype-itemid-tagid-tiuserid (unique) to be dropped form tag_instance.
+ $table = new xmldb_table('tag_instance');
+ $index = new xmldb_index('itemtype-itemid-tagid-tiuserid', XMLDB_INDEX_UNIQUE,
+ array('itemtype', 'itemid', 'tagid', 'tiuserid'));
+
+ // Conditionally launch drop index itemtype-itemid-tagid-tiuserid.
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011100.12);
+ }
+
+ if ($oldversion < 2016011100.13) {
+
+ $DB->execute("UPDATE {tag_instance} SET component = ? WHERE component IS NULL", array(''));
+
+ // Changing nullability of field component on table tag_instance to not null.
+ $table = new xmldb_table('tag_instance');
+ $field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, 'tagid');
+
+ // Launch change of nullability for field component.
+ $dbman->change_field_notnull($table, $field);
+
+ // Changing type of field itemtype on table tag_instance to char.
+ $table = new xmldb_table('tag_instance');
+ $field = new xmldb_field('itemtype', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, 'component');
+
+ // Launch change of type for field itemtype.
+ $dbman->change_field_type($table, $field);
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011100.13);
+ }
+
+ if ($oldversion < 2016011100.14) {
+
+ // Define index taggeditem (unique) to be added to tag_instance.
+ $table = new xmldb_table('tag_instance');
+ $index = new xmldb_index('taggeditem', XMLDB_INDEX_UNIQUE, array('component', 'itemtype', 'itemid', '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, 2016011100.14);
+ }
+
+ if ($oldversion < 2016011100.15) {
+
+ // Define index taglookup (not unique) to be added to tag_instance.
+ $table = new xmldb_table('tag_instance');
+ $index = new xmldb_index('taglookup', XMLDB_INDEX_NOTUNIQUE, array('itemtype', 'component', 'tagid', 'contextid'));
+
+ // Conditionally launch add index taglookup.
+ if (!$dbman->index_exists($table, $index)) {
+ $dbman->add_index($table, $index);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011100.15);
+ }
+
return true;
}
diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php
index cd38aa4d44f..f20c2d1349c 100644
--- a/lib/deprecatedlib.php
+++ b/lib/deprecatedlib.php
@@ -2617,30 +2617,17 @@ function coursetag_store_keywords($tags, $courseid, $userid=0, $tagtype='officia
debugging('Function coursetag_store_keywords() is deprecated. Userid is no longer used for tagging courses.', DEBUG_DEVELOPER);
global $CFG;
- require_once $CFG->dirroot.'/tag/lib.php';
if (is_array($tags) and !empty($tags)) {
+ if ($tagtype === 'official') {
+ $tagcoll = core_tag_area::get_collection('core', 'course');
+ // We don't normally need to create tags, they are created automatically when added to items. but we do here because we want them to be official.
+ core_tag_tag::create_if_missing($tagcoll, $tags, true);
+ }
foreach ($tags as $tag) {
$tag = trim($tag);
if (strlen($tag) > 0) {
- //tag_set_add('course', $courseid, $tag, $userid); //deletes official tags
-
- //add tag if does not exist
- if (!$tagid = tag_get_id($tag)) {
- $tag_id_array = tag_add(array($tag), $tagtype);
- $tagid = $tag_id_array[core_text::strtolower($tag)];
- }
- //ordering
- $ordering = 0;
- if ($current_ids = tag_get_tags_ids('course', $courseid)) {
- end($current_ids);
- $ordering = key($current_ids) + 1;
- }
- //set type
- tag_type_set($tagid, $tagtype);
-
- //tag_instance entry
- tag_assign('course', $courseid, $tagid, $ordering, $userid, 'core', context_course::instance($courseid)->id);
+ core_tag_tag::add_item_tag('core', 'course', $courseid, context_course::instance($courseid), $tag, $userid);
}
}
}
@@ -2660,7 +2647,8 @@ function coursetag_store_keywords($tags, $courseid, $userid=0, $tagtype='officia
function coursetag_delete_keyword($tagid, $userid, $courseid) {
debugging('Function coursetag_delete_keyword() is deprecated. Userid is no longer used for tagging courses.', DEBUG_DEVELOPER);
- tag_delete_instance('course', $courseid, $tagid, $userid);
+ $tag = core_tag_tag::get($tagid);
+ core_tag_tag::remove_item_tag('core', 'course', $courseid, $tag->rawname, $userid);
}
/**
@@ -2709,24 +2697,431 @@ function coursetag_get_tagged_courses($tagid) {
* @param bool $showfeedback if we should output a notification of the delete to the end user
*/
function coursetag_delete_course_tags($courseid, $showfeedback=false) {
- debugging('Function coursetag_delete_course_tags() is deprecated. Userid is no longer used for tagging courses.', DEBUG_DEVELOPER);
+ debugging('Function coursetag_delete_course_tags() is deprecated. Use core_tag_tag::remove_all_item_tags().', DEBUG_DEVELOPER);
- global $DB, $OUTPUT;
-
- if ($taginstances = $DB->get_recordset_select('tag_instance', "itemtype = 'course' AND itemid = :courseid",
- array('courseid' => $courseid), '', 'tagid, tiuserid')) {
-
- foreach ($taginstances as $record) {
- tag_delete_instance('course', $courseid, $record->tagid, $record->tiuserid);
- }
- $taginstances->close();
- }
+ global $OUTPUT;
+ core_tag_tag::remove_all_item_tags('core', 'course', $courseid);
if ($showfeedback) {
echo $OUTPUT->notification(get_string('deletedcoursetags', 'tag'), 'notifysuccess');
}
}
+/**
+ * Set the type of a tag. At this time (version 2.2) the possible values are 'default' or 'official'. Official tags will be
+ * displayed separately "at tagging time" (while selecting the tags to apply to a record).
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $tagid tagid to modify
+ * @param string $type either 'default' or 'official'
+ * @return bool true on success, false otherwise
+ */
+function tag_type_set($tagid, $type) {
+ debugging('Function tag_type_set() is deprecated and can be replaced with use core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ return $tag->update(array('tagtype' => $type));
+ }
+ return false;
+}
+
+/**
+ * Set the description of a tag
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param int $tagid the id of the tag
+ * @param string $description the tag's description string to be set
+ * @param int $descriptionformat the moodle text format of the description
+ * {@link http://docs.moodle.org/dev/Text_formats_2.0#Database_structure}
+ * @return bool true on success, false otherwise
+ */
+function tag_description_set($tagid, $description, $descriptionformat) {
+ debugging('Function tag_type_set() is deprecated and can be replaced with core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ return $tag->update(array('description' => $description, 'descriptionformat' => $descriptionformat));
+ }
+ return false;
+}
+
+/**
+ * Get the array of db record of tags associated to a record (instances).
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned.
+ * @param int $userid (optional) only required for course tagging
+ * @return array the array of tags
+ */
+function tag_get_tags($record_type, $record_id, $type=null, $userid=0) {
+ debugging('Method tag_get_tags() is deprecated and replaced with core_tag_tag::get_item_tags(). ' .
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+ $tags = core_tag_tag::get_item_tags(null, $record_type, $record_id, $official, $userid);
+ $rv = array();
+ foreach ($tags as $id => $t) {
+ $rv[$id] = $t->to_object();
+ }
+ return $rv;
+}
+
+/**
+ * Get the array of tags display names, indexed by id.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned.
+ * @return array the array of tags (with the value returned by core_tag_tag::make_display_name), indexed by id
+ */
+function tag_get_tags_array($record_type, $record_id, $type=null) {
+ debugging('Method tag_get_tags_array() is deprecated and replaced with core_tag_tag::get_item_tags_array(). ' .
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+ return core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official);
+}
+
+/**
+ * Get a comma-separated string of tags associated to a record.
+ *
+ * Use {@link tag_get_tags()} to get the same information in an array.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @param int $html either TAG_RETURN_HTML or TAG_RETURN_TEXT, depending on the type of output desired
+ * @param string $type either 'official' or 'default', if null, all tags are returned
+ * @return string the comma-separated list of tags.
+ */
+function tag_get_tags_csv($record_type, $record_id, $html=null, $type=null) {
+ global $CFG, $OUTPUT;
+ debugging('Method tag_get_tags_csv() is deprecated. Instead you should use either ' .
+ 'core_tag_tag::get_item_tags_array() or $OUTPUT->tag_list(core_tag_tag::get_item_tags()). ' .
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+ if ($html != TAG_RETURN_TEXT) {
+ return $OUTPUT->tag_list(core_tag_tag::get_item_tags('', $record_type, $record_id, $official), '');
+ } else {
+ return join(', ', core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official, 0, false));
+ }
+}
+
+/**
+ * Get an array of tag ids associated to a record.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @return array tag ids, indexed and sorted by 'ordering'
+ */
+function tag_get_tags_ids($record_type, $record_id) {
+ debugging('Method tag_get_tags_ids() is deprecated. Please consider using core_tag_tag::get_item_tags() or similar methods.', DEBUG_DEVELOPER);
+ $tag_ids = array();
+ $tagobjects = core_tag_tag::get_item_tags(null, $record_type, $record_id);
+ foreach ($tagobjects as $tagobject) {
+ $tag = $tagobject->to_object();
+ if ( array_key_exists($tag->ordering, $tag_ids) ) {
+ $tag->ordering++;
+ }
+ $tag_ids[$tag->ordering] = $tag->id;
+ }
+ ksort($tag_ids);
+ return $tag_ids;
+}
+
+/**
+ * Returns the database ID of a set of tags.
+ *
+ * @deprecated since 3.1
+ * @param mixed $tags one tag, or array of tags, to look for.
+ * @param bool $return_value specify the type of the returned value. Either TAG_RETURN_OBJECT, or TAG_RETURN_ARRAY (default).
+ * If TAG_RETURN_ARRAY is specified, an array will be returned even if only one tag was passed in $tags.
+ * @return mixed tag-indexed array of ids (or objects, if second parameter is TAG_RETURN_OBJECT), or only an int, if only one tag
+ * is given *and* the second parameter is null. No value for a key means the tag wasn't found.
+ */
+function tag_get_id($tags, $return_value = null) {
+ global $CFG, $DB;
+ debugging('Method tag_get_id() is deprecated and can be replaced with core_tag_tag::get_by_name() or core_tag_tag::get_by_name_bulk(). ' .
+ 'You need to specify tag collection when retrieving tag by name', DEBUG_DEVELOPER);
+
+ if (!is_array($tags)) {
+ if(is_null($return_value) || $return_value == TAG_RETURN_OBJECT) {
+ if ($tagobject = core_tag_tag::get_by_name(core_tag_collection::get_default(), $tags)) {
+ return $tagobject->id;
+ } else {
+ return 0;
+ }
+ }
+ $tags = array($tags);
+ }
+
+ $records = core_tag_tag::get_by_name_bulk(core_tag_collection::get_default(), $tags,
+ $return_value == TAG_RETURN_OBJECT ? '*' : 'id, name');
+ foreach ($records as $name => $record) {
+ if ($return_value != TAG_RETURN_OBJECT) {
+ $records[$name] = $record->id ? $record->id : null;
+ } else {
+ $records[$name] = $record->to_object();
+ }
+ }
+ return $records;
+}
+
+/**
+ * Change the "value" of a tag, and update the associated 'name'.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param int $tagid the id of the tag to modify
+ * @param string $newrawname the new rawname
+ * @return bool true on success, false otherwise
+ */
+function tag_rename($tagid, $newrawname) {
+ debugging('Function tag_rename() is deprecated and may be replaced with core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ return $tag->update(array('rawname' => $newrawname));
+ }
+ return false;
+}
+
+/**
+ * Delete one instance of a tag. If the last instance was deleted, it will also delete the tag, unless its type is 'official'.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the type of the record for which to remove the instance
+ * @param int $record_id the id of the record for which to remove the instance
+ * @param int $tagid the tagid that needs to be removed
+ * @param int $userid (optional) the userid
+ * @return bool true on success, false otherwise
+ */
+function tag_delete_instance($record_type, $record_id, $tagid, $userid = null) {
+ debugging('Function tag_delete_instance() is deprecated and replaced with core_tag_tag::remove_item_tag() instead. ' .
+ 'Component is required for retrieving instances', DEBUG_DEVELOPER);
+ $tag = core_tag_tag::get($tagid);
+ core_tag_tag::remove_item_tag('', $record_type, $record_id, $tag->rawname, $userid);
+}
+
+/**
+ * Find all records tagged with a tag of a given type ('post', 'user', etc.)
+ *
+ * @package core_tag
+ * @category tag
+ * @param string $tag tag to look for
+ * @param string $type type to restrict search to. If null, every matching record will be returned
+ * @param int $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point.
+ * @param int $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records.
+ * @return array of matching objects, indexed by record id, from the table containing the type requested
+ */
+function tag_find_records($tag, $type, $limitfrom='', $limitnum='') {
+ debugging('Function tag_find_records() is deprecated and replaced with core_tag_tag::get_by_name()->get_tagged_items(). '.
+ 'You need to specify tag collection when retrieving tag by name', DEBUG_DEVELOPER);
+
+ if (!$tag || !$type) {
+ return array();
+ }
+
+ $tagobject = core_tag_tag::get_by_name(core_tag_area::get_collection('', $type), $tag);
+ return $tagobject->get_tagged_items('', $type, $limitfrom, $limitnum);
+}
+
+/**
+ * Adds one or more tag in the database. This function should not be called directly : you should
+ * use tag_set.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param mixed $tags one tag, or an array of tags, to be created
+ * @param string $type type of tag to be created ("default" is the default value and "official" is the only other supported
+ * value at this time). An official tag is kept even if there are no records tagged with it.
+ * @return array $tags ids indexed by their lowercase normalized names. Any boolean false in the array indicates an error while
+ * adding the tag.
+ */
+function tag_add($tags, $type="default") {
+ debugging('Function tag_add() is deprecated. You can use core_tag_tag::create_if_missing(), however it should not be necessary ' .
+ 'since tags are created automatically when assigned to items', DEBUG_DEVELOPER);
+ if (!is_array($tags)) {
+ $tags = array($tags);
+ }
+ $objects = core_tag_tag::create_if_missing(core_tag_collection::get_default(), $tags, $type === 'official');
+
+ // New function returns the tags in different format, for BC we keep the format that this function used to have.
+ $rv = array();
+ foreach ($objects as $name => $tagobject) {
+ if (isset($tagobject->id)) {
+ $rv[$tagobject->name] = $tagobject->id;
+ } else {
+ $rv[$name] = false;
+ }
+ }
+ return $rv;
+}
+
+/**
+ * Assigns a tag to a record; if the record already exists, the time and ordering will be updated.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the type of the record that will be tagged
+ * @param int $record_id the id of the record that will be tagged
+ * @param string $tagid the tag id to set on the record.
+ * @param int $ordering the order of the instance for this record
+ * @param int $userid (optional) only required for course tagging
+ * @param string|null $component the component that was tagged
+ * @param int|null $contextid the context id of where this tag was assigned
+ * @return bool true on success, false otherwise
+ */
+function tag_assign($record_type, $record_id, $tagid, $ordering, $userid = 0, $component = null, $contextid = null) {
+ global $DB;
+ $message = 'Function tag_assign() is deprecated. Use core_tag_tag::set_item_tags() or core_tag_tag::add_item_tag() instead. ' .
+ 'Tag instance ordering should not be set manually';
+ if ($component === null || $contextid === null) {
+ $message .= '. You should specify the component and contextid of the item being tagged in your call to tag_assign.';
+ }
+ debugging($message, DEBUG_DEVELOPER);
+
+ if ($contextid) {
+ $context = context::instance_by_id($contextid);
+ } else {
+ $context = context_system::instance();
+ }
+
+ // Get the tag.
+ $tag = $DB->get_record('tag', array('id' => $tagid), 'name, rawname', MUST_EXIST);
+
+ $taginstanceid = core_tag_tag::add_item_tag($component, $record_type, $record_id, $context, $tag->rawname, $userid);
+
+ // Alter the "ordering" of tag_instance. This should never be done manually and only remains here for the backward compatibility.
+ $taginstance = new stdClass();
+ $taginstance->id = $taginstanceid;
+ $taginstance->ordering = $ordering;
+ $taginstance->timemodified = time();
+
+ $DB->update_record('tag_instance', $taginstance);
+
+ return true;
+}
+
+/**
+ * Count how many records are tagged with a specific tag.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type record to look for ('post', 'user', etc.)
+ * @param int $tagid is a single tag id
+ * @return int number of mathing tags.
+ */
+function tag_record_count($record_type, $tagid) {
+ debugging('Method tag_record_count() is deprecated and replaced with core_tag_tag::get($tagid)->count_tagged_items(). '.
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ return core_tag_tag::get($tagid)->count_tagged_items('', $record_type);
+}
+
+/**
+ * Determine if a record is tagged with a specific tag
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type to look for
+ * @param int $record_id the record id to look for
+ * @param string $tag a tag name
+ * @return bool/int true if it is tagged, 0 (false) otherwise
+ */
+function tag_record_tagged_with($record_type, $record_id, $tag) {
+ debugging('Method tag_record_tagged_with() is deprecated and replaced with core_tag_tag::get($tagid)->is_item_tagged_with(). '.
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ return core_tag_tag::is_item_tagged_with('', $record_type, $record_id, $tag);
+}
+
+/**
+ * Flag a tag as inappropriate.
+ *
+ * @deprecated since 3.1
+ * @param int|array $tagids a single tagid, or an array of tagids
+ */
+function tag_set_flag($tagids) {
+ debugging('Function tag_set_flag() is deprecated and replaced with core_tag_tag::get($tagid)->flag().', DEBUG_DEVELOPER);
+ $tagids = (array) $tagids;
+ foreach ($tagids as $tagid) {
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ $tag->flag();
+ }
+ }
+}
+
+/**
+ * Remove the inappropriate flag on a tag.
+ *
+ * @deprecated since 3.1
+ * @param int|array $tagids a single tagid, or an array of tagids
+ */
+function tag_unset_flag($tagids) {
+ debugging('Function tag_unset_flag() is deprecated and replaced with core_tag_tag::get($tagid)->reset_flag().', DEBUG_DEVELOPER);
+ $tagids = (array) $tagids;
+ foreach ($tagids as $tagid) {
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ $tag->reset_flag();
+ }
+ }
+}
+
+/**
+ * Prints or returns a HTML tag cloud with varying classes styles depending on the popularity and type of each tag.
+ *
+ * @deprecated since 3.1
+ *
+ * @param array $tagset Array of tags to display
+ * @param int $nr_of_tags Limit for the number of tags to return/display, used if $tagset is null
+ * @param bool $return if true the function will return the generated tag cloud instead of displaying it.
+ * @param string $sort (optional) selected sorting, default is alpha sort (name) also timemodified or popularity
+ * @return string|null a HTML string or null if this function does the output
+ */
+function tag_print_cloud($tagset=null, $nr_of_tags=150, $return=false, $sort='') {
+ global $OUTPUT;
+
+ debugging('Function tag_print_cloud() is deprecated and replaced with function core_tag_collection::get_tag_cloud(), '
+ . 'templateable core_tag\output\tagcloud and template core_tag/tagcloud.', DEBUG_DEVELOPER);
+
+ // Set up sort global - used to pass sort type into core_tag_collection::cloud_sort through usort() avoiding multiple sort functions.
+ if ($sort == 'popularity') {
+ $sort = 'count';
+ } else if ($sort == 'date') {
+ $sort = 'timemodified';
+ } else {
+ $sort = 'name';
+ }
+
+ if (is_null($tagset)) {
+ // No tag set received, so fetch tags from database.
+ // Always add query by tagcollid even when it's not known to make use of the table index.
+ $tagcloud = core_tag_collection::get_tag_cloud(0, '', $nr_of_tags, $sort);
+ } else {
+ $tagsincloud = $tagset;
+
+ $etags = array();
+ foreach ($tagsincloud as $tag) {
+ $etags[] = $tag;
+ }
+
+ core_tag_collection::$cloudsortfield = $sort;
+ usort($tagsincloud, "core_tag_collection::cloud_sort");
+
+ $tagcloud = new \core_tag\output\tagcloud($tagsincloud);
+ }
+
+ $output = $OUTPUT->render_from_template('core_tag/tagcloud', $tagcloud->export_for_template($OUTPUT));
+ if ($return) {
+ return $output;
+ } else {
+ echo $output;
+ }
+}
+
/**
* Function that returns tags that start with some text, for use by the autocomplete feature
*
@@ -2744,3 +3139,838 @@ function tag_autocomplete($text) {
FROM {tag} tg
WHERE tg.name LIKE ?", array(core_text::strtolower($text)."%"));
}
+
+/**
+ * Prints a box with the description of a tag and its related tags
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param stdClass $tag_object
+ * @param bool $return if true the function will return the generated tag cloud instead of displaying it.
+ * @return string/null a HTML box showing a description of the tag object and it's relationsips or null if output is done directly
+ * in the function.
+ */
+function tag_print_description_box($tag_object, $return=false) {
+ global $USER, $CFG, $OUTPUT;
+ require_once($CFG->libdir.'/filelib.php');
+
+ debugging('Function tag_print_description_box() is deprecated without replacement. ' .
+ 'See core_tag_renderer for similar code.', DEBUG_DEVELOPER);
+
+ $relatedtags = array();
+ if ($tag = core_tag_tag::get($tag_object->id)) {
+ $relatedtags = $tag->get_related_tags();
+ }
+
+ $content = !empty($tag_object->description);
+ $output = '';
+
+ if ($content) {
+ $output .= $OUTPUT->box_start('generalbox tag-description');
+ }
+
+ if (!empty($tag_object->description)) {
+ $options = new stdClass();
+ $options->para = false;
+ $options->overflowdiv = true;
+ $tag_object->description = file_rewrite_pluginfile_urls($tag_object->description, 'pluginfile.php', context_system::instance()->id, 'tag', 'description', $tag_object->id);
+ $output .= format_text($tag_object->description, $tag_object->descriptionformat, $options);
+ }
+
+ if ($content) {
+ $output .= $OUTPUT->box_end();
+ }
+
+ if ($relatedtags) {
+ $output .= $OUTPUT->tag_list($relatedtags, get_string('relatedtags', 'tag'), 'tag-relatedtags');
+ }
+
+ if ($return) {
+ return $output;
+ } else {
+ echo $output;
+ }
+}
+
+/**
+ * Prints a box that contains the management links of a tag
+ *
+ * @deprecated since 3.1
+ * @param core_tag_tag|stdClass $tag_object
+ * @param bool $return if true the function will return the generated tag cloud instead of displaying it.
+ * @return string|null a HTML string or null if this function does the output
+ */
+function tag_print_management_box($tag_object, $return=false) {
+ global $USER, $CFG, $OUTPUT;
+
+ debugging('Function tag_print_description_box() is deprecated without replacement. ' .
+ 'See core_tag_renderer for similar code.', DEBUG_DEVELOPER);
+
+ $tagname = core_tag_tag::make_display_name($tag_object);
+ $output = '';
+
+ if (!isguestuser()) {
+ $output .= $OUTPUT->box_start('box','tag-management-box');
+ $systemcontext = context_system::instance();
+ $links = array();
+
+ // Add a link for users to add/remove this from their interests
+ if (core_tag_tag::is_enabled('core', 'user') && core_tag_area::get_collection('core', 'user') == $tag_object->tagcollid) {
+ if (core_tag_tag::is_item_tagged_with('core', 'user', $USER->id, $tag_object->name)) {
+ $links[] = ''.
+ get_string('removetagfrommyinterests', 'tag', $tagname) .'';
+ } else {
+ $links[] = ''.
+ get_string('addtagtomyinterests', 'tag', $tagname) .'';
+ }
+ }
+
+ // Flag as inappropriate link. Only people with moodle/tag:flag capability.
+ if (has_capability('moodle/tag:flag', $systemcontext)) {
+ $links[] = ''. get_string('flagasinappropriate',
+ 'tag', rawurlencode($tagname)) .'';
+ }
+
+ // Edit tag: Only people with moodle/tag:edit capability who either have it as an interest or can manage tags
+ if (has_capability('moodle/tag:edit', $systemcontext) ||
+ has_capability('moodle/tag:manage', $systemcontext)) {
+ $links[] = '' .
+ get_string('edittag', 'tag') . '';
+ }
+
+ $output .= implode(' | ', $links);
+ $output .= $OUTPUT->box_end();
+ }
+
+ if ($return) {
+ return $output;
+ } else {
+ echo $output;
+ }
+}
+
+/**
+ * Prints the tag search box
+ *
+ * @deprecated since 3.1
+ * @param bool $return if true return html string
+ * @return string|null a HTML string or null if this function does the output
+ */
+function tag_print_search_box($return=false) {
+ global $CFG, $OUTPUT;
+
+ debugging('Function tag_print_search_box() is deprecated without replacement. ' .
+ 'See core_tag_renderer for similar code.', DEBUG_DEVELOPER);
+
+ $query = optional_param('query', '', PARAM_RAW);
+
+ $output = $OUTPUT->box_start('','tag-search-box');
+ $output .= '';
+ $output .= $OUTPUT->box_end();
+
+ if ($return) {
+ return $output;
+ }
+ else {
+ echo $output;
+ }
+}
+
+/**
+ * Prints the tag search results
+ *
+ * @deprecated since 3.1
+ * @param string $query text that tag names will be matched against
+ * @param int $page current page
+ * @param int $perpage nr of users displayed per page
+ * @param bool $return if true return html string
+ * @return string|null a HTML string or null if this function does the output
+ */
+function tag_print_search_results($query, $page, $perpage, $return=false) {
+ global $CFG, $USER, $OUTPUT;
+
+ debugging('Function tag_print_search_results() is deprecated without replacement. ' .
+ 'In /tag/search.php the search results are printed using the core_tag/tagcloud template.', DEBUG_DEVELOPER);
+
+ $query = clean_param($query, PARAM_TAG);
+
+ $count = count(tag_find_tags($query, false));
+ $tags = array();
+
+ if ( $found_tags = tag_find_tags($query, true, $page * $perpage, $perpage) ) {
+ $tags = array_values($found_tags);
+ }
+
+ $baseurl = $CFG->wwwroot.'/tag/search.php?query='. rawurlencode($query);
+ $output = '';
+
+ // link "Add $query to my interests"
+ $addtaglink = '';
+ if (core_tag_tag::is_enabled('core', 'user') && !core_tag_tag::is_item_tagged_with('core', 'user', $USER->id, $query)) {
+ $addtaglink = html_writer::link(new moodle_url('/tag/user.php', array('action' => 'addinterest', 'sesskey' => sesskey(),
+ 'tag' => $query)), get_string('addtagtomyinterests', 'tag', s($query)));
+ }
+
+ if ( !empty($tags) ) { // there are results to display!!
+ $output .= $OUTPUT->heading(get_string('searchresultsfor', 'tag', htmlspecialchars($query)) ." : {$count}", 3, 'main');
+
+ //print a link "Add $query to my interests"
+ if (!empty($addtaglink)) {
+ $output .= $OUTPUT->box($addtaglink, 'box', 'tag-management-box');
+ }
+
+ $nr_of_lis_per_ul = 6;
+ $nr_of_uls = ceil( sizeof($tags) / $nr_of_lis_per_ul );
+
+ $output .= '';
+ for($i = 0; $i < $nr_of_uls; $i++) {
+ foreach (array_slice($tags, $i * $nr_of_lis_per_ul, $nr_of_lis_per_ul) as $tag) {
+ $output .= '- ';
+ $tag_link = html_writer::link(core_tag_tag::make_url($tag->tagcollid, $tag->rawname),
+ core_tag_tag::make_display_name($tag));
+ $output .= $tag_link;
+ $output .= '
';
+ }
+ }
+ $output .= '
';
+ $output .= '
'; // <-- small layout hack in order to look good in Firefox
+
+ $output .= $OUTPUT->paging_bar($count, $page, $perpage, $baseurl);
+ }
+ else { //no results were found!!
+ $output .= $OUTPUT->heading(get_string('noresultsfor', 'tag', htmlspecialchars($query)), 3, 'main');
+
+ //print a link "Add $query to my interests"
+ if (!empty($addtaglink)) {
+ $output .= $OUTPUT->box($addtaglink, 'box', 'tag-management-box');
+ }
+ }
+
+ if ($return) {
+ return $output;
+ }
+ else {
+ echo $output;
+ }
+}
+
+/**
+ * Prints a table of the users tagged with the tag passed as argument
+ *
+ * @deprecated since 3.1
+ * @param stdClass $tagobject the tag we wish to return data for
+ * @param int $limitfrom (optional, required if $limitnum is set) prints users starting at this point.
+ * @param int $limitnum (optional, required if $limitfrom is set) prints this many users.
+ * @param bool $return if true return html string
+ * @return string|null a HTML string or null if this function does the output
+ */
+function tag_print_tagged_users_table($tagobject, $limitfrom='', $limitnum='', $return=false) {
+
+ debugging('Function tag_print_tagged_users_table() is deprecated without replacement. ' .
+ 'See core_user_renderer for similar code.', DEBUG_DEVELOPER);
+
+ //List of users with this tag
+ $tagobject = core_tag_tag::get($tagobject->id);
+ $userlist = $tagobject->get_tagged_items('core', 'user', $limitfrom, $limitnum);
+
+ $output = tag_print_user_list($userlist, true);
+
+ if ($return) {
+ return $output;
+ }
+ else {
+ echo $output;
+ }
+}
+
+/**
+ * Prints an individual user box
+ *
+ * @deprecated since 3.1
+ * @param user_object $user (contains the following fields: id, firstname, lastname and picture)
+ * @param bool $return if true return html string
+ * @return string|null a HTML string or null if this function does the output
+ */
+function tag_print_user_box($user, $return=false) {
+ global $CFG, $OUTPUT;
+
+ debugging('Function tag_print_user_box() is deprecated without replacement. ' .
+ 'See core_user_renderer for similar code.', DEBUG_DEVELOPER);
+
+ $usercontext = context_user::instance($user->id);
+ $profilelink = '';
+
+ if ($usercontext and (has_capability('moodle/user:viewdetails', $usercontext) || has_coursecontact_role($user->id))) {
+ $profilelink = $CFG->wwwroot .'/user/view.php?id='. $user->id;
+ }
+
+ $output = $OUTPUT->box_start('user-box', 'user'. $user->id);
+ $fullname = fullname($user);
+ $alt = '';
+
+ if (!empty($profilelink)) {
+ $output .= '';
+ $alt = $fullname;
+ }
+
+ $output .= $OUTPUT->user_picture($user, array('size'=>100));
+ $output .= '
';
+
+ if (!empty($profilelink)) {
+ $output .= '';
+ }
+
+ //truncate name if it's too big
+ if (core_text::strlen($fullname) > 26) {
+ $fullname = core_text::substr($fullname, 0, 26) .'...';
+ }
+
+ $output .= ''. $fullname .'';
+ $output .= $OUTPUT->box_end();
+
+ if ($return) {
+ return $output;
+ }
+ else {
+ echo $output;
+ }
+}
+
+/**
+ * Prints a list of users
+ *
+ * @deprecated since 3.1
+ * @param array $userlist an array of user objects
+ * @param bool $return if true return html string, otherwise output the result
+ * @return string|null a HTML string or null if this function does the output
+ */
+function tag_print_user_list($userlist, $return=false) {
+
+ debugging('Function tag_print_user_list() is deprecated without replacement. ' .
+ 'See core_user_renderer for similar code.', DEBUG_DEVELOPER);
+
+ $output = '';
+
+ foreach ($userlist as $user){
+ $output .= '- '. tag_print_user_box($user, true) ."
\n";
+ }
+ $output .= "
\n";
+
+ if ($return) {
+ return $output;
+ }
+ else {
+ echo $output;
+ }
+}
+
+/**
+ * Function that returns the name that should be displayed for a specific tag
+ *
+ * @package core_tag
+ * @category tag
+ * @deprecated since 3.1
+ * @param stdClass|core_tag_tag $tagobject a line out of tag table, as returned by the adobd functions
+ * @param int $html TAG_RETURN_HTML (default) will return htmlspecialchars encoded string, TAG_RETURN_TEXT will not encode.
+ * @return string
+ */
+function tag_display_name($tagobject, $html=TAG_RETURN_HTML) {
+ debugging('Function tag_display_name() is deprecated. Use core_tag_tag::make_display_name().', DEBUG_DEVELOPER);
+ if (!isset($tagobject->name)) {
+ return '';
+ }
+ return core_tag_tag::make_display_name($tagobject, $html != TAG_RETURN_TEXT);
+}
+
+/**
+ * Function that normalizes a list of tag names.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param array/string $rawtags array of tags, or a single tag.
+ * @param int $case case to use for returned value (default: lower case). Either TAG_CASE_LOWER (default) or TAG_CASE_ORIGINAL
+ * @return array lowercased normalized tags, indexed by the normalized tag, in the same order as the original array.
+ * (Eg: 'Banana' => 'banana').
+ */
+function tag_normalize($rawtags, $case = TAG_CASE_LOWER) {
+ debugging('Function tag_normalize() is deprecated. Use core_tag_tag::normalize().', DEBUG_DEVELOPER);
+
+ if ( !is_array($rawtags) ) {
+ $rawtags = array($rawtags);
+ }
+
+ return core_tag_tag::normalize($rawtags, $case == TAG_CASE_LOWER);
+}
+
+/**
+ * Get a comma-separated list of tags related to another tag.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param array $related_tags the array returned by tag_get_related_tags
+ * @param int $html either TAG_RETURN_HTML (default) or TAG_RETURN_TEXT : return html links, or just text.
+ * @return string comma-separated list
+ */
+function tag_get_related_tags_csv($related_tags, $html=TAG_RETURN_HTML) {
+ global $OUTPUT;
+ debugging('Method tag_get_related_tags_csv() is deprecated. Consider '
+ . 'looping through array or using $OUTPUT->tag_list(core_tag_tag::get_item_tags())',
+ DEBUG_DEVELOPER);
+ if ($html != TAG_RETURN_TEXT) {
+ return $OUTPUT->tag_list($related_tags, '');
+ }
+
+ $tagsnames = array();
+ foreach ($related_tags as $tag) {
+ $tagsnames[] = core_tag_tag::make_display_name($tag, false);
+ }
+ return implode(', ', $tagsnames);
+}
+
+/**
+ * Used to require that the return value from a function is an array.
+ * Only used in the deprecated function {@link tag_get_id()}
+ * @deprecated since 3.1
+ */
+define('TAG_RETURN_ARRAY', 0);
+/**
+ * Used to require that the return value from a function is an object.
+ * Only used in the deprecated function {@link tag_get_id()}
+ * @deprecated since 3.1
+ */
+define('TAG_RETURN_OBJECT', 1);
+/**
+ * Use to specify that HTML free text is expected to be returned from a function.
+ * Only used in deprecated functions {@link tag_get_tags_csv()}, {@link tag_display_name()},
+ * {@link tag_get_related_tags_csv()}
+ * @deprecated since 3.1
+ */
+define('TAG_RETURN_TEXT', 2);
+/**
+ * Use to specify that encoded HTML is expected to be returned from a function.
+ * Only used in deprecated functions {@link tag_get_tags_csv()}, {@link tag_display_name()},
+ * {@link tag_get_related_tags_csv()}
+ * @deprecated since 3.1
+ */
+define('TAG_RETURN_HTML', 3);
+
+/**
+ * Used to specify that we wish a lowercased string to be returned
+ * Only used in deprecated function {@link tag_normalize()}
+ * @deprecated since 3.1
+ */
+define('TAG_CASE_LOWER', 0);
+/**
+ * Used to specify that we do not wish the case of the returned string to change
+ * Only used in deprecated function {@link tag_normalize()}
+ * @deprecated since 3.1
+ */
+define('TAG_CASE_ORIGINAL', 1);
+
+/**
+ * Used to specify that we want all related tags returned, no matter how they are related.
+ * Only used in deprecated function {@link tag_get_related_tags()}
+ * @deprecated since 3.1
+ */
+define('TAG_RELATED_ALL', 0);
+/**
+ * Used to specify that we only want back tags that were manually related.
+ * Only used in deprecated function {@link tag_get_related_tags()}
+ * @deprecated since 3.1
+ */
+define('TAG_RELATED_MANUAL', 1);
+/**
+ * Used to specify that we only want back tags where the relationship was automatically correlated.
+ * Only used in deprecated function {@link tag_get_related_tags()}
+ * @deprecated since 3.1
+ */
+define('TAG_RELATED_CORRELATED', 2);
+
+/**
+ * Set the tags assigned to a record. This overwrites the current tags.
+ *
+ * This function is meant to be fed the string coming up from the user interface, which contains all tags assigned to a record.
+ *
+ * Due to API change $component and $contextid are now required. Instead of
+ * calling this function you can use {@link core_tag_tag::set_item_tags()} or
+ * {@link core_tag_tag::set_related_tags()}
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $itemtype the type of record to tag ('post' for blogs, 'user' for users, 'tag' for tags, etc.)
+ * @param int $itemid the id of the record to tag
+ * @param array $tags the array of tags to set on the record. If given an empty array, all tags will be removed.
+ * @param string|null $component the component that was tagged
+ * @param int|null $contextid the context id of where this tag was assigned
+ * @return bool|null
+ */
+function tag_set($itemtype, $itemid, $tags, $component = null, $contextid = null) {
+ debugging('Function tag_set() is deprecated. Use ' .
+ ' core_tag_tag::set_item_tags() instead', DEBUG_DEVELOPER);
+
+ if ($itemtype === 'tag') {
+ return core_tag_tag::get($itemid, '*', MUST_EXIST)->set_related_tags($tags);
+ } else {
+ $context = $contextid ? context::instance_by_id($contextid) : context_system::instance();
+ return core_tag_tag::set_item_tags($component, $itemtype, $itemid, $context, $tags);
+ }
+}
+
+/**
+ * Adds a tag to a record, without overwriting the current tags.
+ *
+ * This function remains here for backward compatiblity. It is recommended to use
+ * {@link core_tag_tag::add_item_tag()} or {@link core_tag_tag::add_related_tags()} instead
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $itemtype the type of record to tag ('post' for blogs, 'user' for users, etc.)
+ * @param int $itemid the id of the record to tag
+ * @param string $tag the tag to add
+ * @param string|null $component the component that was tagged
+ * @param int|null $contextid the context id of where this tag was assigned
+ * @return bool|null
+ */
+function tag_set_add($itemtype, $itemid, $tag, $component = null, $contextid = null) {
+ debugging('Function tag_set_add() is deprecated. Use ' .
+ ' core_tag_tag::add_item_tag() instead', DEBUG_DEVELOPER);
+
+ if ($itemtype === 'tag') {
+ return core_tag_tag::get($itemid, '*', MUST_EXIST)->add_related_tags(array($tag));
+ } else {
+ $context = $contextid ? context::instance_by_id($contextid) : context_system::instance();
+ return core_tag_tag::add_item_tag($component, $itemtype, $itemid, $context, $tag);
+ }
+}
+
+/**
+ * Removes a tag from a record, without overwriting other current tags.
+ *
+ * This function remains here for backward compatiblity. It is recommended to use
+ * {@link core_tag_tag::remove_item_tag()} instead
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $itemtype the type of record to tag ('post' for blogs, 'user' for users, etc.)
+ * @param int $itemid the id of the record to tag
+ * @param string $tag the tag to delete
+ * @param string|null $component the component that was tagged
+ * @param int|null $contextid the context id of where this tag was assigned
+ * @return bool|null
+ */
+function tag_set_delete($itemtype, $itemid, $tag, $component = null, $contextid = null) {
+ debugging('Function tag_set_delete() is deprecated. Use ' .
+ ' core_tag_tag::remove_item_tag() instead', DEBUG_DEVELOPER);
+ return core_tag_tag::remove_item_tag($component, $itemtype, $itemid, $tag);
+}
+
+/**
+ * Simple function to just return a single tag object when you know the name or something
+ *
+ * See also {@link core_tag_tag::get()} and {@link core_tag_tag::get_by_name()}
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $field which field do we use to identify the tag: id, name or rawname
+ * @param string $value the required value of the aforementioned field
+ * @param string $returnfields which fields do we want returned. This is a comma seperated string containing any combination of
+ * 'id', 'name', 'rawname' or '*' to include all fields.
+ * @return mixed tag object
+ */
+function tag_get($field, $value, $returnfields='id, name, rawname, tagcollid') {
+ global $DB;
+ debugging('Function tag_get() is deprecated. Use ' .
+ ' core_tag_tag::get() or core_tag_tag::get_by_name()',
+ DEBUG_DEVELOPER);
+ if ($field === 'id') {
+ $tag = core_tag_tag::get((int)$value, $returnfields);
+ } else if ($field === 'name') {
+ $tag = core_tag_tag::get_by_name(0, $value, $returnfields);
+ } else {
+ $params = array($field => $value);
+ return $DB->get_record('tag', $params, $returnfields);
+ }
+ if ($tag) {
+ return $tag->to_object();
+ }
+ return null;
+}
+
+/**
+ * Returns tags related to a tag
+ *
+ * Related tags of a tag come from two sources:
+ * - manually added related tags, which are tag_instance entries for that tag
+ * - correlated tags, which are calculated
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $tagid is a single **normalized** tag name or the id of a tag
+ * @param int $type the function will return either manually (TAG_RELATED_MANUAL) related tags or correlated
+ * (TAG_RELATED_CORRELATED) tags. Default is TAG_RELATED_ALL, which returns everything.
+ * @param int $limitnum (optional) return a subset comprising this many records, the default is 10
+ * @return array an array of tag objects
+ */
+function tag_get_related_tags($tagid, $type=TAG_RELATED_ALL, $limitnum=10) {
+ debugging('Method tag_get_related_tags() is deprecated, '
+ . 'use core_tag_tag::get_correlated_tags(), core_tag_tag::get_related_tags() or '
+ . 'core_tag_tag::get_manual_related_tags()', DEBUG_DEVELOPER);
+ $result = array();
+ if ($tag = core_tag_tag::get($tagid)) {
+ if ($type == TAG_RELATED_CORRELATED) {
+ $tags = $tag->get_correlated_tags();
+ } else if ($type == TAG_RELATED_MANUAL) {
+ $tags = $tag->get_manual_related_tags();
+ } else {
+ $tags = $tag->get_related_tags();
+ }
+ $tags = array_slice($tags, 0, $limitnum);
+ foreach ($tags as $id => $tag) {
+ $result[$id] = $tag->to_object();
+ }
+ }
+ return $result;
+}
+
+/**
+ * Delete one or more tag, and all their instances if there are any left.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param mixed $tagids one tagid (int), or one array of tagids to delete
+ * @return bool true on success, false otherwise
+ */
+function tag_delete($tagids) {
+ debugging('Method tag_delete() is deprecated, use core_tag_tag::delete_tags()',
+ DEBUG_DEVELOPER);
+ return core_tag_tag::delete_tags($tagids);
+}
+
+/**
+ * Deletes all the tag instances given a component and an optional contextid.
+ *
+ * @deprecated since 3.1
+ * @param string $component
+ * @param int $contextid if null, then we delete all tag instances for the $component
+ */
+function tag_delete_instances($component, $contextid = null) {
+ debugging('Method tag_delete() is deprecated, use core_tag_tag::delete_instances()',
+ DEBUG_DEVELOPER);
+ core_tag_tag::delete_instances($component, null, $contextid);
+}
+
+/**
+ * Clean up the tag tables, making sure all tagged object still exists.
+ *
+ * This should normally not be necessary, but in case related tags are not deleted when the tagged record is removed, this should be
+ * done once in a while, perhaps on an occasional cron run. On a site with lots of tags, this could become an expensive function to
+ * call: don't run at peak time.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ */
+function tag_cleanup() {
+ debugging('Method tag_cleanup() is deprecated, use \core\task\tag_cron_task::cleanup()',
+ DEBUG_DEVELOPER);
+
+ $task = new \core\task\tag_cron_task();
+ return $task->cleanup();
+}
+
+/**
+ * This function will delete numerous tag instances efficiently.
+ * This removes tag instances only. It doesn't check to see if it is the last use of a tag.
+ *
+ * @deprecated since 3.1
+ * @param array $instances An array of tag instance objects with the addition of the tagname and tagrawname
+ * (used for recording a delete event).
+ */
+function tag_bulk_delete_instances($instances) {
+ debugging('Method tag_bulk_delete_instances() is deprecated, '
+ . 'use \core\task\tag_cron_task::bulk_delete_instances()',
+ DEBUG_DEVELOPER);
+
+ $task = new \core\task\tag_cron_task();
+ return $task->bulk_delete_instances($instances);
+}
+
+/**
+ * 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.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param int $mincorrelation Only tags with more than $mincorrelation correlations will be identified.
+ */
+function tag_compute_correlations($mincorrelation = 2) {
+ debugging('Method tag_compute_correlations() is deprecated, '
+ . 'use \core\task\tag_cron_task::compute_correlations()',
+ DEBUG_DEVELOPER);
+
+ $task = new \core\task\tag_cron_task();
+ return $task->compute_correlations($mincorrelation);
+}
+
+/**
+ * This function processes a tag correlation and makes changes in the database as required.
+ *
+ * The tag correlation object needs have both a tagid property and a correlatedtags property that is an array.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param stdClass $tagcorrelation
+ * @return int/bool The id of the tag correlation that was just processed or false.
+ */
+function tag_process_computed_correlation(stdClass $tagcorrelation) {
+ debugging('Method tag_process_computed_correlation() is deprecated, '
+ . 'use \core\task\tag_cron_task::process_computed_correlation()',
+ DEBUG_DEVELOPER);
+
+ $task = new \core\task\tag_cron_task();
+ return $task->process_computed_correlation($tagcorrelation);
+}
+
+/**
+ * Tasks that should be performed at cron time
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ */
+function tag_cron() {
+ debugging('Method tag_cron() is deprecated, use \core\task\tag_cron_task::execute()',
+ DEBUG_DEVELOPER);
+
+ $task = new \core\task\tag_cron_task();
+ $task->execute();
+}
+
+/**
+ * Search for tags with names that match some text
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $text escaped string that the tag names will be matched against
+ * @param bool $ordered If true, tags are ordered by their popularity. If false, no ordering.
+ * @param int/string $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point.
+ * @param int/string $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records.
+ * @param int $tagcollid
+ * @return array/boolean an array of objects, or false if no records were found or an error occured.
+ */
+function tag_find_tags($text, $ordered=true, $limitfrom='', $limitnum='', $tagcollid = null) {
+ debugging('Method tag_find_tags() is deprecated without replacement', DEBUG_DEVELOPER);
+ global $DB;
+
+ $text = core_text::strtolower(clean_param($text, PARAM_TAG));
+
+ list($sql, $params) = $DB->get_in_or_equal($tagcollid ? array($tagcollid) :
+ array_keys(core_tag_collection::get_collections(true)));
+ array_unshift($params, "%{$text}%");
+
+ if ($ordered) {
+ $query = "SELECT tg.id, tg.name, tg.rawname, tg.tagcollid, COUNT(ti.id) AS count
+ FROM {tag} tg LEFT JOIN {tag_instance} ti ON tg.id = ti.tagid
+ WHERE tg.name LIKE ? AND tg.tagcollid $sql
+ GROUP BY tg.id, tg.name, tg.rawname
+ ORDER BY count DESC";
+ } else {
+ $query = "SELECT tg.id, tg.name, tg.rawname, tg.tagcollid
+ FROM {tag} tg
+ WHERE tg.name LIKE ? AND tg.tagcollid $sql";
+ }
+ return $DB->get_records_sql($query, $params, $limitfrom , $limitnum);
+}
+
+/**
+ * Get the name of a tag
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param mixed $tagids the id of the tag, or an array of ids
+ * @return mixed string name of one tag, or id-indexed array of strings
+ */
+function tag_get_name($tagids) {
+ debugging('Method tag_get_name() is deprecated without replacement', DEBUG_DEVELOPER);
+ global $DB;
+
+ if (!is_array($tagids)) {
+ if ($tag = $DB->get_record('tag', array('id'=>$tagids))) {
+ return $tag->name;
+ }
+ return false;
+ }
+
+ $tag_names = array();
+ foreach($DB->get_records_list('tag', 'id', $tagids) as $tag) {
+ $tag_names[$tag->id] = $tag->name;
+ }
+
+ return $tag_names;
+}
+
+/**
+ * Returns the correlated tags of a tag, retrieved from the tag_correlation table. Make sure cron runs, otherwise the table will be
+ * empty and this function won't return anything.
+ *
+ * Correlated tags are calculated in cron based on existing tag instances.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param int $tagid is a single tag id
+ * @param int $notused this argument is no longer used
+ * @return array an array of tag objects or an empty if no correlated tags are found
+ */
+function tag_get_correlated($tagid, $notused = null) {
+ debugging('Method tag_get_correlated() is deprecated, '
+ . 'use core_tag_tag::get_correlated_tags()', DEBUG_DEVELOPER);
+ $result = array();
+ if ($tag = core_tag_tag::get($tagid)) {
+ $tags = $tag->get_correlated_tags(true);
+ // Convert to objects for backward-compatibility.
+ foreach ($tags as $id => $tag) {
+ $result[$id] = $tag->to_object();
+ }
+ }
+ return $result;
+}
+
+/**
+ * This function is used by print_tag_cloud, to usort() the tags in the cloud. See php.net/usort for the parameters documentation.
+ * This was originally in blocks/blog_tags/block_blog_tags.php, named blog_tags_sort().
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $a Tag name to compare against $b
+ * @param string $b Tag name to compare against $a
+ * @return int The result of the comparison/validation 1, 0 or -1
+ */
+function tag_cloud_sort($a, $b) {
+ debugging('Method tag_cloud_sort() is deprecated, similar method can be found in core_tag_collection::cloud_sort()', DEBUG_DEVELOPER);
+ global $CFG;
+
+ if (empty($CFG->tagsort)) {
+ $tagsort = 'name'; // by default, sort by name
+ } else {
+ $tagsort = $CFG->tagsort;
+ }
+
+ if (is_numeric($a->$tagsort)) {
+ return ($a->$tagsort == $b->$tagsort) ? 0 : ($a->$tagsort > $b->$tagsort) ? 1 : -1;
+ } elseif (is_string($a->$tagsort)) {
+ return strcmp($a->$tagsort, $b->$tagsort);
+ } else {
+ return 0;
+ }
+}
diff --git a/lib/form/tags.php b/lib/form/tags.php
index a4727672634..096bbd91955 100644
--- a/lib/form/tags.php
+++ b/lib/form/tags.php
@@ -63,6 +63,12 @@ class MoodleQuickForm_tags extends MoodleQuickForm_autocomplete {
*/
protected $showingofficial = false;
+ /**
+ * Options passed when creating an element.
+ * @var array
+ */
+ protected $tagsoptions = array();
+
/**
* Constructor
*
@@ -72,29 +78,66 @@ class MoodleQuickForm_tags extends MoodleQuickForm_autocomplete {
* @param mixed $attributes Either a typical HTML attribute string or an associative array.
*/
public function __construct($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
- if (!isset($options['display'])) {
- $options['display'] = self::DEFAULTUI;
- }
-
- $this->showingofficial = $options['display'] != MoodleQuickForm_tags::NOOFFICIAL;
-
$validoptions = array();
- if ($this->showingofficial) {
- $validoptions = $this->load_official_tags();
+
+ if (!empty($options)) {
+ // Only execute it when the element was created and $options has values set by user.
+ // In onQuickFormEvent() we make sure that $options is not empty even if developer left it empty.
+ if (empty($options['display'])) {
+ $options['display'] = self::DEFAULTUI;
+ }
+ $this->tagsoptions = $options;
+
+ $this->showingofficial = $options['display'] != self::NOOFFICIAL;
+
+ if ($this->showingofficial) {
+ $validoptions = $this->load_official_tags();
+ }
+ // Option 'tags' allows us to type new tags.
+ if ($options['display'] == self::ONLYOFFICIAL) {
+ $attributes['tags'] = false;
+ } else {
+ $attributes['tags'] = true;
+ }
+ $attributes['multiple'] = 'multiple';
+ $attributes['placeholder'] = get_string('entertags', 'tag');
+ $attributes['showsuggestions'] = $this->showingofficial;
}
- // 'tags' option allows us to type new tags.
- if ($options['display'] == MoodleQuickForm_tags::ONLYOFFICIAL) {
- $attributes['tags'] = false;
- } else {
- $attributes['tags'] = true;
- }
- $attributes['multiple'] = 'multiple';
- $attributes['placeholder'] = get_string('entertags', 'tag');
- $attributes['showsuggestions'] = $this->showingofficial;
parent::__construct($elementName, $elementLabel, $validoptions, $attributes);
}
+ /**
+ * Called by HTML_QuickForm whenever form event is made on this element
+ *
+ * @param string $event Name of event
+ * @param mixed $arg event arguments
+ * @param object $caller calling object
+ * @return bool
+ */
+ public function onQuickFormEvent($event, $arg, &$caller) {
+ if ($event === 'createElement') {
+ $arg[2] += array('itemtype' => '', 'component' => '');
+ }
+ return parent::onQuickFormEvent($event, $arg, $caller);
+ }
+
+ /**
+ * Checks if tagging is enabled for this itemtype
+ *
+ * @return boolean
+ */
+ protected function is_tagging_enabled() {
+ if (!empty($this->tagsoptions['itemtype']) && !empty($this->tagsoptions['component'])) {
+ $enabled = core_tag_tag::is_enabled($this->tagsoptions['component'], $this->tagsoptions['itemtype']);
+ if ($enabled === false) {
+ return false;
+ }
+ }
+ // Backward compatibility with code developed before Moodle 3.0 where itemtype/component were not specified.
+ return true;
+ }
+
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
@@ -105,21 +148,41 @@ class MoodleQuickForm_tags extends MoodleQuickForm_autocomplete {
self::__construct($elementName, $elementLabel, $options, $attributes);
}
+ /**
+ * Finds the tag collection to use for official tag selector
+ *
+ * @return int
+ */
+ protected function get_tag_collection() {
+ if (empty($this->tagsoptions['tagcollid']) && (empty($this->tagsoptions['itemtype']) ||
+ empty($this->tagsoptions['component']))) {
+ debugging('You need to specify \'itemtype\' and \'component\' of the tagged '
+ . 'area in the tags form element options',
+ DEBUG_DEVELOPER);
+ }
+ if (!empty($this->tagsoptions['tagcollid'])) {
+ return $this->tagsoptions['tagcollid'];
+ }
+ if ($this->tagsoptions['itemtype']) {
+ $this->tagsoptions['tagcollid'] = core_tag_area::get_collection($this->tagsoptions['component'],
+ $this->tagsoptions['itemtype']);
+ } else {
+ $this->tagsoptions['tagcollid'] = core_tag_collection::get_default();
+ }
+ return $this->tagsoptions['tagcollid'];
+ }
+
/**
* Returns HTML for select form element.
*
* @return string
*/
function toHtml(){
- global $CFG, $OUTPUT;
-
- if (empty($CFG->usetags)) {
- debugging('A tags formslib field has been created even thought $CFG->usetags is false.', DEBUG_DEVELOPER);
- }
+ global $OUTPUT;
$managelink = '';
if (has_capability('moodle/tag:manage', context_system::instance()) && $this->showingofficial) {
- $url = $CFG->wwwroot .'/tag/manage.php';
+ $url = new moodle_url('/tag/manage.php', array('tc' => $this->get_tag_collection()));
$managelink = ' ' . $OUTPUT->action_link($url, get_string('manageofficialtags', 'tag'));
}
@@ -127,21 +190,47 @@ class MoodleQuickForm_tags extends MoodleQuickForm_autocomplete {
}
/**
- * Internal function to load official tags
+ * Accepts a renderer
*
- * @access protected
+ * @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
+ * @param bool $required Whether a group is required
+ * @param string $error An error message associated with a group
+ */
+ public function accept(&$renderer, $required = false, $error = null) {
+ if ($this->is_tagging_enabled()) {
+ $renderer->renderElement($this, $required, $error);
+ } else {
+ $renderer->renderHidden($this);
+ }
+ }
+
+ /**
+ * Internal function to load official tags
*/
protected function load_official_tags() {
global $CFG, $DB;
-
- $namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
- $records = $DB->get_records('tag', array('tagtype' => 'official'), $namefield, 'id,' . $namefield);
- $tags = array();
-
- foreach ($records as $record) {
- $tags[$record->$namefield] = $record->$namefield;
+ if (!$this->is_tagging_enabled()) {
+ return array();
}
- return $tags;
+ $namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
+ $tags = $DB->get_records_menu('tag',
+ array('tagtype' => 'official', 'tagcollid' => $this->get_tag_collection()),
+ $namefield, 'id,' . $namefield);
+ return array_combine($tags, $tags);
}
+ /**
+ * Returns a 'safe' element's value
+ *
+ * @param array $submitValues array of submitted values to search
+ * @param bool $assoc whether to return the value as associative array
+ * @return mixed
+ */
+ public function exportValue(&$submitValues, $assoc = false) {
+ if (!$this->is_tagging_enabled()) {
+ return $assoc ? array($this->getName() => array()) : array();
+ }
+
+ return parent::exportValue($submitValues, $assoc);
+ }
}
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index e9598eb71ca..5638971b966 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -3901,7 +3901,6 @@ function delete_user(stdClass $user) {
require_once($CFG->libdir.'/grouplib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/message/lib.php');
- require_once($CFG->dirroot.'/tag/lib.php');
require_once($CFG->dirroot.'/user/lib.php');
// Make sure nobody sends bogus record type as parameter.
@@ -3944,7 +3943,7 @@ function delete_user(stdClass $user) {
// TODO: remove from cohorts using standard API here.
// Remove user tags.
- tag_set('user', $user->id, array(), 'core', $usercontext->id);
+ core_tag_tag::remove_all_item_tags('core', 'user', $user->id);
// Unconditionally unenrol from all courses.
enrol_user_delete($user);
@@ -4735,7 +4734,6 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/group/lib.php');
- require_once($CFG->dirroot.'/tag/lib.php');
require_once($CFG->dirroot.'/comment/lib.php');
require_once($CFG->dirroot.'/rating/lib.php');
require_once($CFG->dirroot.'/notes/lib.php');
@@ -4910,7 +4908,7 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
$rm->delete_ratings($delopt);
// Delete course tags.
- tag_set('course', $course->id, array(), 'core', $coursecontext->id);
+ core_tag_tag::remove_all_item_tags('core', 'course', $course->id);
// Delete calendar events.
$DB->delete_records('event', array('courseid' => $course->id));
diff --git a/lib/myprofilelib.php b/lib/myprofilelib.php
index 825753bab22..e397770ad57 100644
--- a/lib/myprofilelib.php
+++ b/lib/myprofilelib.php
@@ -23,7 +23,6 @@
*/
defined('MOODLE_INTERNAL') || die();
-require_once($CFG->dirroot . '/tag/lib.php');
/**
* Defines core nodes for my profile navigation tree.
@@ -36,7 +35,7 @@ require_once($CFG->dirroot . '/tag/lib.php');
* @return bool
*/
function core_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $iscurrentuser, $course) {
- global $CFG, $USER, $DB, $PAGE;
+ global $CFG, $USER, $DB, $PAGE, $OUTPUT;
$usercontext = context_user::instance($user->id, MUST_EXIST);
$systemcontext = context_system::instance();
@@ -218,11 +217,10 @@ function core_myprofile_navigation(core_user\output\myprofile\tree $tree, $user,
}
// Printing tagged interests. We want this only for full profile.
- if (!empty($CFG->usetags) && empty($course)) {
- if ($interests = tag_get_tags_csv('user', $user->id) ) {
- $node = new core_user\output\myprofile\node('contact', 'interests', get_string('interests'), null, null, $interests);
- $tree->add_node($node);
- }
+ if (empty($course) && ($interests = core_tag_tag::get_item_tags('core', 'user', $user->id))) {
+ $node = new core_user\output\myprofile\node('contact', 'interests', get_string('interests'), null, null,
+ $OUTPUT->tag_list($interests, ''));
+ $tree->add_node($node);
}
if (!isset($hiddenfields['mycourses'])) {
diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php
index 35fba720204..858061ea009 100644
--- a/lib/outputrenderers.php
+++ b/lib/outputrenderers.php
@@ -4103,6 +4103,23 @@ EOD;
$html .= html_writer::end_tag('header');
return $html;
}
+
+ /**
+ * Displays the list of tags associated with an entry
+ *
+ * @param array $tags list of instances of core_tag or stdClass
+ * @param string $label label to display in front, by default 'Tags' (get_string('tags')), set to null
+ * to use default, set to '' (empty string) to omit the label completely
+ * @param string $classes additional classes for the enclosing div element
+ * @param int $limit limit the number of tags to display, if size of $tags is more than this limit the "more" link
+ * will be appended to the end, JS will toggle the rest of the tags
+ * @param context $pagecontext specify if needed to overwrite the current page context for the view tag link
+ * @return string
+ */
+ public function tag_list($tags, $label = null, $classes = '', $limit = 10, $pagecontext = null) {
+ $list = new \core_tag\output\taglist($tags, $label, $classes, $limit, $pagecontext);
+ return $this->render_from_template('core_tag/taglist', $list->export_for_template($this));
+ }
}
/**
diff --git a/lib/questionlib.php b/lib/questionlib.php
index 2a2d106f86e..a4fbf68ccde 100644
--- a/lib/questionlib.php
+++ b/lib/questionlib.php
@@ -334,7 +334,7 @@ function question_delete_question($questionid) {
$questionid, $question->contextid);
// Delete all tag instances.
- $DB->delete_records('tag_instance', array('component' => 'core_question', 'itemid' => $question->id));
+ core_tag_tag::remove_all_item_tags('core_question', 'question', $question->id);
// Now recursively delete all child questions
if ($children = $DB->get_records('question',
@@ -435,8 +435,7 @@ function question_delete_course_category($category, $newcategory, $feedback=true
}
// Update the contextid for any tag instances for questions in the old context.
- $DB->set_field('tag_instance', 'contextid', $newcontext->id, array('component' => 'core_question',
- 'contextid' => $context->id));
+ core_tag_tag::move_context('core_question', 'question', $context, $newcontext);
$DB->set_field('question_categories', 'contextid', $newcontext->id, array('contextid' => $context->id));
@@ -544,8 +543,7 @@ function question_move_questions_to_category($questionids, $newcategoryid) {
"parent $questionidcondition", $params);
// Update the contextid for any tag instances that may exist for these questions.
- $DB->set_field_select('tag_instance', 'contextid', $newcontextid,
- "component = 'core_question' AND itemid $questionidcondition", $params);
+ core_tag_tag::change_items_context('core_question', 'question', $questionids, $newcontextid);
// TODO Deal with datasets.
@@ -577,12 +575,8 @@ function question_move_category_to_context($categoryid, $oldcontextid, $newconte
question_bank::notify_question_edited($questionid);
}
- if ($questionids) {
- // Update the contextid for any tag instances that may exist for these questions.
- list($questionids, $params) = $DB->get_in_or_equal(array_keys($questionids));
- $DB->set_field_select('tag_instance', 'contextid', $newcontextid,
- "component = 'core_question' AND itemid $questionids", $params);
- }
+ core_tag_tag::change_items_context('core_question', 'question',
+ array_keys($questionids), $newcontextid);
$subcatids = $DB->get_records_menu('question_categories',
array('parent' => $categoryid), '', 'id,1');
@@ -765,9 +759,8 @@ function _tidy_question($question, $loadtags = false) {
unset($question->_partiallyloaded);
}
- if ($loadtags && !empty($CFG->usetags)) {
- require_once($CFG->dirroot . '/tag/lib.php');
- $question->tags = tag_get_tags_array('question', $question->id);
+ if ($loadtags && core_tag_tag::is_enabled('core_question', 'question')) {
+ $question->tags = core_tag_tag::get_item_tags_array('core_question', 'question', $question->id);
}
}
diff --git a/lib/testing/generator/data_generator.php b/lib/testing/generator/data_generator.php
index 7dfe2a44d50..756b15cf398 100644
--- a/lib/testing/generator/data_generator.php
+++ b/lib/testing/generator/data_generator.php
@@ -847,6 +847,10 @@ EOD;
$record['tagtype'] = 'default';
}
+ if (!isset($record['tagcollid'])) {
+ $record['tagcollid'] = core_tag_collection::get_default();
+ }
+
if (!isset($record['description'])) {
$record['description'] = 'Tag description';
}
diff --git a/lib/testing/tests/generator_test.php b/lib/testing/tests/generator_test.php
index 32cb7f309d1..42e07fd2bf1 100644
--- a/lib/testing/tests/generator_test.php
+++ b/lib/testing/tests/generator_test.php
@@ -174,7 +174,7 @@ class core_test_generator_testcase extends advanced_testcase {
$this->assertEquals($course->id, $section->course);
$course = $generator->create_course(array('tags' => 'Cat, Dog'));
- $this->assertEquals('Cat, Dog', tag_get_tags_csv('course', $course->id, TAG_RETURN_TEXT));
+ $this->assertEquals(array('Cat', 'Dog'), array_values(core_tag_tag::get_item_tags_array('core', 'course', $course->id)));
$scale = $generator->create_scale();
$this->assertNotEmpty($scale);
diff --git a/lib/tests/questionlib_test.php b/lib/tests/questionlib_test.php
index 6455616711f..16350107d16 100644
--- a/lib/tests/questionlib_test.php
+++ b/lib/tests/questionlib_test.php
@@ -29,7 +29,6 @@ global $CFG;
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
-require_once($CFG->dirroot . '/tag/lib.php');
// Get the necessary files to perform backup and restore.
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
@@ -164,21 +163,23 @@ class core_questionlib_testcase extends advanced_testcase {
$coursecat2 = $this->getDataGenerator()->create_category();
// Create a couple of categories and questions.
+ $context1 = context_coursecat::instance($coursecat1->id);
+ $context2 = context_coursecat::instance($coursecat2->id);
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$questioncat1 = $questiongenerator->create_question_category(array('contextid' =>
- context_coursecat::instance($coursecat1->id)->id));
+ $context1->id));
$questioncat2 = $questiongenerator->create_question_category(array('contextid' =>
- context_coursecat::instance($coursecat2->id)->id));
+ $context2->id));
$question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id));
$question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat1->id));
$question3 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id));
$question4 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat2->id));
// Now lets tag these questions.
- tag_set('question', $question1->id, array('tag 1', 'tag 2'), 'core_question', $questioncat1->contextid);
- tag_set('question', $question2->id, array('tag 3', 'tag 4'), 'core_question', $questioncat1->contextid);
- tag_set('question', $question3->id, array('tag 5', 'tag 6'), 'core_question', $questioncat2->contextid);
- tag_set('question', $question4->id, array('tag 7', 'tag 8'), 'core_question', $questioncat2->contextid);
+ core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $context1, array('tag 1', 'tag 2'));
+ core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $context1, array('tag 3', 'tag 4'));
+ core_tag_tag::set_item_tags('core_question', 'question', $question3->id, $context2, array('tag 5', 'tag 6'));
+ core_tag_tag::set_item_tags('core_question', 'question', $question4->id, $context2, array('tag 7', 'tag 8'));
// Test moving the questions to another category.
question_move_questions_to_category(array($question1->id, $question2->id), $questioncat2->id);
@@ -224,14 +225,15 @@ class core_questionlib_testcase extends advanced_testcase {
$course = $this->getDataGenerator()->create_course();
// Create some question categories and questions in this course.
+ $coursecontext = context_course::instance($course->id);
$questioncat = $questiongenerator->create_question_category(array('contextid' =>
- context_course::instance($course->id)->id));
+ $coursecontext->id));
$question1 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id));
$question2 = $questiongenerator->create_question('shortanswer', null, array('category' => $questioncat->id));
// Add some tags to these questions.
- tag_set('question', $question1->id, array('tag 1', 'tag 2'), 'core_question', $questioncat->contextid);
- tag_set('question', $question2->id, array('tag 1', 'tag 2'), 'core_question', $questioncat->contextid);
+ core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, array('tag 1', 'tag 2'));
+ core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, array('tag 1', 'tag 2'));
// Create a course that we are going to restore the other course to.
$course2 = $this->getDataGenerator()->create_course();
diff --git a/lib/upgrade.txt b/lib/upgrade.txt
index c7d74efca90..02437f8b7fe 100644
--- a/lib/upgrade.txt
+++ b/lib/upgrade.txt
@@ -31,8 +31,9 @@ information provided here is intended especially for developers.
custom user filters. Similar deprecations in existing user_filter_* classes.
* table_default_export_format_parent::table_default_export_format_parent() is
deprecated, use parent::__construct() in extending classes.
- * groups_delete_group_members() $showfeedback parameter has been removed and is no longer
- respected. Users of this function should output their own feedback if required.
+* groups_delete_group_members() $showfeedback parameter has been removed and is no longer
+ respected. Users of this function should output their own feedback if required.
+* Number of changes to Tags API, see tag/upgrade.txt for more details
=== 3.0 ===
diff --git a/lib/upgradelib.php b/lib/upgradelib.php
index c44cee9bd6f..1771153be90 100644
--- a/lib/upgradelib.php
+++ b/lib/upgradelib.php
@@ -494,6 +494,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
message_update_processors($plug);
}
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, true, $verbose);
}
}
@@ -532,6 +533,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
message_update_processors($plug);
}
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, true, $verbose);
} else if ($installedversion < $plugin->version) { // upgrade
@@ -566,6 +568,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
message_update_processors($plug);
}
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, false, $verbose);
} else if ($installedversion > $plugin->version) {
@@ -669,6 +672,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
message_update_providers($component);
\core\message\inbound\manager::update_handlers_for_component($component);
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, true, $verbose);
}
}
@@ -703,6 +707,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
message_update_providers($component);
\core\message\inbound\manager::update_handlers_for_component($component);
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, true, $verbose);
@@ -739,6 +744,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
message_update_providers($component);
\core\message\inbound\manager::update_handlers_for_component($component);
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, false, $verbose);
@@ -860,6 +866,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
message_update_providers($component);
\core\message\inbound\manager::update_handlers_for_component($component);
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, true, $verbose);
}
}
@@ -899,6 +906,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
\core\task\manager::reset_scheduled_tasks_for_component($component);
message_update_providers($component);
\core\message\inbound\manager::update_handlers_for_component($component);
+ core_tag_area::reset_definitions_for_component($component);
upgrade_plugin_mnet_functions($component);
$endcallback($component, true, $verbose);
@@ -935,6 +943,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
message_update_providers($component);
\core\message\inbound\manager::update_handlers_for_component($component);
upgrade_plugin_mnet_functions($component);
+ core_tag_area::reset_definitions_for_component($component);
$endcallback($component, false, $verbose);
@@ -1536,6 +1545,7 @@ function install_core($version, $verbose) {
\core\task\manager::reset_scheduled_tasks_for_component('moodle');
message_update_providers('moodle');
\core\message\inbound\manager::update_handlers_for_component('moodle');
+ core_tag_area::reset_definitions_for_component('moodle');
// Write default settings unconditionally
admin_apply_default_settings(NULL, true);
@@ -1603,6 +1613,7 @@ function upgrade_core($version, $verbose) {
\core\task\manager::reset_scheduled_tasks_for_component('moodle');
message_update_providers('moodle');
\core\message\inbound\manager::update_handlers_for_component('moodle');
+ core_tag_area::reset_definitions_for_component('moodle');
// Update core definitions.
cache_helper::update_definitions(true);
diff --git a/mod/wiki/backup/moodle2/backup_wiki_stepslib.php b/mod/wiki/backup/moodle2/backup_wiki_stepslib.php
index 91192905142..b836df9e155 100644
--- a/mod/wiki/backup/moodle2/backup_wiki_stepslib.php
+++ b/mod/wiki/backup/moodle2/backup_wiki_stepslib.php
@@ -104,8 +104,10 @@ class backup_wiki_activity_structure_step extends backup_activity_structure_step
FROM {tag} t
JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.itemtype = ?
+ AND ti.component = ?
AND ti.itemid = ?', array(
backup_helper::is_sqlparam('wiki_pages'),
+ backup_helper::is_sqlparam('mod_wiki'),
backup::VAR_PARENTID));
}
diff --git a/mod/wiki/backup/moodle2/restore_wiki_stepslib.php b/mod/wiki/backup/moodle2/restore_wiki_stepslib.php
index bae98ddfcdb..9f43f23cbf8 100644
--- a/mod/wiki/backup/moodle2/restore_wiki_stepslib.php
+++ b/mod/wiki/backup/moodle2/restore_wiki_stepslib.php
@@ -159,7 +159,7 @@ class restore_wiki_activity_structure_step extends restore_activity_structure_st
$data = (object)$data;
$oldid = $data->id;
- if (empty($CFG->usetags)) { // tags disabled in server, nothing to process
+ if (!core_tag_tag::is_enabled('mod_wiki', 'wiki_pages')) { // Tags disabled in server, nothing to process.
return;
}
@@ -167,8 +167,8 @@ class restore_wiki_activity_structure_step extends restore_activity_structure_st
$itemid = $this->get_new_parentid('wiki_page');
$wikiid = $this->get_new_parentid('wiki');
- $cm = get_coursemodule_from_instance('wiki', $wikiid);
- tag_set_add('wiki_pages', $itemid, $tag, 'mod_wiki', context_module::instance($cm->id)->id);
+ $context = context_module::instance($this->task->get_moduleid());
+ core_tag_tag::add_item_tag('mod_wiki', 'wiki_pages', $itemid, $context, $tag);
}
protected function after_execute() {
diff --git a/mod/wiki/db/tag.php b/mod/wiki/db/tag.php
new file mode 100644
index 00000000000..11a44c6f37c
--- /dev/null
+++ b/mod/wiki/db/tag.php
@@ -0,0 +1,33 @@
+.
+
+/**
+ * Tag areas in component mod_wiki
+ *
+ * @package mod_wiki
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+$tagareas = array(
+ array(
+ 'itemtype' => 'wiki_pages',
+ 'component' => 'mod_wiki',
+ ),
+);
diff --git a/mod/wiki/edit_form.php b/mod/wiki/edit_form.php
index 265a172fd55..81e7fcc88fe 100644
--- a/mod/wiki/edit_form.php
+++ b/mod/wiki/edit_form.php
@@ -100,11 +100,11 @@ class mod_wiki_edit_form extends moodleform {
$mform->addElement('hidden', 'contentformat', $format);
$mform->setType('contentformat', PARAM_ALPHANUMEXT);
- if (!empty($CFG->usetags)) {
+ if (core_tag_tag::is_enabled('mod_wiki', 'wiki_pages')) {
$mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
- $mform->addElement('tags', 'tags', get_string('tags'));
- $mform->setType('tags', PARAM_TEXT);
}
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'wiki_pages', 'component' => 'mod_wiki'));
$buttongroup = array();
$buttongroup[] = $mform->createElement('submit', 'editoption', get_string('save', 'wiki'), array('id' => 'save'));
diff --git a/mod/wiki/lang/en/wiki.php b/mod/wiki/lang/en/wiki.php
index 881bcfd4fbb..58fa7e8f945 100644
--- a/mod/wiki/lang/en/wiki.php
+++ b/mod/wiki/lang/en/wiki.php
@@ -219,6 +219,7 @@ $string['searchterms'] = 'Search terms';
$string['searchwikis'] = 'Search wikis';
$string['special'] = 'Special';
$string['tableofcontents'] = 'Table of contents';
+$string['tagarea_wiki_pages'] = 'Wiki pages';
$string['tagsdeleted'] = 'Wiki tags have been deleted';
$string['tagtitle'] = 'See the "{$a}" tag';
$string['teacherrating'] = 'Teacher rating';
diff --git a/mod/wiki/lib.php b/mod/wiki/lib.php
index 997475e64f0..a2def6c17db 100644
--- a/mod/wiki/lib.php
+++ b/mod/wiki/lib.php
@@ -141,10 +141,15 @@ function wiki_delete_instance($id) {
return $result;
}
+/**
+ * Implements callback to reset course
+ *
+ * @param stdClass $data
+ * @return boolean|array
+ */
function wiki_reset_userdata($data) {
global $CFG,$DB;
require_once($CFG->dirroot . '/mod/wiki/pagelib.php');
- require_once($CFG->dirroot . '/tag/lib.php');
require_once($CFG->dirroot . "/mod/wiki/locallib.php");
$componentstr = get_string('modulenameplural', 'wiki');
@@ -154,10 +159,12 @@ function wiki_reset_userdata($data) {
if (!$wikis = $DB->get_records('wiki', array('course' => $data->courseid))) {
return false;
}
- $errors = false;
- foreach ($wikis as $wiki) {
+ if (empty($data->reset_wiki_comments) && empty($data->reset_wiki_tags) && empty($data->reset_wiki_pages)) {
+ return $status;
+ }
- if (!$cm = get_coursemodule_from_instance('wiki', $wiki->id)) {
+ foreach ($wikis as $wiki) {
+ if (!$cm = get_coursemodule_from_instance('wiki', $wiki->id, $data->courseid)) {
continue;
}
$context = context_module::instance($cm->id);
@@ -175,14 +182,7 @@ function wiki_reset_userdata($data) {
if (empty($data->reset_wiki_pages)) {
// Go through each page and delete the tags.
foreach ($pages as $page) {
-
- $tags = tag_get_tags_array('wiki_pages', $page->id);
- foreach ($tags as $tagid => $tagname) {
- // Delete the related tag_instances related to the wiki page.
- $errors = tag_delete_instance('wiki_pages', $page->id, $tagid);
- $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'wiki'),
- 'error' => $errors);
- }
+ core_tag_tag::remove_all_item_tags('mod_wiki', 'wiki_pages', $page->id);
}
} else {
// Otherwise we are removing pages and tags.
@@ -196,17 +196,24 @@ function wiki_reset_userdata($data) {
// Delete any attached files.
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_wiki', 'attachments');
-
- $status[] = array('component' => $componentstr, 'item' => get_string('deleteallpages', 'wiki'),
- 'error' => $errors);
}
}
+
+ if (!empty($data->reset_wiki_pages)) {
+ $status[] = array('component' => $componentstr, 'item' => get_string('deleteallpages', 'wiki'),
+ 'error' => false);
+ }
+ if (!empty($data->reset_wiki_tags)) {
+ $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'wiki'), 'error' => false);
+ }
}
// Remove all comments.
if (!empty($data->reset_wiki_comments) || !empty($data->reset_wiki_pages)) {
$DB->delete_records_select('comments', "contextid = ? AND commentarea='wiki_page'", array($context->id));
- $status[] = array('component' => $componentstr, 'item' => get_string('deleteallcomments'), 'error' => false);
+ if (!empty($data->reset_wiki_comments)) {
+ $status[] = array('component' => $componentstr, 'item' => get_string('deleteallcomments'), 'error' => false);
+ }
}
}
return $status;
diff --git a/mod/wiki/locallib.php b/mod/wiki/locallib.php
index e2395888b4f..f19d4629b42 100644
--- a/mod/wiki/locallib.php
+++ b/mod/wiki/locallib.php
@@ -1163,8 +1163,6 @@ function wiki_delete_pages($context, $pageids = null, $subwikiid = null) {
return;
}
- require_once($CFG->dirroot . '/tag/lib.php');
-
/// Delete page and all it's relevent data
foreach ($pageids as $pageid) {
if (is_object($pageid)) {
@@ -1178,10 +1176,7 @@ function wiki_delete_pages($context, $pageids = null, $subwikiid = null) {
}
//Delete page tags
- $tags = tag_get_tags_array('wiki_pages', $pageid);
- foreach ($tags as $tagid => $tagvalue) {
- tag_delete_instance('wiki_pages', $pageid, $tagid);
- }
+ core_tag_tag::remove_all_item_tags('mod_wiki', 'wiki_pages', $pageid);
//Delete Synonym
wiki_delete_synonym($subwikiid, $pageid);
@@ -1386,18 +1381,8 @@ function wiki_print_page_content($page, $context, $subwikiid) {
$html = format_text($html, FORMAT_MOODLE, array('overflowdiv'=>true, 'allowid'=>true));
echo $OUTPUT->box($html);
- if (!empty($CFG->usetags)) {
- $tags = tag_get_tags_array('wiki_pages', $page->id);
- echo $OUTPUT->container_start('wiki-tags');
- echo ''.get_string('tags').': ';
- $links = array();
- foreach ($tags as $tagid=>$tag) {
- $url = new moodle_url('/tag/index.php', array('tag'=>$tag));
- $links[] = html_writer::link($url, $tag, array('title'=>get_string('tagtitle', 'wiki', $tag)));
- }
- echo join($links, ", ");
- echo $OUTPUT->container_end();
- }
+ echo $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_wiki', 'wiki_pages', $page->id),
+ null, 'wiki-tags');
wiki_increment_pageviews($page);
}
diff --git a/mod/wiki/pagelib.php b/mod/wiki/pagelib.php
index e6a840c099f..3c4ca767cb4 100644
--- a/mod/wiki/pagelib.php
+++ b/mod/wiki/pagelib.php
@@ -34,7 +34,6 @@
*/
require_once($CFG->dirroot . '/mod/wiki/edit_form.php');
-require_once($CFG->dirroot . '/tag/lib.php');
/**
* Class page_wiki contains the common code between all pages
@@ -570,18 +569,9 @@ class page_wiki_edit extends page_wiki {
$params['filearea'] = 'attachments';
}
+ $data->tags = core_tag_tag::get_item_tags_array('mod_wiki', 'wiki_pages', $this->page->id);
+
$form = new mod_wiki_edit_form($url, $params);
-
- if ($formdata = $form->get_data()) {
- if (!empty($CFG->usetags)) {
- $data->tags = $formdata->tags;
- }
- } else {
- if (!empty($CFG->usetags)) {
- $data->tags = tag_get_tags_array('wiki_pages', $this->page->id);
- }
- }
-
$form->set_data($data);
$form->display();
}
@@ -2060,9 +2050,7 @@ class page_wiki_save extends page_wiki_edit {
}
if ($save && $data) {
- if (!empty($CFG->usetags)) {
- tag_set('wiki_pages', $this->page->id, $data->tags, 'mod_wiki', $this->modcontext->id);
- }
+ core_tag_tag::set_item_tags('mod_wiki', 'wiki_pages', $this->page->id, $this->modcontext, $data->tags);
$message = '' . get_string('saving', 'wiki') . '
';
diff --git a/mod/wiki/styles.css b/mod/wiki/styles.css
index d028184ba2f..45153b6e809 100644
--- a/mod/wiki/styles.css
+++ b/mod/wiki/styles.css
@@ -128,10 +128,6 @@
text-align: right;
}
-.wiki-tags span {
- font-weight: bold;
-}
-
.wiki_modifieduser p {
line-height: 35px;
}
diff --git a/mod/wiki/tests/behat/edit_tags.feature b/mod/wiki/tests/behat/edit_tags.feature
index dc957e27552..5899b8c4056 100644
--- a/mod/wiki/tests/behat/edit_tags.feature
+++ b/mod/wiki/tests/behat/edit_tags.feature
@@ -49,6 +49,7 @@ Feature: Edited wiki pages handle tags correctly
And I expand "Site administration" node
And I expand "Appearance" node
And I follow "Manage tags"
+ And I follow "Default collection"
And I set the field "otagsadd" to "OT1, OT2, OT3"
And I press "Add official tags"
And I log out
diff --git a/mod/wiki/version.php b/mod/wiki/version.php
index 38719eee369..d82e96b62f4 100644
--- a/mod/wiki/version.php
+++ b/mod/wiki/version.php
@@ -33,7 +33,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2015111602; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2016011100; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2015111000; // Requires this Moodle version
$plugin->component = 'mod_wiki'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
diff --git a/question/format.php b/question/format.php
index 2f3744f5e47..b2ddad210d7 100644
--- a/question/format.php
+++ b/question/format.php
@@ -425,9 +425,8 @@ class qformat_default {
$result = question_bank::get_qtype($question->qtype)->save_question_options($question);
- if (!empty($CFG->usetags) && isset($question->tags)) {
- require_once($CFG->dirroot . '/tag/lib.php');
- tag_set('question', $question->id, $question->tags, 'core_question', $question->context->id);
+ if (isset($question->tags)) {
+ core_tag_tag::set_item_tags('core_question', 'question', $question->context, $question->id, $question->tags);
}
if (!empty($result->error)) {
diff --git a/question/format/xml/format.php b/question/format/xml/format.php
index 8bcada5abd7..efd209c6579 100644
--- a/question/format/xml/format.php
+++ b/question/format/xml/format.php
@@ -383,9 +383,9 @@ class qformat_xml extends qformat_default {
public function import_question_tags($qo, $questionxml) {
global $CFG;
- if (!empty($CFG->usetags) && array_key_exists('tags', $questionxml['#'])
+ if (core_tag_tag::is_enabled('core_question', 'question')
+ && array_key_exists('tags', $questionxml['#'])
&& !empty($questionxml['#']['tags'][0]['#']['tag'])) {
- require_once($CFG->dirroot.'/tag/lib.php');
$qo->tags = array();
foreach ($questionxml['#']['tags'][0]['#']['tag'] as $tagdata) {
$qo->tags[] = $this->getpath($tagdata, array('#', 'text', 0, '#'), '', true);
@@ -1467,16 +1467,13 @@ class qformat_xml extends qformat_default {
$expout .= $this->write_hints($question);
// Write the question tags.
- if (!empty($CFG->usetags)) {
- require_once($CFG->dirroot.'/tag/lib.php');
- $tags = tag_get_tags_array('question', $question->id);
- if (!empty($tags)) {
- $expout .= " \n";
- foreach ($tags as $tag) {
- $expout .= " " . $this->writetext($tag, 0, true) . "\n";
- }
- $expout .= " \n";
+ $tags = core_tag_tag::get_item_tags_array('core_question', 'question', $question->id);
+ if (!empty($tags)) {
+ $expout .= " \n";
+ foreach ($tags as $tag) {
+ $expout .= " " . $this->writetext($tag, 0, true) . "\n";
}
+ $expout .= " \n";
}
// Close the question tag.
diff --git a/question/format/xml/tests/xmlformat_test.php b/question/format/xml/tests/xmlformat_test.php
index e7c180d7083..8ed027164fb 100644
--- a/question/format/xml/tests/xmlformat_test.php
+++ b/question/format/xml/tests/xmlformat_test.php
@@ -29,7 +29,6 @@ global $CFG;
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot . '/question/format/xml/format.php');
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
-require_once($CFG->dirroot . '/tag/lib.php');
/**
diff --git a/question/question.php b/question/question.php
index 0e75f4dc8bd..c47be702198 100644
--- a/question/question.php
+++ b/question/question.php
@@ -262,11 +262,9 @@ if ($mform->is_cancelled()) {
}
}
$question = $qtypeobj->save_question($question, $fromform);
- if (!empty($CFG->usetags) && isset($fromform->tags)) {
- // A wizardpage from multipe pages questiontype like calculated may not
- // allow editing the question tags, hence the isset($fromform->tags) test.
- require_once($CFG->dirroot.'/tag/lib.php');
- tag_set('question', $question->id, $fromform->tags, 'core_question', $contextid);
+ if (isset($fromform->tags)) {
+ core_tag_tag::set_item_tags('core_question', 'question', $question->id,
+ context::instance_by_id($contextid), $fromform->tags);
}
// Purge this question from the cache.
diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php
index d0e3d278d93..636c858a39e 100644
--- a/question/type/edit_question_form.php
+++ b/question/type/edit_question_form.php
@@ -201,10 +201,11 @@ abstract class question_edit_form extends question_wizard_form {
// Any questiontype specific fields.
$this->definition_inner($mform);
- if (!empty($CFG->usetags)) {
+ if (core_tag_tag::is_enabled('core_question', 'question')) {
$mform->addElement('header', 'tagsheader', get_string('tags'));
- $mform->addElement('tags', 'tags', get_string('tags'));
}
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'question', 'component' => 'core_question'));
if (!empty($this->question->id)) {
$mform->addElement('header', 'createdmodifiedheader',
diff --git a/tag/classes/area.php b/tag/classes/area.php
new file mode 100644
index 00000000000..49ed67e2f92
--- /dev/null
+++ b/tag/classes/area.php
@@ -0,0 +1,450 @@
+.
+
+/**
+ * Class core_tag_area for managing tag areas
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class to manage tag areas
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_tag_area {
+
+ /**
+ * Returns the list of areas indexed by itemtype and component
+ *
+ * @param int $tagcollid return only areas in this tag collection
+ * @param bool $enabledonly return only enabled tag areas
+ * @return array itemtype=>component=>tagarea object
+ */
+ public static function get_areas($tagcollid = null, $enabledonly = false) {
+ global $DB;
+ $cache = cache::make('core', 'tags');
+ if (($itemtypes = $cache->get('tag_area')) === false) {
+ $colls = core_tag_collection::get_collections();
+ $defaultcoll = reset($colls);
+ $itemtypes = array();
+ $areas = $DB->get_records('tag_area', array(), 'component,itemtype');
+ foreach ($areas as $area) {
+ if ($colls[$area->tagcollid]->component) {
+ $area->locked = true;
+ }
+ $itemtypes[$area->itemtype][$area->component] = $area;
+ }
+ $cache->set('tag_area', $itemtypes);
+ }
+ if ($tagcollid || $enabledonly) {
+ $rv = array();
+ foreach ($itemtypes as $itemtype => $it) {
+ foreach ($it as $component => $v) {
+ if (($v->tagcollid == $tagcollid || !$tagcollid) && (!$enabledonly || $v->enabled)) {
+ $rv[$itemtype][$component] = $v;
+ }
+ }
+ }
+ return $rv;
+ }
+ return $itemtypes;
+ }
+
+ /**
+ * Retrieves info about one tag area
+ *
+ * @param int $tagareaid
+ * @return stdClass
+ */
+ public static function get_by_id($tagareaid) {
+ $tagareas = self::get_areas();
+ foreach ($tagareas as $itemtype => $it) {
+ foreach ($it as $component => $v) {
+ if ($v->id == $tagareaid) {
+ return $v;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the display name for this area
+ *
+ * @param string $component
+ * @param string $itemtype
+ * @return lang_string
+ */
+ public static function display_name($component, $itemtype) {
+ $identifier = 'tagarea_' . clean_param($itemtype, PARAM_STRINGID);
+ if ($component === 'core') {
+ $component = 'tag';
+ }
+ return new lang_string($identifier, $component);
+ }
+
+ /**
+ * Returns whether the tag area is enabled
+ *
+ * @param string $component component responsible for tagging
+ * @param string $itemtype what is being tagged, for example, 'post', 'course', 'user', etc.
+ * @return bool|null
+ */
+ public static function is_enabled($component, $itemtype) {
+ global $CFG;
+ if (empty($CFG->usetags)) {
+ return false;
+ }
+ $itemtypes = self::get_areas();
+ if (isset($itemtypes[$itemtype][$component])) {
+ return $itemtypes[$itemtype][$component]->enabled ? true : false;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the id of the tag collection that should be used for storing tags of this itemtype
+ *
+ * @param string $component component responsible for tagging
+ * @param string $itemtype what is being tagged, for example, 'post', 'course', 'user', etc.
+ * @return int
+ */
+ public static function get_collection($component, $itemtype) {
+ $itemtypes = self::get_areas();
+ if (array_key_exists($itemtype, $itemtypes)) {
+ if (!array_key_exists($component, $itemtypes[$itemtype])) {
+ $component = key($itemtypes[$itemtype]);
+ }
+ return $itemtypes[$itemtype][$component]->tagcollid;
+ }
+ return core_tag_collection::get_default();
+ }
+
+ /**
+ * Returns all tag areas and collections that are currently cached in DB for this component
+ *
+ * @param string $componentname
+ * @return array first element is the list of areas and the second list of collections
+ */
+ protected static function get_definitions_for_component($componentname) {
+ global $DB;
+ list($a, $b) = core_component::normalize_component($componentname);
+ $component = $b ? ($a . '_' . $b) : $a;
+ $sql = 'component = :component';
+ $params = array('component' => $component);
+ if ($component === 'core') {
+ $sql .= ' OR component LIKE :coreprefix';
+ $params['coreprefix'] = 'core_%';
+ }
+ $fields = $DB->sql_concat_join("':'", array('itemtype', 'component'));
+ $existingareas = $DB->get_records_sql(
+ "SELECT $fields AS returnkey, a.* FROM {tag_area} a WHERE $sql", $params);
+ $fields = $DB->sql_concat_join("':'", array('name', 'component'));
+ $existingcolls = $DB->get_records_sql(
+ "SELECT $fields AS returnkey, t.* FROM {tag_coll} t WHERE $sql", $params);
+ return array($existingareas, $existingcolls);
+
+ }
+
+ /**
+ * Completely delete a tag area and all instances inside it
+ *
+ * @param stdClass $record
+ */
+ protected static function delete($record) {
+ global $DB;
+
+ core_tag_tag::delete_instances($record->component, $record->itemtype);
+
+ $DB->delete_records('tag_area',
+ array('itemtype' => $record->itemtype,
+ 'component' => $record->component));
+
+ // Reset cache.
+ cache::make('core', 'tags')->delete('tag_area');
+ }
+
+ /**
+ * Create a new tag area
+ *
+ * @param stdClass $record
+ */
+ protected static function create($record) {
+ global $DB;
+ if (empty($record->tagcollid)) {
+ $record->tagcollid = core_tag_collection::get_default();
+ }
+ $DB->insert_record('tag_area', array('component' => $record->component,
+ 'itemtype' => $record->itemtype,
+ 'tagcollid' => $record->tagcollid,
+ 'callback' => $record->callback,
+ 'callbackfile' => $record->callbackfile));
+
+ // Reset cache.
+ cache::make('core', 'tags')->delete('tag_area');
+ }
+
+ /**
+ * Update the tag area
+ *
+ * @param stdClass $existing current record from DB table tag_area
+ * @param array|stdClass $data fields that need updating
+ */
+ public static function update($existing, $data) {
+ global $DB;
+ $data = array_intersect_key((array)$data,
+ array('enabled' => 1, 'tagcollid' => 1,
+ 'callback' => 1, 'callbackfile' => 1));
+ foreach ($data as $key => $value) {
+ if ($existing->$key == $value) {
+ unset($data[$key]);
+ }
+ }
+ if (!$data) {
+ return;
+ }
+
+ if (!empty($data['tagcollid'])) {
+ self::move_tags($existing->component, $existing->itemtype, $data['tagcollid']);
+ }
+
+ $data['id'] = $existing->id;
+ $DB->update_record('tag_area', $data);
+
+ // Reset cache.
+ cache::make('core', 'tags')->delete('tag_area');
+ }
+
+ /**
+ * Update the database to contain a list of tagged areas for a component.
+ * The list of tagged areas is read from [plugindir]/db/tag.php
+ *
+ * @param string $componentname - The frankenstyle component name.
+ */
+ public static function reset_definitions_for_component($componentname) {
+ global $DB;
+ $dir = core_component::get_component_directory($componentname);
+ $file = $dir . '/db/tag.php';
+ $tagareas = null;
+ if (file_exists($file)) {
+ require_once($file);
+ }
+
+ list($a, $b) = core_component::normalize_component($componentname);
+ $component = $b ? ($a . '_' . $b) : $a;
+
+ list($existingareas, $existingcolls) = self::get_definitions_for_component($componentname);
+
+ $itemtypes = array();
+ $collections = array();
+ $needcleanup = false;
+ if ($tagareas) {
+ foreach ($tagareas as $tagarea) {
+ $record = (object)$tagarea;
+ if ($component !== 'core' || empty($record->component)) {
+ if (isset($record->component) && $record->component !== $component) {
+ debugging("Item type {$record->itemtype} has illegal component {$record->component}", DEBUG_DEVELOPER);
+ }
+ $record->component = $component;
+ }
+ unset($record->tagcollid);
+ if (!empty($record->collection)) {
+ // Create collection if it does not exist, or update 'searchable' and/or 'customurl' if needed.
+ $key = $record->collection . ':' . $record->component;
+ $collectiondata = array_intersect_key((array)$record,
+ array('component' => 1, 'searchable' => 1, 'customurl' => 1));
+ $collectiondata['name'] = $record->collection;
+ if (!array_key_exists($key, $existingcolls)) {
+ $existingcolls[$key] = core_tag_collection::create($collectiondata);
+ } else {
+ core_tag_collection::update($existingcolls[$key], $collectiondata);
+ }
+ $record->tagcollid = $existingcolls[$key]->id;
+ $collections[$key] = $existingcolls[$key];
+ unset($record->collection);
+ }
+ unset($record->searchable);
+ unset($record->customurl);
+ if (!isset($record->callback)) {
+ $record->callback = null;
+ }
+ if (!isset($record->callbackfile)) {
+ $record->callbackfile = null;
+ }
+ $itemtypes[$record->itemtype . ':' . $record->component] = $record;
+ }
+ }
+ $todeletearea = array_diff_key($existingareas, $itemtypes);
+ $todeletecoll = array_diff_key($existingcolls, $collections);
+
+ // Delete tag areas that are no longer needed.
+ foreach ($todeletearea as $key => $record) {
+ self::delete($record);
+ }
+
+ // Update tag areas if changed.
+ $toupdatearea = array_intersect_key($existingareas, $itemtypes);
+ foreach ($toupdatearea as $key => $tagarea) {
+ if (!isset($itemtypes[$key]->tagcollid)) {
+ foreach ($todeletecoll as $tagcoll) {
+ if ($tagcoll->id == $tagarea->tagcollid) {
+ $itemtypes[$key]->tagcollid = core_tag_collection::get_default();
+ }
+ }
+ }
+ self::update($tagarea, $itemtypes[$key]);
+ }
+
+ // Create new tag areas.
+ $toaddarea = array_diff_key($itemtypes, $existingareas);
+ foreach ($toaddarea as $record) {
+ self::create($record);
+ }
+
+ // Delete tag collections that are no longer needed.
+ foreach ($todeletecoll as $key => $tagcoll) {
+ core_tag_collection::delete($tagcoll);
+ }
+ }
+
+ /**
+ * Deletes all tag areas, collections and instances associated with the plugin.
+ *
+ * @param string $pluginname
+ */
+ public static function uninstall($pluginname) {
+ global $DB;
+
+ list($a, $b) = core_component::normalize_component($pluginname);
+ if (empty($b) || $a === 'core') {
+ throw new coding_exception('Core component can not be uninstalled');
+ }
+ $component = $a . '_' . $b;
+
+ core_tag_tag::delete_instances($component);
+
+ $DB->delete_records('tag_area', array('component' => $component));
+ $DB->delete_records('tag_coll', array('component' => $component));
+ cache::make('core', 'tags')->delete_many(array('tag_area', 'tag_coll'));
+ }
+
+ /**
+ * Moves existing tags associated with an item type to another tag collection
+ *
+ * @param string $component
+ * @param string $itemtype
+ * @param int $tagcollid
+ */
+ public static function move_tags($component, $itemtype, $tagcollid) {
+ global $DB;
+ $params = array('itemtype1' => $itemtype, 'component1' => $component,
+ 'itemtype2' => $itemtype, 'component2' => $component,
+ 'tagcollid1' => $tagcollid, 'tagcollid2' => $tagcollid);
+
+ // Find all collections that need to be cleaned later.
+ $sql = "SELECT DISTINCT t.tagcollid " .
+ "FROM {tag_instance} ti " .
+ "JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 " .
+ "WHERE ti.itemtype = :itemtype2 AND ti.component = :component2 ";
+ $cleanupcollections = $DB->get_fieldset_sql($sql, $params);
+
+ // Find all tags that are related to the tags being moved and make sure they are present in the target tagcoll.
+ $sql = "SELECT DISTINCT r.name, r.rawname, r.description, r.descriptionformat, ".
+ " r.userid, r.tagtype, r.flag ".
+ "FROM {tag_instance} ti ". // Instances that need moving.
+ "JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 ". // Tags that need moving.
+ "JOIN {tag_instance} tr ON tr.itemtype = 'tag' and tr.component = 'core' AND tr.itemid = t.id ".
+ "JOIN {tag} r ON r.id = tr.tagid ". // Tags related to the tags that need moving.
+ "LEFT JOIN {tag} re ON re.name = r.name AND re.tagcollid = :tagcollid2 ". // Existing tags in the target tagcoll with the same name as related tags.
+ "WHERE ti.itemtype = :itemtype2 AND ti.component = :component2 ".
+ " AND re.id IS NULL"; // We need related tags that ARE NOT present in the target tagcoll.
+ $result = $DB->get_records_sql($sql, $params);
+ foreach ($result as $tag) {
+ $tag->tagcollid = $tagcollid;
+ $tag->id = $DB->insert_record('tag', $tag);
+ \core\event\tag_created::create_from_tag($tag);
+ }
+
+ // Find all tags that need moving and have related tags, remember their related tags.
+ $sql = "SELECT t.name AS tagname, r.rawname AS relatedtag ".
+ "FROM {tag_instance} ti ". // Instances that need moving.
+ "JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1 ". // Tags that need moving.
+ "JOIN {tag_instance} tr ON t.id = tr.tagid AND tr.itemtype = 'tag' and tr.component = 'core' ".
+ "JOIN {tag} r ON r.id = tr.itemid ". // Tags related to the tags that need moving.
+ "WHERE ti.itemtype = :itemtype2 AND ti.component = :component2 ".
+ "ORDER BY t.id, tr.ordering ";
+ $relatedtags = array();
+ $result = $DB->get_recordset_sql($sql, $params);
+ foreach ($result as $record) {
+ $relatedtags[$record->tagname][] = $record->relatedtag;
+ }
+ $result->close();
+
+ // Find all tags that are used for this itemtype/component and are not present in the target tag collection.
+ $sql = "SELECT DISTINCT t.id, t.name, t.rawname, t.description, t.descriptionformat,
+ t.userid, t.tagtype, t.flag
+ FROM {tag_instance} ti
+ JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1
+ LEFT JOIN {tag} tt ON tt.name = t.name AND tt.tagcollid = :tagcollid2
+ WHERE ti.itemtype = :itemtype2 AND ti.component = :component2
+ AND tt.id IS NULL";
+ $todelete = array();
+ $result = $DB->get_records_sql($sql, $params);
+ foreach ($result as $tag) {
+ $originaltagid = $tag->id;
+ unset($tag->id);
+ $tag->tagcollid = $tagcollid;
+ $tag->id = $DB->insert_record('tag', $tag);
+ \core\event\tag_created::create_from_tag($tag);
+ $DB->execute("UPDATE {tag_instance} SET tagid = ? WHERE tagid = ? AND itemtype = ? AND component = ?",
+ array($tag->id, $originaltagid, $itemtype, $component));
+ }
+
+ // Find all tags that are used for this itemtype/component and are already present in the target tag collection.
+ $sql = "SELECT DISTINCT t.id, tt.id AS targettagid
+ FROM {tag_instance} ti
+ JOIN {tag} t ON t.id = ti.tagid AND t.tagcollid <> :tagcollid1
+ JOIN {tag} tt ON tt.name = t.name AND tt.tagcollid = :tagcollid2
+ WHERE ti.itemtype = :itemtype2 AND ti.component = :component2";
+ $result = $DB->get_records_sql($sql, $params);
+ foreach ($result as $tag) {
+ $DB->execute("UPDATE {tag_instance} SET tagid = ? WHERE tagid = ? AND itemtype = ? AND component = ?",
+ array($tag->targettagid, $tag->id, $itemtype, $component));
+ }
+
+ // Add related tags to the moved tags.
+ if ($relatedtags) {
+ $tags = core_tag_tag::get_by_name_bulk($tagcollid, array_keys($relatedtags));
+ foreach ($tags as $tag) {
+ $tag->add_related_tags($relatedtags[$tag->name]);
+ }
+ }
+
+ if ($cleanupcollections) {
+ core_tag_collection::cleanup_unused_tags($cleanupcollections);
+ }
+
+ // Reset caches.
+ cache::make('core', 'tags')->delete('tag_area');
+ }
+}
diff --git a/tag/classes/areas_table.php b/tag/classes/areas_table.php
new file mode 100644
index 00000000000..495f011d7ad
--- /dev/null
+++ b/tag/classes/areas_table.php
@@ -0,0 +1,95 @@
+.
+
+/**
+ * Contains class core_tag_areas_table
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Table with the list of available tag areas for "Manage tags" page.
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_tag_areas_table extends html_table {
+
+ /**
+ * Constructor
+ *
+ * @param string|moodle_url $pageurl
+ */
+ public function __construct($pageurl) {
+ global $OUTPUT;
+ parent::__construct();
+
+ $this->attributes['class'] = 'generaltable tag-areas-table';
+
+ $this->head = array(
+ get_string('tagareaname', 'core_tag'),
+ get_string('component', 'tag'),
+ get_string('tagareaenabled', 'core_tag'),
+ get_string('tagcollection', 'tag'),
+ );
+
+ $this->data = array();
+ $this->rowclasses = array();
+
+ $tagareas = core_tag_area::get_areas();
+ $tagcollections = core_tag_collection::get_collections_menu(true);
+ $tagcollectionsall = core_tag_collection::get_collections_menu();
+
+ foreach ($tagareas as $itemtype => $it) {
+ foreach ($it as $component => $record) {
+ $areaname = core_tag_area::display_name($record->component, $record->itemtype);
+ $baseurl = new moodle_url($pageurl, array('ta' => $record->id, 'sesskey' => sesskey()));
+ if ($record->enabled) {
+ $enableurl = new moodle_url($baseurl, array('action' => 'areadisable'));
+ $enabled = html_writer::link($enableurl, $OUTPUT->pix_icon('i/hide', get_string('disable')));
+ } else {
+ $enableurl = new moodle_url($baseurl, array('action' => 'areaenable'));
+ $enabled = html_writer::link($enableurl, $OUTPUT->pix_icon('i/show', get_string('enable')));
+ }
+
+ if ($record->enabled && empty($record->locked) && count($tagcollections) > 1) {
+ $changecollurl = new moodle_url($baseurl, array('action' => 'areasetcoll'));
+
+ $select = new single_select($changecollurl, 'areacollid', $tagcollections, $record->tagcollid, null);
+ $select->set_label(get_string('changetagcoll', 'core_tag', $areaname), array('class' => 'accesshide'));
+ $collectionselect = $OUTPUT->render($select);
+ } else {
+ $collectionselect = $tagcollectionsall[$record->tagcollid];
+ }
+ $this->data[] = array(
+ $areaname,
+ ($record->component === 'core' || preg_match('/^core_/', $record->component)) ?
+ get_string('coresystem') : get_string('pluginname', $record->component),
+ $enabled,
+ $collectionselect
+ );
+ $this->rowclasses[] = $record->enabled ? '' : 'dimmed_text';
+ }
+ }
+
+ }
+
+}
diff --git a/tag/classes/collection.php b/tag/classes/collection.php
new file mode 100644
index 00000000000..c8be5d27501
--- /dev/null
+++ b/tag/classes/collection.php
@@ -0,0 +1,419 @@
+.
+
+/**
+ * Class to manage tag collections
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class to manage tag collections
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_tag_collection {
+
+ /** @var string used for function cloud_sort() */
+ public static $cloudsortfield = 'name';
+
+ /**
+ * Returns the list of tag collections defined in the system.
+ *
+ * @param bool $onlysearchable only return collections that can be searched.
+ * @return array array of objects where each object has properties: id, name, isdefault, itemtypes, sortorder
+ */
+ public static function get_collections($onlysearchable = false) {
+ global $DB;
+ $cache = cache::make('core', 'tags');
+ if (($tagcolls = $cache->get('tag_coll')) === false) {
+ // Retrieve records from DB and create a default one if it is not present.
+ $tagcolls = $DB->get_records('tag_coll', null, 'isdefault DESC, sortorder, id');
+ if (empty($tagcolls)) {
+ // When this method is called for the first time it automatically creates the default tag collection.
+ $DB->insert_record('tag_coll', array('isdefault' => 1, 'sortorder' => 0));
+ $tagcolls = $DB->get_records('tag_coll');
+ } else {
+ // Make sure sortorder is correct.
+ $idx = 0;
+ foreach ($tagcolls as $id => $tagcoll) {
+ if ($tagcoll->sortorder != $idx) {
+ $DB->update_record('tag_coll', array('sortorder' => $idx, 'id' => $id));
+ $tagcolls[$id]->sortorder = $idx;
+ }
+ $idx++;
+ }
+ }
+ $cache->set('tag_coll', $tagcolls);
+ }
+ if ($onlysearchable) {
+ $rv = array();
+ foreach ($tagcolls as $id => $tagcoll) {
+ if ($tagcoll->searchable) {
+ $rv[$id] = $tagcoll;
+ }
+ }
+ return $rv;
+ }
+ return $tagcolls;
+ }
+
+ /**
+ * Returns the tag collection object
+ *
+ * @param int $tagcollid
+ * @return stdClass
+ */
+ public static function get_by_id($tagcollid) {
+ $tagcolls = self::get_collections();
+ if (array_key_exists($tagcollid, $tagcolls)) {
+ return $tagcolls[$tagcollid];
+ }
+ return null;
+ }
+
+ /**
+ * Returns the list of existing tag collections as id=>name
+ *
+ * @param bool $unlockedonly
+ * @param bool $onlysearchable
+ * @param string $selectalllabel
+ * @return array
+ */
+ public static function get_collections_menu($unlockedonly = false, $onlysearchable = false,
+ $selectalllabel = null) {
+ $tagcolls = self::get_collections($onlysearchable);
+ $options = array();
+ foreach ($tagcolls as $id => $tagcoll) {
+ if (!$unlockedonly || empty($tagcoll->component)) {
+ $options[$id] = self::display_name($tagcoll);
+ }
+ }
+ if (count($options) > 1 && $selectalllabel) {
+ $options = array(0 => $selectalllabel) + $options;
+ }
+ return $options;
+ }
+
+ /**
+ * Returns id of the default tag collection
+ *
+ * @return int
+ */
+ public static function get_default() {
+ $collections = self::get_collections();
+ $keys = array_keys($collections);
+ return $keys[0];
+ }
+
+ /**
+ * Returns formatted name of the tag collection
+ *
+ * @param stdClass $record record from DB table tag_coll
+ * @return string
+ */
+ public static function display_name($record) {
+ $syscontext = context_system::instance();
+ if (!empty($record->component)) {
+ $identifier = 'tagcollection_' .
+ clean_param($record->name, PARAM_STRINGID);
+ $component = $record->component;
+ if ($component === 'core') {
+ $component = 'tag';
+ }
+ return get_string($identifier, $component);
+ }
+ if (!empty($record->name)) {
+ return format_string($record->name, true, $syscontext);
+ } else if ($record->isdefault) {
+ return get_string('defautltagcoll', 'tag');
+ } else {
+ return $record->id;
+ }
+ }
+
+ /**
+ * Returns all tag areas in the given tag collection
+ *
+ * @param int $tagcollid
+ * @return array
+ */
+ public static function get_areas($tagcollid) {
+ $allitemtypes = core_tag_area::get_areas($tagcollid, true);
+ $itemtypes = array();
+ foreach ($allitemtypes as $itemtype => $it) {
+ foreach ($it as $component => $v) {
+ $itemtypes[$v->id] = $v;
+ }
+ }
+ return $itemtypes;
+ }
+
+ /**
+ * Returns the list of names of areas (enabled only) that are in this collection.
+ *
+ * @param int $tagcollid
+ * @return array
+ */
+ public static function get_areas_names($tagcollid) {
+ $allitemtypes = core_tag_area::get_areas($tagcollid, true);
+ $itemtypes = array();
+ foreach ($allitemtypes as $itemtype => $it) {
+ foreach ($it as $component => $v) {
+ $itemtypes[] = core_tag_area::display_name($component, $itemtype);
+ }
+ }
+ return $itemtypes;
+ }
+
+ /**
+ * Creates a new tag collection
+ *
+ * @param stdClass $data data from form core_tag_collection_form
+ * @return int|false id of created tag collection or false if failed
+ */
+ public static function create($data) {
+ global $DB;
+ $data = (object)$data;
+ $tagcolls = self::get_collections();
+ $tagcoll = (object)array(
+ 'name' => $data->name,
+ 'isdefault' => 0,
+ 'component' => !empty($data->component) ? $data->component : null,
+ 'sortorder' => count($tagcolls),
+ 'searchable' => isset($data->searchable) ? (int)(bool)$data->searchable : 1,
+ 'customurl' => !empty($data->customurl) ? $data->customurl : null,
+ );
+ $tagcoll->id = $DB->insert_record('tag_coll', $tagcoll);
+
+ // Reset cache.
+ cache::make('core', 'tags')->delete('tag_coll');
+
+ \core\event\tag_collection_created::create_from_record($tagcoll)->trigger();
+ return $tagcoll;
+ }
+
+ /**
+ * Updates the tag collection information
+ *
+ * @param stdClass $tagcoll existing record in DB table tag_coll
+ * @param stdClass $data data from form core_tag_collection_form
+ * @return bool wether the record was updated
+ */
+ public static function update($tagcoll, $data) {
+ global $DB;
+ $defaulttagcollid = self::get_default();
+ $allowedfields = array('name', 'searchable', 'customurl');
+ if ($tagcoll->id == $defaulttagcollid) {
+ $allowedfields = array('name');
+ }
+
+ $updatedata = array();
+ $data = (array)$data;
+ foreach ($allowedfields as $key) {
+ if (array_key_exists($key, $data) && $data[$key] !== $tagcoll->$key) {
+ $updatedata[$key] = $data[$key];
+ }
+ }
+
+ if (!$updatedata) {
+ // Nothing to update.
+ return false;
+ }
+
+ if (isset($updatedata['searchable'])) {
+ $updatedata['searchable'] = (int)(bool)$updatedata['searchable'];
+ }
+ foreach ($updatedata as $key => $value) {
+ $tagcoll->$key = $value;
+ }
+ $updatedata['id'] = $tagcoll->id;
+ $DB->update_record('tag_coll', $updatedata);
+
+ // Reset cache.
+ cache::make('core', 'tags')->delete('tag_coll');
+
+ \core\event\tag_collection_updated::create_from_record($tagcoll)->trigger();
+
+ return true;
+ }
+
+ /**
+ * Deletes a custom tag collection
+ *
+ * @param stdClass $tagcoll existing record in DB table tag_coll
+ * @return bool wether the tag collection was deleted
+ */
+ public static function delete($tagcoll) {
+ global $DB, $CFG;
+
+ $defaulttagcollid = self::get_default();
+ if ($tagcoll->id == $defaulttagcollid) {
+ return false;
+ }
+
+ // Move all tags from this tag collection to the default one.
+ $allitemtypes = core_tag_area::get_areas($tagcoll->id);
+ foreach ($allitemtypes as $it) {
+ foreach ($it as $v) {
+ core_tag_area::update($v, array('tagcollid' => $defaulttagcollid));
+ }
+ }
+
+ // Delete tags from this tag_coll.
+ core_tag_tag::delete_tags($DB->get_fieldset_select('tag', 'id', 'tagcollid = ?', array($tagcoll->id)));
+
+ // Delete the tag collection.
+ $DB->delete_records('tag_coll', array('id' => $tagcoll->id));
+
+ // Reset cache.
+ cache::make('core', 'tags')->delete('tag_coll');
+
+ \core\event\tag_collection_deleted::create_from_record($tagcoll)->trigger();
+
+ return true;
+ }
+
+ /**
+ * Moves the tag collection in the list one position up or down
+ *
+ * @param stdClass $tagcoll existing record in DB table tag_coll
+ * @param int $direction move direction: +1 or -1
+ * @return bool
+ */
+ public static function change_sortorder($tagcoll, $direction) {
+ global $DB;
+ if ($direction != -1 && $direction != 1) {
+ throw coding_exception('Second argument in tag_coll_change_sortorder() can be only 1 or -1');
+ }
+ $tagcolls = self::get_collections();
+ $keys = array_keys($tagcolls);
+ $idx = array_search($tagcoll->id, $keys);
+ if ($idx === false || $idx == 0 || $idx + $direction < 1 || $idx + $direction >= count($tagcolls)) {
+ return false;
+ }
+ $otherid = $keys[$idx + $direction];
+ $DB->update_record('tag_coll', array('id' => $tagcoll->id, 'sortorder' => $idx + $direction));
+ $DB->update_record('tag_coll', array('id' => $otherid, 'sortorder' => $idx));
+ // Reset cache.
+ cache::make('core', 'tags')->delete('tag_coll');
+ return true;
+ }
+
+ /**
+ * Permanently deletes all non-official tags that no longer have any instances pointing to them
+ *
+ * @param array $collections optional list of tag collections ids to cleanup
+ */
+ public static function cleanup_unused_tags($collections = null) {
+ global $DB, $CFG;
+
+ $params = array();
+ $sql = "SELECT tg.id FROM {tag} tg LEFT OUTER JOIN {tag_instance} ti ON ti.tagid = tg.id
+ WHERE ti.id IS NULL AND tg.tagtype = 'default'";
+ if ($collections) {
+ list($sqlcoll, $params) = $DB->get_in_or_equal($collections);
+ $sql .= " AND tg.tagcollid " . $sqlcoll;
+ }
+ if ($unusedtags = $DB->get_fieldset_sql($sql, $params)) {
+ core_tag_tag::delete_tags($unusedtags);
+ }
+ }
+
+ /**
+ * Returns the list of tags with number of items tagged
+ *
+ * @param int $tagcollid
+ * @param string $tagtype possible values 'official', 'default' or empty for any tag type
+ * @param int $limit maximum number of tags to retrieve, tags are sorted by the instance count
+ * descending here regardless of $sort parameter
+ * @param string $sort sort order for display, default 'name' - tags will be sorted after they are retrieved
+ * @param string $search search string
+ * @param int $fromctx context id where this tag cloud is displayed
+ * @param int $ctx only retrieve tag instances in this context
+ * @param int $rec retrieve tag instances in the $ctx context and it's children (default 1)
+ * @return \core_tag\output\tagcloud
+ */
+ public static function get_tag_cloud($tagcollid, $tagtype = '', $limit = 150, $sort = 'name',
+ $search = '', $fromctx = 0, $ctx = 0, $rec = 1) {
+ global $DB;
+
+ $fromclause = 'FROM {tag_instance} ti JOIN {tag} tg ON tg.id = ti.tagid';
+ $whereclause = 'WHERE ti.itemtype <> \'tag\'';
+ list($sql, $params) = $DB->get_in_or_equal($tagcollid ? array($tagcollid) :
+ array_keys(self::get_collections(true)));
+ $whereclause .= ' AND tg.tagcollid ' . $sql;
+ if (!empty($tagtype)) {
+ $whereclause .= ' AND tg.tagtype = ?';
+ $params[] = $tagtype;
+ }
+ $context = $ctx ? context::instance_by_id($ctx) : context_system::instance();
+ if ($rec && $context->contextlevel != CONTEXT_SYSTEM) {
+ $fromclause .= ' JOIN {context} ctx ON ctx.id = ti.contextid ';
+ $whereclause .= ' AND ctx.path LIKE ?';
+ $params[] = $context->path . '%';
+ } else if (!$rec) {
+ $whereclause .= ' AND ti.contextid = ?';
+ $params[] = $context->id;
+ }
+ if (strval($search) !== '') {
+ $whereclause .= ' AND tg.name LIKE ?';
+ $params[] = '%' . core_text::strtolower($search) . '%';
+ }
+ $tagsincloud = $DB->get_records_sql(
+ "SELECT tg.id, tg.rawname, tg.name, tg.tagtype, COUNT(ti.id) AS count, tg.flag, tg.tagcollid
+ $fromclause
+ $whereclause
+ GROUP BY tg.id, tg.rawname, tg.name, tg.flag, tg.tagtype, tg.tagcollid
+ ORDER BY count DESC, tg.name ASC",
+ $params, 0, $limit);
+
+ $tagscount = count($tagsincloud);
+ if ($tagscount == $limit) {
+ $tagscount = $DB->get_field_sql("SELECT COUNT(DISTINCT tg.id) $fromclause $whereclause", $params);
+ }
+
+ self::$cloudsortfield = $sort;
+ usort($tagsincloud, "self::cloud_sort");
+
+ return new core_tag\output\tagcloud($tagsincloud, $tagscount, $fromctx, $ctx, $rec);
+ }
+
+ /**
+ * This function is used to sort the tags in the cloud.
+ *
+ * @param string $a Tag name to compare against $b
+ * @param string $b Tag name to compare against $a
+ * @return int The result of the comparison/validation 1, 0 or -1
+ */
+ public static function cloud_sort($a, $b) {
+ $tagsort = self::$cloudsortfield ?: 'name';
+
+ if (is_numeric($a->$tagsort)) {
+ return ($a->$tagsort == $b->$tagsort) ? 0 : ($a->$tagsort > $b->$tagsort) ? 1 : -1;
+ } else if (is_string($a->$tagsort)) {
+ return strcmp($a->$tagsort, $b->$tagsort);
+ } else {
+ return 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tag/classes/collection_form.php b/tag/classes/collection_form.php
new file mode 100644
index 00000000000..04a95b75aeb
--- /dev/null
+++ b/tag/classes/collection_form.php
@@ -0,0 +1,79 @@
+.
+
+/**
+ * Contains class core_tag_collection_form
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/formslib.php');
+
+/**
+ * Form for editing tag collection
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_tag_collection_form extends moodleform {
+
+ /**
+ * Form definition
+ */
+ public function definition() {
+ $data = fullclone($this->_customdata);
+ if (isset($data->id)) {
+ $data->tc = $data->id;
+ $data->action = 'colledit';
+ } else {
+ $data = new stdClass();
+ $data->action = 'colladd';
+ $data->isdefault = false;
+ }
+
+ $mform = $this->_form;
+ $mform->addElement('hidden', 'tc');
+ $mform->setType('tc', PARAM_INT);
+ $mform->addElement('hidden', 'action');
+ $mform->setType('action', PARAM_ALPHA);
+
+ $mform->addElement('text', 'name', get_string('name'));
+ $mform->setType('name', PARAM_NOTAGS);
+ $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+ if (empty($data->isdefault)) {
+ $mform->addRule('name', get_string('required'), 'required', null, 'client');
+ } else {
+ $mform->addElement('static', 'collnameexplained', '', get_string('collnameexplained', 'tag',
+ get_string('defautltagcoll', 'tag')));
+ }
+
+ $mform->addElement('advcheckbox', 'searchable', get_string('searchable', 'tag'));
+ $mform->addHelpButton('searchable', 'searchable', 'tag');
+ $mform->setDefault('searchable', 1);
+ if (!empty($data->isdefault)) {
+ $mform->freeze('searchable');
+ }
+
+ $this->add_action_buttons();
+
+ $this->set_data($data);
+ }
+}
diff --git a/tag/classes/collections_table.php b/tag/classes/collections_table.php
new file mode 100644
index 00000000000..28ae760e0c7
--- /dev/null
+++ b/tag/classes/collections_table.php
@@ -0,0 +1,100 @@
+.
+
+/**
+ * Contains class core_tag_collections_table
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Table with the list of tag collections for "Manage tags" page.
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_tag_collections_table extends html_table {
+
+ /**
+ * Constructor
+ * @param string|moodle_url $pageurl
+ */
+ public function __construct($pageurl) {
+ global $OUTPUT;
+ parent::__construct();
+
+ $this->attributes['class'] = 'generaltable tag-collections-table';
+
+ $this->head = array(
+ get_string('name'),
+ get_string('component', 'tag'),
+ get_string('tagareas', 'tag'),
+ get_string('searchable', 'tag') . $OUTPUT->help_icon('searchable', 'tag'),
+ ''
+ );
+
+ $this->data = array();
+
+ $tagcolls = core_tag_collection::get_collections();
+ $idx = 0;
+ foreach ($tagcolls as $tagcoll) {
+ $actions = '';
+ $name = core_tag_collection::display_name($tagcoll);
+ $url = new moodle_url($pageurl, array('sesskey' => sesskey(), 'tc' => $tagcoll->id));
+ if (!$tagcoll->isdefault) {
+ // Move up.
+ if ($idx > 1) {
+ $url->param('action', 'collmoveup');
+ $actions .= $OUTPUT->action_icon($url, new pix_icon('t/up', get_string('moveup')));
+ }
+ // Move down.
+ if ($idx < count($tagcolls) - 1) {
+ $url->param('action', 'collmovedown');
+ $actions .= $OUTPUT->action_icon($url, new pix_icon('t/down', get_string('movedown')));
+ }
+ }
+ if (empty($tagcoll->component)) {
+ // Edit.
+ $url->param('action', 'colledit');
+ $actions .= $OUTPUT->action_icon($url, new pix_icon('t/edit', get_string('edittagcoll', 'tag', $name)));
+ }
+ if (!$tagcoll->isdefault && empty($tagcoll->component)) {
+ // Delete.
+ $url->param('action', 'colldelete');
+ $actions .= $OUTPUT->action_icon($url, new pix_icon('t/delete', get_string('delete')));
+ }
+ $manageurl = new moodle_url('/tag/manage.php', array('tc' => $tagcoll->id));
+ $component = '';
+ if ($tagcoll->component) {
+ $component = ($tagcoll->component === 'core' || preg_match('/^core_/', $tagcoll->component)) ?
+ get_string('coresystem') : get_string('pluginname', $tagcoll->component);
+ }
+ $this->data[] = array(
+ html_writer::link($manageurl, $name),
+ $component,
+ join(', ', core_tag_collection::get_areas_names($tagcoll->id)),
+ $tagcoll->searchable ? get_string('yes') : '-',
+ $actions);
+ $idx++;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tag/classes/external.php b/tag/classes/external.php
index 2bb564f335b..324ecad963c 100644
--- a/tag/classes/external.php
+++ b/tag/classes/external.php
@@ -66,7 +66,6 @@ class core_tag_external extends external_api {
*/
public static function update_tags($tags) {
global $CFG, $PAGE, $DB;
- require_once($CFG->dirroot.'/tag/lib.php');
// Validate and normalize parameters.
$tags = self::validate_parameters(self::update_tags_parameters(), array('tags' => $tags));
@@ -87,8 +86,6 @@ class core_tag_external extends external_api {
$tag['rawname'] = clean_param($tag['rawname'], PARAM_TAG);
if (empty($tag['rawname'])) {
unset($tag['rawname']);
- } else {
- $tag['name'] = core_text::strtolower($tag['rawname']);
}
}
if (!$canmanage) {
@@ -109,7 +106,7 @@ class core_tag_external extends external_api {
);
continue;
}
- if (!$tagobject = $DB->get_record('tag', array('id' => $tag['id']))) {
+ if (!$tagobject = core_tag_tag::get($tag['id'], '*')) {
$warnings[] = array(
'item' => $tag['id'],
'warningcode' => 'tagnotfound',
@@ -118,7 +115,7 @@ class core_tag_external extends external_api {
continue;
}
// First check if new tag name is allowed.
- if (!empty($tag['name']) && ($existing = $DB->get_record('tag', array('name' => $tag['name']), 'id'))) {
+ if (!empty($tag['rawname']) && ($existing = core_tag_tag::get_by_name($tagobject->tagcollid, $tag['rawname']))) {
if ($existing->id != $tag['id']) {
$warnings[] = array(
'item' => $tag['id'],
@@ -132,23 +129,18 @@ class core_tag_external extends external_api {
$tag['tagtype'] = $tag['official'] ? 'official' : 'default';
unset($tag['official']);
}
- $tag['timemodified'] = time();
- $DB->update_record('tag', $tag);
-
- foreach ($tag as $key => $value) {
- $tagobject->$key = $value;
+ if (isset($tag['flag'])) {
+ if ($tag['flag']) {
+ $tagobject->flag();
+ } else {
+ $tagobject->reset_flag();
+ }
+ unset($tag['flag']);
+ }
+ unset($tag['id']);
+ if (count($tag)) {
+ $tagobject->update($tag);
}
-
- $event = \core\event\tag_updated::create(array(
- 'objectid' => $tagobject->id,
- 'relateduserid' => $tagobject->userid,
- 'context' => context_system::instance(),
- 'other' => array(
- 'name' => $tagobject->name,
- 'rawname' => $tagobject->rawname
- )
- ));
- $event->trigger();
}
return array('warnings' => $warnings);
}
@@ -192,7 +184,6 @@ class core_tag_external extends external_api {
*/
public static function get_tags($tags) {
global $CFG, $PAGE, $DB;
- require_once($CFG->dirroot.'/tag/lib.php');
// Validate and normalize parameters.
$tags = self::validate_parameters(self::get_tags_parameters(), array('tags' => $tags));
@@ -248,6 +239,7 @@ class core_tag_external extends external_api {
'tags' => new external_multiple_structure( new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'tag id'),
+ 'tagcollid' => new external_value(PARAM_INT, 'tag collection id'),
'name' => new external_value(PARAM_TAG, 'name'),
'rawname' => new external_value(PARAM_RAW, 'tag raw name (may contain capital letters)'),
'description' => new external_value(PARAM_RAW, 'tag description'),
@@ -263,4 +255,81 @@ class core_tag_external extends external_api {
)
);
}
+
+ /**
+ * Parameters for function get_tagindex()
+ *
+ * @return external_function_parameters
+ */
+ public static function get_tagindex_parameters() {
+ return new external_function_parameters(
+ array(
+ 'tagindex' => new external_single_structure(array(
+ 'tag' => new external_value(PARAM_TAG, 'tag name'),
+ 'tc' => new external_value(PARAM_INT, 'tag collection id'),
+ 'ta' => new external_value(PARAM_INT, 'tag area id'),
+ 'excl' => new external_value(PARAM_BOOL, 'exlusive mode for this tag area', VALUE_OPTIONAL, 0),
+ 'from' => new external_value(PARAM_INT, 'context id where the link was displayed', VALUE_OPTIONAL, 0),
+ 'ctx' => new external_value(PARAM_INT, 'context id where to search for items', VALUE_OPTIONAL, 0),
+ 'rec' => new external_value(PARAM_INT, 'search in the context recursive', VALUE_OPTIONAL, 1),
+ 'page' => new external_value(PARAM_INT, 'page number (0-based)', VALUE_OPTIONAL, 0),
+ ), 'parameters')
+ )
+ );
+ }
+
+ /**
+ * Get tags by their ids
+ *
+ * @param array $params
+ */
+ public static function get_tagindex($params) {
+ global $PAGE;
+ // Validate and normalize parameters.
+ $tagindex = self::validate_parameters(
+ self::get_tagindex_parameters(), array('tagindex' => $params));
+ $params = $tagindex['tagindex'] + array(
+ 'excl' => 0,
+ 'from' => 0,
+ 'ctx' => 0,
+ 'rec' => 1,
+ 'page' => 0
+ );
+
+ // Login to the course / module if applicable.
+ $context = $params['ctx'] ? context::instance_by_id($params['ctx']) : context_system::instance();
+ require_login(null, false, null, false, true);
+ self::validate_context($context);
+
+ $tag = core_tag_tag::get_by_name($params['tc'], $params['tag'], '*', MUST_EXIST);
+ $tagareas = core_tag_collection::get_areas($params['tc']);
+ $tagindex = $tag->get_tag_index($tagareas[$params['ta']], $params['excl'], $params['from'],
+ $params['ctx'], $params['rec'], $params['page']);
+ $renderer = $PAGE->get_renderer('core');
+ return $tagindex->export_for_template($renderer);
+ }
+
+ /**
+ * Return structure for get_tag()
+ *
+ * @return external_description
+ */
+ public static function get_tagindex_returns() {
+ return new external_single_structure(
+ array(
+ 'tagid' => new external_value(PARAM_INT, 'tag id'),
+ 'ta' => new external_value(PARAM_INT, 'tag area id'),
+ 'component' => new external_value(PARAM_COMPONENT, 'component'),
+ 'itemtype' => new external_value(PARAM_NOTAGS, 'itemtype'),
+ 'nextpageurl' => new external_value(PARAM_URL, 'URL for the next page', VALUE_OPTIONAL),
+ 'prevpageurl' => new external_value(PARAM_URL, 'URL for the next page', VALUE_OPTIONAL),
+ 'exclusiveurl' => new external_value(PARAM_URL, 'URL for exclusive link', VALUE_OPTIONAL),
+ 'exclusivetext' => new external_value(PARAM_TEXT, 'text for exclusive link', VALUE_OPTIONAL),
+ 'title' => new external_value(PARAM_RAW, 'title'),
+ 'content' => new external_value(PARAM_RAW, 'title'),
+ 'hascontent' => new external_value(PARAM_INT, 'whether the content is present'),
+ 'anchor' => new external_value(PARAM_TEXT, 'name of anchor', VALUE_OPTIONAL),
+ ), 'tag index'
+ );
+ }
}
diff --git a/tag/classes/manage_table.php b/tag/classes/manage_table.php
index 61d3b19375d..48af9026876 100644
--- a/tag/classes/manage_table.php
+++ b/tag/classes/manage_table.php
@@ -38,16 +38,24 @@ class core_tag_manage_table extends table_sql {
/** @var int stores the total number of found tags */
public $totalcount = null;
+ /** @var int */
+ protected $tagcollid;
+
/**
* Constructor
+ *
+ * @param int $tagcollid
*/
- public function __construct() {
+ public function __construct($tagcollid) {
global $USER, $CFG, $PAGE;
parent::__construct('tag-management-list-'.$USER->id);
+ $this->tagcollid = $tagcollid;
+
$perpage = optional_param('perpage', DEFAULT_PAGE_SIZE, PARAM_INT);
$page = optional_param('page', 0, PARAM_INT);
- $baseurl = new moodle_url('/tag/manage.php', array('perpage' => $perpage, 'page' => $page));
+ $baseurl = new moodle_url('/tag/manage.php', array('tc' => $tagcollid,
+ 'perpage' => $perpage, 'page' => $page));
$tablecolumns = array('select', 'name', 'fullname', 'count', 'flag', 'timemodified', 'tagtype', 'controls');
$tableheaders = array(get_string('select', 'tag'),
@@ -80,8 +88,10 @@ class core_tag_manage_table extends table_sql {
$this->set_attribute('id', 'tag-management-list');
$this->set_attribute('class', 'admintable generaltable tag-management-table');
- $totalcount = "SELECT COUNT(id) FROM {tag}";
- $params = array();
+ $totalcount = "SELECT COUNT(id)
+ FROM {tag}
+ WHERE tagcollid = :tagcollid";
+ $params = array('tagcollid' => $this->tagcollid);
$this->set_count_sql($totalcount, $params);
@@ -135,13 +145,13 @@ class core_tag_manage_table extends table_sql {
$sql = "
SELECT tg.id, tg.name, tg.rawname, tg.tagtype, tg.flag, tg.timemodified,
u.id AS owner, $allusernames,
- COUNT(ti.id) AS count
+ COUNT(ti.id) AS count, tg.tagcollid
FROM {tag} tg
LEFT JOIN {tag_instance} ti ON ti.tagid = tg.id
LEFT JOIN {user} u ON u.id = tg.userid
- WHERE 1 = 1 $where
+ WHERE tagcollid = :tagcollid $where
GROUP BY tg.id, tg.name, tg.rawname, tg.tagtype, tg.flag, tg.timemodified,
- u.id, $allusernames
+ u.id, $allusernames, tg.tagcollid
ORDER BY $sort";
if (!$this->is_downloading()) {
diff --git a/tag/classes/output/tag.php b/tag/classes/output/tag.php
index 74aa47d6c8d..dd8559f5723 100644
--- a/tag/classes/output/tag.php
+++ b/tag/classes/output/tag.php
@@ -39,15 +39,19 @@ use moodle_url;
*/
class tag implements renderable, templatable {
- /** @var stdClass */
+ /** @var \core_tag_tag|stdClass */
protected $record;
/**
* Constructor
*
- * @param stdClass $tag
+ * @param \core_tag_tag|stdClass $tag
*/
public function __construct($tag) {
+ if ($tag instanceof \core_tag_tag) {
+ $this->record = $tag;
+ return;
+ }
$tag = (array)$tag +
array(
'name' => '',
@@ -56,7 +60,8 @@ class tag implements renderable, templatable {
'descriptionformat' => FORMAT_HTML,
'flag' => 0,
'tagtype' => 'default',
- 'id' => 0
+ 'id' => 0,
+ 'tagcollid' => 0,
);
$this->record = (object)$tag;
}
@@ -73,6 +78,7 @@ class tag implements renderable, templatable {
$r = new stdClass();
$r->id = (int)$this->record->id;
+ $r->tagcollid = clean_param($this->record->tagcollid, PARAM_INT);
$r->rawname = clean_param($this->record->rawname, PARAM_TAG);
$r->name = clean_param($this->record->name, PARAM_TAG);
$format = clean_param($this->record->descriptionformat, PARAM_INT);
@@ -85,7 +91,7 @@ class tag implements renderable, templatable {
$r->official = ($this->record->tagtype === 'official') ? 1 : 0;
}
- $url = new moodle_url('/tag/index.php', array('id' => $this->record->id));
+ $url = \core_tag_tag::make_url($r->tagcollid, $r->rawname);
$r->viewurl = $url->out(false);
$manageurl = new moodle_url('/tag/manage.php', array('sesskey' => sesskey(),
diff --git a/tag/classes/output/tagcloud.php b/tag/classes/output/tagcloud.php
new file mode 100644
index 00000000000..85d61ce7350
--- /dev/null
+++ b/tag/classes/output/tagcloud.php
@@ -0,0 +1,121 @@
+.
+
+/**
+ * Contains class core_tag\output\tagindex
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\output;
+
+use renderable;
+use templatable;
+use renderer_base;
+use stdClass;
+use moodle_url;
+use core_tag_tag;
+
+/**
+ * Class to display a tag cloud - set of tags where each has a weight.
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tagcloud implements templatable {
+
+ /** @var array */
+ protected $tagset;
+
+ /** @var int */
+ protected $totalcount;
+
+ /**
+ * Constructor
+ *
+ * @param array $tagset array of core_tag or stdClass elements, each of them must have attributes:
+ * name, rawname, tagcollid
+ * preferrably also have attributes:
+ * tagtype, count, flag
+ * @param int $totalcount total count of tags (for example to indicate that there are more tags than the count of tagset)
+ * leave 0 if count of tagset is the actual count of tags
+ * @param int $fromctx context id where this tag cloud is displayed
+ * @param int $ctx context id for tag view link
+ * @param int $rec recursive argument for tag view link
+ */
+ public function __construct($tagset, $totalcount = 0, $fromctx = 0, $ctx = 0, $rec = 1) {
+ $canmanagetags = has_capability('moodle/tag:manage', \context_system::instance());
+
+ $maxcount = 1;
+ foreach ($tagset as $tag) {
+ if (isset($tag->count) && $tag->count > $maxcount) {
+ $maxcount = $tag->count;
+ }
+ }
+
+ $this->tagset = array();
+ foreach ($tagset as $idx => $tag) {
+ $this->tagset[$idx] = new stdClass();
+
+ $this->tagset[$idx]->name = core_tag_tag::make_display_name($tag, false);
+
+ if ($canmanagetags && !empty($tag->flag)) {
+ $this->tagset[$idx]->flag = 1;
+ }
+
+ $viewurl = core_tag_tag::make_url($tag->tagcollid, $tag->rawname, 0, $fromctx, $ctx, $rec);
+ $this->tagset[$idx]->viewurl = $viewurl->out(false);
+
+ if (!empty($tag->tagtype)) {
+ $this->tagset[$idx]->tagtype = $tag->tagtype;
+ }
+
+ if (!empty($tag->count)) {
+ $this->tagset[$idx]->count = $tag->count;
+ $this->tagset[$idx]->size = (int)($tag->count / $maxcount * 20);
+ }
+ }
+
+ $this->totalcount = $totalcount ? $totalcount : count($this->tagset);
+ }
+
+ /**
+ * Returns number of tags in the cloud
+ * @return int
+ */
+ public function get_count() {
+ return count($this->tagset);
+ }
+
+ /**
+ * Export this data so it can be used as the context for a mustache template.
+ *
+ * @param renderer_base $output
+ * @return stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+ $cnt = count($this->tagset);
+ return (object)array(
+ 'tags' => $this->tagset,
+ 'tagscount' => $cnt,
+ 'totalcount' => $this->totalcount,
+ 'overflow' => ($this->totalcount > $cnt) ? 1 : 0,
+ );
+ }
+}
diff --git a/tag/classes/output/tagfeed.php b/tag/classes/output/tagfeed.php
new file mode 100644
index 00000000000..4c79e405c48
--- /dev/null
+++ b/tag/classes/output/tagfeed.php
@@ -0,0 +1,82 @@
+.
+
+/**
+ * Contains class core_tag\output\tagfeed
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\output;
+
+use templatable;
+use renderer_base;
+use stdClass;
+
+/**
+ * Class to display feed of tagged items
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tagfeed implements templatable {
+
+ /** @var array */
+ protected $items;
+
+ /**
+ * Constructor
+ *
+ * Usually the most convenient way is to call constructor without arguments and
+ * add items later using add() method.
+ *
+ * @param array $items
+ */
+ public function __construct($items = array()) {
+ $this->items = array();
+ if ($items) {
+ foreach ($items as $item) {
+ $item = (array)$item + array('img' => '', 'heading' => '', 'details' => '');
+ $this->add($item['img'], $item['heading'], $item['details']);
+ }
+ }
+ }
+
+ /**
+ * Adds one item to the tagfeed
+ *
+ * @param string $img HTML code representing image (or image wrapped in a link), note that
+ * core_tag/tagfeed template expects image to be 35x35 px
+ * @param string $heading HTML for item heading
+ * @param string $details HTML for item details (keep short)
+ */
+ public function add($img, $heading, $details = '') {
+ $this->items[] = array('img' => $img, 'heading' => $heading, 'details' => $details);
+ }
+
+ /**
+ * Export this data so it can be used as the context for a mustache template.
+ *
+ * @param renderer_base $output
+ * @return stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+ return array('items' => $this->items);
+ }
+}
diff --git a/tag/classes/output/tagindex.php b/tag/classes/output/tagindex.php
new file mode 100644
index 00000000000..f888d6f2008
--- /dev/null
+++ b/tag/classes/output/tagindex.php
@@ -0,0 +1,161 @@
+.
+
+/**
+ * Contains class core_tag\output\tagindex
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\output;
+
+use renderable;
+use templatable;
+use renderer_base;
+use stdClass;
+use moodle_url;
+use core_tag_tag;
+
+/**
+ * Class to display items tagged with a specific tag
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tagindex implements templatable {
+
+ /** @var core_tag_tag|stdClass */
+ protected $tag;
+
+ /** @var stdClass */
+ protected $tagarea;
+
+ /** @var stdClass */
+ protected $record;
+
+ /**
+ * Constructor
+ *
+ * @param core_tag_tag|stdClass $tag
+ * @param string $component
+ * @param string $itemtype
+ * @param string $content
+ * @param bool $exclusivemode
+ * @param int $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where we need to search for items
+ * @param int $rec search items in sub contexts as well
+ * @param int $page
+ * @param bool $totalpages
+ */
+ public function __construct($tag, $component, $itemtype, $content,
+ $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0, $totalpages = 1) {
+ $this->record = new stdClass();
+ $this->tag = $tag;
+
+ $tagareas = \core_tag_area::get_areas();
+ if (!isset($tagareas[$itemtype][$component])) {
+ throw new \coding_exception('Tag area for component '.$component.' and itemtype '.$itemtype.' is not defined');
+ }
+ $this->tagarea = $tagareas[$itemtype][$component];
+ $this->record->tagid = $tag->id;
+ $this->record->ta = $this->tagarea->id;
+ $this->record->itemtype = $itemtype;
+ $this->record->component = $component;
+
+ $a = (object)array(
+ 'tagarea' => \core_tag_area::display_name($component, $itemtype),
+ 'tag' => \core_tag_tag::make_display_name($tag)
+ );
+ if ($exclusivemode) {
+ $this->record->title = get_string('itemstaggedwith', 'tag', $a);
+ } else {
+ $this->record->title = (string)$a->tagarea;
+ }
+ $this->record->content = $content;
+
+ $this->record->nextpageurl = null;
+ $this->record->prevpageurl = null;
+ $this->record->exclusiveurl = null;
+
+ $url = core_tag_tag::make_url($tag->tagcollid, $tag->rawname, $exclusivemode, $fromctx, $ctx, $rec);
+ $urlparams = array('ta' => $this->tagarea->id);
+ if ($totalpages > $page + 1) {
+ $this->record->nextpageurl = new moodle_url($url, $urlparams + array('page' => $page + 1));
+ }
+ if ($page > 0) {
+ $this->record->prevpageurl = new moodle_url($url, $urlparams + array('page' => $page - 1));
+ }
+ if (!$exclusivemode && ($totalpages > 1 || $page)) {
+ $this->record->exclusiveurl = new moodle_url($url, $urlparams + array('excl' => 1));
+ }
+ $this->record->exclusivetext = get_string('exclusivemode', 'tag', $a);
+ $this->record->hascontent = ($totalpages > 1 || $page || $content);
+ $this->record->anchor = $component . '_' . $itemtype;
+ }
+
+ /**
+ * Magic setter
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function __set($name, $value) {
+ $this->record->$name = $value;
+ }
+
+ /**
+ * Magic getter
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name) {
+ return $this->record->$name;
+ }
+
+ /**
+ * Magic isset
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name) {
+ return isset($this->record->$name);
+ }
+
+ /**
+ * Export this data so it can be used as the context for a mustache template.
+ *
+ * @param renderer_base $output
+ * @return stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+ if ($this->record->nextpageurl && $this->record->nextpageurl instanceof moodle_url) {
+ $this->record->nextpageurl = $this->record->nextpageurl->out(false);
+ }
+ if ($this->record->prevpageurl && $this->record->prevpageurl instanceof moodle_url) {
+ $this->record->prevpageurl = $this->record->prevpageurl->out(false);
+ }
+ if ($this->record->exclusiveurl && $this->record->exclusiveurl instanceof moodle_url) {
+ $this->record->exclusiveurl = $this->record->exclusiveurl->out(false);
+ }
+ return $this->record;
+ }
+}
diff --git a/tag/classes/output/taglist.php b/tag/classes/output/taglist.php
new file mode 100644
index 00000000000..d0ad198591d
--- /dev/null
+++ b/tag/classes/output/taglist.php
@@ -0,0 +1,114 @@
+.
+
+/**
+ * Contains class core_tag\output\taglist
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_tag\output;
+
+use templatable;
+use renderer_base;
+use stdClass;
+use core_tag_tag;
+use context;
+
+/**
+ * Class to preapare a list of tags for display, usually the list of tags some entry is tagged with.
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class taglist implements templatable {
+
+ /** @var array */
+ protected $tags;
+
+ /** @var string */
+ protected $label;
+
+ /** @var string */
+ protected $classes;
+
+ /** @var int */
+ protected $limit;
+
+ /**
+ * Constructor
+ *
+ * @param array $tags list of instances of \core_tag_tag or \stdClass
+ * @param string $label label to display in front, by default 'Tags' (get_string('tags')), set to null
+ * to use default, set to '' (empty string) to omit the label completely
+ * @param string $classes additional classes for the enclosing div element
+ * @param int $limit limit the number of tags to display, if size of $tags is more than this limit the "more" link
+ * will be appended to the end, JS will toggle the rest of the tags
+ * @param context $pagecontext specify if needed to overwrite the current page context for the view tag link
+ */
+ public function __construct($tags, $label = null, $classes = '', $limit = 10, $pagecontext = null) {
+ global $PAGE;
+ $canmanagetags = has_capability('moodle/tag:manage', \context_system::instance());
+
+ $this->label = ($label === null) ? get_string('tags') : $label;
+ $this->classes = $classes;
+ $fromctx = $pagecontext ? $pagecontext->id :
+ (($PAGE->context->contextlevel == CONTEXT_SYSTEM) ? 0 : $PAGE->context->id);
+
+ $this->tags = array();
+ foreach ($tags as $idx => $tag) {
+ $this->tags[$idx] = new stdClass();
+
+ $this->tags[$idx]->name = core_tag_tag::make_display_name($tag, false);
+
+ if ($canmanagetags && !empty($tag->flag)) {
+ $this->tags[$idx]->flag = 1;
+ }
+
+ $viewurl = core_tag_tag::make_url($tag->tagcollid, $tag->rawname, 0, $fromctx);
+ $this->tags[$idx]->viewurl = $viewurl->out(false);
+
+ if (!empty($tag->tagtype)) {
+ $this->tags[$idx]->tagtype = $tag->tagtype;
+ }
+
+ if ($limit && count($this->tags) > $limit) {
+ $this->tags[$idx]->overlimit = 1;
+ }
+ }
+ $this->limit = $limit;
+ }
+
+ /**
+ * Export this data so it can be used as the context for a mustache template.
+ *
+ * @param renderer_base $output
+ * @return stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+ $cnt = count($this->tags);
+ return (object)array(
+ 'tags' => array_values($this->tags),
+ 'label' => $this->label,
+ 'tagscount' => $cnt,
+ 'overflow' => ($this->limit && $cnt > $this->limit) ? 1 : 0,
+ 'classes' => $this->classes,
+ );
+ }
+}
diff --git a/tag/classes/renderer.php b/tag/classes/renderer.php
new file mode 100644
index 00000000000..3949c5e81e3
--- /dev/null
+++ b/tag/classes/renderer.php
@@ -0,0 +1,180 @@
+.
+
+/**
+ * Contains class core_tag_renderer
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class core_tag_renderer
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_tag_renderer extends plugin_renderer_base {
+
+ /**
+ * Renders the tag search page
+ *
+ * @param string $query
+ * @param int $tagcollid
+ * @return string
+ */
+ public function tag_search_page($query = '', $tagcollid = 0) {
+ $rv = $this->output->heading(get_string('searchtags', 'tag'), 2);
+
+ $searchbox = $this->search_form($query, $tagcollid);
+ $rv .= html_writer::div($searchbox, '', array('id' => 'tag-search-box'));
+
+ $tagcloud = core_tag_collection::get_tag_cloud($tagcollid, '', 150, 'name', $query);
+ $searchresults = '';
+ if ($tagcloud->get_count()) {
+ $searchresults = $this->output->render_from_template('core_tag/tagcloud',
+ $tagcloud->export_for_template($this->output));
+ $rv .= html_writer::div($searchresults, '', array('id' => 'tag-search-results'));
+ } else if (strval($query) !== '') {
+ $rv .= '' . get_string('notagsfound', 'tag', s($query)) . '
';
+ }
+
+ return $rv;
+ }
+
+ /**
+ * Renders the tag index page
+ *
+ * @param core_tag_tag $tag
+ * @param \core_tag\output\tagindex[] $entities
+ * @param int $tagareaid
+ * @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 $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param int $page 0-based number of page being displayed
+ * @return string
+ */
+ public function tag_index_page($tag, $entities, $tagareaid, $exclusivemode, $fromctx, $ctx, $rec, $page) {
+ global $CFG, $OUTPUT;
+ $this->page->requires->js_call_amd('core/tag', 'init_tagindex_page');
+
+ $tagname = $tag->get_display_name();
+ $systemcontext = context_system::instance();
+
+ if ($tag->flag > 0 && has_capability('moodle/tag:manage', $systemcontext)) {
+ $tagname = '' . $tagname . '';
+ }
+
+ $rv = '';
+ $rv .= $this->output->heading($tagname, 2);
+
+ $rv .= $this->tag_links($tag);
+
+ if ($desciption = $tag->get_formatted_description()) {
+ $rv .= $this->output->box($desciption, 'generalbox tag-description');
+ }
+
+ $relatedtagslimit = 10;
+ $relatedtags = $tag->get_related_tags();
+ $taglist = new \core_tag\output\taglist($relatedtags, get_string('relatedtags', 'tag'),
+ 'tag-relatedtags', $relatedtagslimit);
+ $rv .= $OUTPUT->render_from_template('core_tag/taglist', $taglist->export_for_template($OUTPUT));
+
+ // Display quick menu of the item types (if more than one item type found).
+ $entitylinks = array();
+ foreach ($entities as $entity) {
+ if (!empty($entity->hascontent)) {
+ $entitylinks[] = '' .
+ core_tag_area::display_name($entity->component, $entity->itemtype) . '';
+ }
+ }
+
+ if (count($entitylinks) > 1) {
+ $rv .= '' . join('', $entitylinks) . '
';
+ } else if (!$entitylinks) {
+ $rv .= '' . get_string('noresultsfor', 'tag', $tagname) . '
';
+ }
+
+ // Display entities tagged with the tag.
+ $content = '';
+ foreach ($entities as $entity) {
+ if (!empty($entity->hascontent)) {
+ $content .= $this->output->render_from_template('core_tag/index', $entity->export_for_template($this->output));
+ }
+ }
+
+ if ($exclusivemode) {
+ $rv .= $content;
+ } else if ($content) {
+ $rv .= html_writer::div($content, 'tag-index-items');
+ }
+
+ // Display back link if we are browsing one tag area.
+ if ($tagareaid) {
+ $url = $tag->get_view_url(0, $fromctx, $ctx, $rec);
+ $rv .= '' .
+ html_writer::link($url, get_string('backtoallitems', 'tag', $tag->get_display_name())) .
+ '
';
+ }
+
+ return $rv;
+ }
+
+ /**
+ * Prints a box that contains the management links of a tag
+ *
+ * @param core_tag_tag $tag
+ * @return string
+ */
+ protected function tag_links($tag) {
+ if ($links = $tag->get_links()) {
+ $content = '- ' . implode('
- ', $links) . '
';
+ return html_writer::div($content, 'tag-management-box');
+ }
+ return '';
+ }
+
+ /**
+ * Prints the tag search box
+ *
+ * @param string $query last search string
+ * @param int $tagcollid last selected tag collection id
+ * @return string
+ */
+ protected function search_form($query = '', $tagcollid = 0) {
+ $searchurl = new moodle_url('/tag/search.php');
+ $output = '';
+
+ return $output;
+ }
+
+}
diff --git a/tag/classes/tag.php b/tag/classes/tag.php
new file mode 100644
index 00000000000..a7a62c7112f
--- /dev/null
+++ b/tag/classes/tag.php
@@ -0,0 +1,1414 @@
+.
+
+/**
+ * Contains class core_tag_tag
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Represents one tag and also contains lots of useful tag-related methods as static functions.
+ *
+ * Tags can be added to any database records.
+ * $itemtype refers to the DB table name
+ * $itemid refers to id field in this DB table
+ * $component is the component that is responsible for the tag instance
+ * $context is the affected context
+ *
+ * BASIC INSTRUCTIONS :
+ * - to "tag a blog post" (for example):
+ * core_tag_tag::set_item_tags('post', 'core', $blogpost->id, $context, $arrayoftags);
+ *
+ * - to "remove all the tags on a blog post":
+ * core_tag_tag::remove_all_item_tags('post', 'core', $blogpost->id);
+ *
+ * set_item_tags() will create tags that do not exist yet.
+ *
+ * @property-read int $id
+ * @property-read string $name
+ * @property-read string $rawname
+ * @property-read int $tagcollid
+ * @property-read int $userid
+ * @property-read string $tagtype "official" or "default"
+ * @property-read string $description
+ * @property-read int $descriptionformat
+ * @property-read int $flag 0 if not flagged or positive integer if flagged
+ * @property-read int $timemodified
+ *
+ * @package core_tag
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_tag_tag {
+
+ /** @var stdClass data about the tag */
+ protected $record = null;
+
+ /**
+ * Constructor. Use functions get(), get_by_name(), etc.
+ *
+ * @param stdClass $record
+ */
+ protected function __construct($record) {
+ if (empty($record->id)) {
+ throw new coding_exeption("Record must contain at least field 'id'");
+ }
+ $this->record = $record;
+ }
+
+ /**
+ * Magic getter
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name) {
+ return $this->record->$name;
+ }
+
+ /**
+ * Magic isset method
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function __isset($name) {
+ return isset($this->record->$name);
+ }
+
+ /**
+ * Converts to object
+ *
+ * @return stdClass
+ */
+ public function to_object() {
+ return fullclone($this->record);
+ }
+
+ /**
+ * Returns tag name ready to be displayed
+ *
+ * @param bool $ashtml (default true) if true will return htmlspecialchars encoded string
+ * @return string
+ */
+ public function get_display_name($ashtml = true) {
+ return static::make_display_name($this->record, $ashtml);
+ }
+
+ /**
+ * Prepares tag name ready to be displayed
+ *
+ * @param stdClass|core_tag_tag $tag record from db table tag, must contain properties name and rawname
+ * @param bool $ashtml (default true) if true will return htmlspecialchars encoded string
+ * @return string
+ */
+ public static function make_display_name($tag, $ashtml = true) {
+ global $CFG;
+
+ if (empty($CFG->keeptagnamecase)) {
+ // This is the normalized tag name.
+ $tagname = core_text::strtotitle($tag->name);
+ } else {
+ // Original casing of the tag name.
+ $tagname = $tag->rawname;
+ }
+
+ // Clean up a bit just in case the rules change again.
+ $tagname = clean_param($tagname, PARAM_TAG);
+
+ return $ashtml ? htmlspecialchars($tagname) : $tagname;
+ }
+
+ /**
+ * Adds one or more tag in the database. This function should not be called directly : you should
+ * use tag_set.
+ *
+ * @param int $tagcollid
+ * @param string|array $tags one tag, or an array of tags, to be created
+ * @param bool $isofficial type of tag to be created. An official tag is kept even if there are no records tagged with it.
+ * @return array tag objects indexed by their lowercase normalized names. Any boolean false in the array
+ * indicates an error while adding the tag.
+ */
+ protected static function add($tagcollid, $tags, $isofficial = false) {
+ global $USER, $DB;
+
+ $tagobject = new stdClass();
+ $tagobject->tagtype = $isofficial ? 'official' : 'default';
+ $tagobject->userid = $USER->id;
+ $tagobject->timemodified = time();
+ $tagobject->tagcollid = $tagcollid;
+
+ $rv = array();
+ foreach ($tags as $veryrawname) {
+ $rawname = clean_param($veryrawname, PARAM_TAG);
+ if (!$rawname) {
+ $rv[$rawname] = false;
+ } else {
+ $obj = (object)(array)$tagobject;
+ $obj->rawname = $rawname;
+ $obj->name = core_text::strtolower($rawname);
+ $obj->id = $DB->insert_record('tag', $obj);
+ $rv[$obj->name] = new static($obj);
+
+ \core\event\tag_created::create_from_tag($rv[$obj->name])->trigger();
+ }
+ }
+
+ return $rv;
+ }
+
+ /**
+ * Simple function to just return a single tag object by its id
+ *
+ * @param int $id
+ * @param string $returnfields which fields do we want returned from table {tag}.
+ * Default value is 'id,name,rawname,tagcollid',
+ * specify '*' to include all fields.
+ * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
+ * IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
+ * MUST_EXIST means throw exception if no record or multiple records found
+ * @return core_tag_tag|false tag object
+ */
+ public static function get($id, $returnfields = 'id, name, rawname, tagcollid', $strictness = IGNORE_MISSING) {
+ global $DB;
+ $record = $DB->get_record('tag', array('id' => $id), $returnfields, $strictness);
+ if ($record) {
+ return new static($record);
+ }
+ return false;
+ }
+
+ /**
+ * Simple function to just return a single tag object by tagcollid and name
+ *
+ * @param int $tagcollid tag collection to use,
+ * if 0 is given we will try to guess the tag collection and return the first match
+ * @param string $name tag name
+ * @param string $returnfields which fields do we want returned. This is a comma separated string
+ * containing any combination of 'id', 'name', 'rawname', 'tagcollid' or '*' to include all fields.
+ * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
+ * IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended);
+ * MUST_EXIST means throw exception if no record or multiple records found
+ * @return core_tag_tag|false tag object
+ */
+ public static function get_by_name($tagcollid, $name, $returnfields='id, name, rawname, tagcollid',
+ $strictness = IGNORE_MISSING) {
+ global $DB;
+ if ($tagcollid == 0) {
+ $tags = static::guess_by_name($name, $returnfields);
+ if ($tags) {
+ $tag = reset($tags);
+ return $tag;
+ } else if ($strictness == MUST_EXIST) {
+ throw new dml_missing_record_exception('tag', 'name=?', array($name));
+ }
+ return false;
+ }
+ $name = core_text::strtolower($name); // To cope with input that might just be wrong case.
+ $params = array('name' => $name, 'tagcollid' => $tagcollid);
+ $record = $DB->get_record('tag', $params, $returnfields, $strictness);
+ if ($record) {
+ return new static($record);
+ }
+ return false;
+ }
+
+ /**
+ * Looking in all tag collections for the tag with the given name
+ *
+ * @param string $name tag name
+ * @param string $returnfields
+ * @return array array of core_tag_tag instances
+ */
+ public static function guess_by_name($name, $returnfields='id, name, rawname, tagcollid') {
+ global $DB;
+ if (empty($name)) {
+ return array();
+ }
+ $tagcolls = core_tag_collection::get_collections();
+ list($sql, $params) = $DB->get_in_or_equal(array_keys($tagcolls), SQL_PARAMS_NAMED);
+ $params['name'] = core_text::strtolower($name);
+ $tags = $DB->get_records_select('tag', 'name = :name AND tagcollid ' . $sql, $params, '', $returnfields);
+ if (count($tags) > 1) {
+ // Sort in the same order as tag collections.
+ uasort($tags, create_function('$a,$b', '$tagcolls = core_tag_collection::get_collections(); ' .
+ 'return $tagcolls[$a->tagcollid]->sortorder < $tagcolls[$b->tagcollid]->sortorder ? -1 : 1;'));
+ }
+ $rv = array();
+ foreach ($tags as $id => $tag) {
+ $rv[$id] = new static($tag);
+ }
+ return $rv;
+ }
+
+ /**
+ * Returns the list of tag objects by tag collection id and the list of tag names
+ *
+ * @param int $tagcollid
+ * @param array $tags array of tags to look for
+ * @param string $returnfields list of DB fields to return, must contain 'id', 'name' and 'rawname'
+ * @return array tag-indexed array of objects. No value for a key means the tag wasn't found.
+ */
+ public static function get_by_name_bulk($tagcollid, $tags, $returnfields = 'id, name, rawname, tagcollid') {
+ global $DB;
+
+ if (empty($tags)) {
+ return array();
+ }
+
+ $cleantags = self::normalize(self::normalize($tags, false)); // Format: rawname => normalised name.
+
+ list($namesql, $params) = $DB->get_in_or_equal(array_values($cleantags));
+ array_unshift($params, $tagcollid);
+
+ $recordset = $DB->get_recordset_sql("SELECT $returnfields FROM {tag} WHERE tagcollid = ? AND name $namesql", $params);
+
+ $result = array_fill_keys($cleantags, null);
+ foreach ($recordset as $record) {
+ $result[$record->name] = new static($record);
+ }
+ $recordset->close();
+ return $result;
+ }
+
+
+ /**
+ * Function that normalizes a list of tag names.
+ *
+ * @param array $rawtags array of tags
+ * @param bool $tolowercase convert to lower case?
+ * @return array lowercased normalized tags, indexed by the normalized tag, in the same order as the original array.
+ * (Eg: 'Banana' => 'banana').
+ */
+ public static function normalize($rawtags, $tolowercase = true) {
+ $result = array();
+ foreach ($rawtags as $rawtag) {
+ $rawtag = trim($rawtag);
+ if (strval($rawtag) !== '') {
+ $clean = clean_param($rawtag, PARAM_TAG);
+ if ($tolowercase) {
+ $result[$rawtag] = core_text::strtolower($clean);
+ } else {
+ $result[$rawtag] = $clean;
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Retrieves tags and/or creates them if do not exist yet
+ *
+ * @param int $tagcollid
+ * @param array $tags array of raw tag names, do not have to be normalised
+ * @param bool $createasofficial
+ * @return core_tag_tag[] array of tag objects indexed with lowercase normalised tag name
+ */
+ public static function create_if_missing($tagcollid, $tags, $createasofficial = false) {
+ $cleantags = self::normalize(array_filter(self::normalize($tags, false))); // Array rawname => normalised name .
+
+ $result = static::get_by_name_bulk($tagcollid, $tags, '*');
+ $existing = array_filter($result);
+ $missing = array_diff_key(array_flip($cleantags), $existing); // Array normalised name => rawname.
+ if ($missing) {
+ $newtags = static::add($tagcollid, array_values($missing), $createasofficial);
+ foreach ($newtags as $tag) {
+ $result[$tag->name] = $tag;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Creates a URL to view a tag
+ *
+ * @param int $tagcollid
+ * @param string $name
+ * @param int $exclusivemode
+ * @param int $fromctx context id where this tag cloud is displayed
+ * @param int $ctx context id for tag view link
+ * @param int $rec recursive argument for tag view link
+ * @return \moodle_url
+ */
+ public static function make_url($tagcollid, $name, $exclusivemode = 0, $fromctx = 0, $ctx = 0, $rec = 1) {
+ $coll = core_tag_collection::get_by_id($tagcollid);
+ if (!empty($coll->customurl)) {
+ $url = '/' . ltrim(trim($coll->customurl), '/');
+ } else {
+ $url = '/tag/index.php';
+ }
+ $params = array('tc' => $tagcollid, 'tag' => $name);
+ if ($exclusivemode) {
+ $params['excl'] = 1;
+ }
+ if ($fromctx) {
+ $params['from'] = $fromctx;
+ }
+ if ($ctx) {
+ $params['ctx'] = $ctx;
+ }
+ if (!$rec) {
+ $params['rec'] = 0;
+ }
+ return new moodle_url($url, $params);
+ }
+
+ /**
+ * Returns URL to view the tag
+ *
+ * @param int $exclusivemode
+ * @param int $fromctx context id where this tag cloud is displayed
+ * @param int $ctx context id for tag view link
+ * @param int $rec recursive argument for tag view link
+ * @return \moodle_url
+ */
+ public function get_view_url($exclusivemode = 0, $fromctx = 0, $ctx = 0, $rec = 1) {
+ return static::make_url($this->record->tagcollid, $this->record->rawname,
+ $exclusivemode, $fromctx, $ctx, $rec);
+ }
+
+ /**
+ * Validates that the required fields were retrieved and retrieves them if missing
+ *
+ * @param array $list array of the fields that need to be validated
+ * @param string $caller name of the function that requested it, for the debugging message
+ */
+ protected function ensure_fields_exist($list, $caller) {
+ global $DB;
+ $missing = array_diff($list, array_keys((array)$this->record));
+ if ($missing) {
+ debugging('core_tag_tag::' . $caller . '() must be called on fully retrieved tag object. Missing fields: '.
+ join(', ', $missing), DEBUG_DEVELOPER);
+ $this->record = $DB->get_record('tag', array('id' => $this->record->id), '*', MUST_EXIST);
+ }
+ }
+
+ /**
+ * Deletes the tag instance given the record from tag_instance DB table
+ *
+ * @param stdClass $taginstance
+ * @param bool $fullobject whether $taginstance contains all fields from DB table tag_instance
+ * (in this case it is safe to add a record snapshot to the event)
+ * @return bool
+ */
+ protected function delete_instance_as_record($taginstance, $fullobject = false) {
+ global $DB;
+
+ $this->ensure_fields_exist(array('name', 'rawname', 'tagtype'), 'delete_instance_as_record');
+
+ $DB->delete_records('tag_instance', array('id' => $taginstance->id));
+
+ // We can not fire an event with 'null' as the contextid.
+ if (is_null($taginstance->contextid)) {
+ $taginstance->contextid = context_system::instance()->id;
+ }
+
+ // Trigger tag removed event.
+ $taginstance->tagid = $this->id;
+ \core\event\tag_removed::create_from_tag_instance($taginstance, $this->name, $this->rawname, $fullobject)->trigger();
+
+ // If there are no other instances of the tag then consider deleting the tag as well.
+ if ($this->tagtype === 'default') {
+ if (!$DB->record_exists('tag_instance', array('tagid' => $this->id))) {
+ self::delete_tags($this->id);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete one instance of a tag. If the last instance was deleted, it will also delete the tag, unless its type is 'official'.
+ *
+ * @param string $component component responsible for tagging. For BC it can be empty but in this case the
+ * query will be slow because DB index will not be used.
+ * @param string $itemtype the type of the record for which to remove the instance
+ * @param int $itemid the id of the record for which to remove the instance
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging (such as core/course)
+ */
+ protected function delete_instance($component, $itemtype, $itemid, $tiuserid = 0) {
+ global $DB;
+ $params = array('tagid' => $this->id,
+ 'itemtype' => $itemtype, 'itemid' => $itemid);
+ if ($tiuserid) {
+ $params['tiuserid'] = $tiuserid;
+ }
+ if ($component) {
+ $params['component'] = $component;
+ }
+
+ $taginstance = $DB->get_record('tag_instance', $params);
+ if (!$taginstance) {
+ return;
+ }
+ $this->delete_instance_as_record($taginstance, true);
+ }
+
+ /**
+ * Bulk delete all tag instances for a component or tag area
+ *
+ * @param string $component
+ * @param string $itemtype (optional)
+ * @param int $contextid (optional)
+ */
+ public static function delete_instances($component, $itemtype = null, $contextid = null) {
+ global $DB;
+
+ $sql = "SELECT ti.*, t.name, t.rawname, t.tagtype
+ FROM {tag_instance} ti
+ JOIN {tag} t
+ ON ti.tagid = t.id
+ WHERE ti.component = :component";
+ $params = array('component' => $component);
+ if (!is_null($contextid)) {
+ $sql .= " AND ti.contextid = :contextid";
+ $params['contextid'] = $contextid;
+ }
+ if (!is_null($itemtype)) {
+ $sql .= " AND ti.itemtype = :itemtype";
+ $params['itemtype'] = $itemtype;
+ }
+ if ($taginstances = $DB->get_records_sql($sql, $params)) {
+ // Now remove all the tag instances.
+ $DB->delete_records('tag_instance', $params);
+ // Save the system context in case the 'contextid' column in the 'tag_instance' table is null.
+ $syscontextid = context_system::instance()->id;
+ // Loop through the tag instances and fire an 'tag_removed' event.
+ foreach ($taginstances as $taginstance) {
+ // We can not fire an event with 'null' as the contextid.
+ if (is_null($taginstance->contextid)) {
+ $taginstance->contextid = $syscontextid;
+ }
+
+ // Trigger tag removed event.
+ \core\event\tag_removed::create_from_tag_instance($taginstance, $taginstance->name,
+ $taginstance->rawname, true)->trigger();
+ }
+ }
+ }
+
+ /**
+ * Adds a tag instance
+ *
+ * @param string $component
+ * @param string $itemtype
+ * @param string $itemid
+ * @param context $context
+ * @param int $ordering
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging (such as core/course)
+ * @return int id of tag_instance
+ */
+ protected function add_instance($component, $itemtype, $itemid, context $context, $ordering, $tiuserid = 0) {
+ global $DB;
+ $this->ensure_fields_exist(array('name', 'rawname'), 'add_instance');
+
+ $taginstance = new StdClass;
+ $taginstance->tagid = $this->id;
+ $taginstance->component = $component ? $component : '';
+ $taginstance->itemid = $itemid;
+ $taginstance->itemtype = $itemtype;
+ $taginstance->contextid = $context->id;
+ $taginstance->ordering = $ordering;
+ $taginstance->timecreated = time();
+ $taginstance->timemodified = $taginstance->timecreated;
+ $taginstance->tiuserid = $tiuserid;
+
+ $taginstance->id = $DB->insert_record('tag_instance', $taginstance);
+
+ // Trigger tag added event.
+ \core\event\tag_added::create_from_tag_instance($taginstance, $this->name, $this->rawname, true)->trigger();
+
+ return $taginstance->id;
+ }
+
+ /**
+ * Updates the ordering on tag instance
+ *
+ * @param int $instanceid
+ * @param int $ordering
+ */
+ protected function update_instance_ordering($instanceid, $ordering) {
+ global $DB;
+ $data = new stdClass();
+ $data->id = $instanceid;
+ $data->ordering = $ordering;
+ $data->timemodified = time();
+
+ $DB->update_record('tag_instance', $data);
+ }
+
+ /**
+ * Get the array of core_tag_tag objects associated with an item (instances).
+ *
+ * Use {@link core_tag_tag::get_item_tags_array()} if you wish to get the same data as simple array.
+ *
+ * @param string $component component responsible for tagging. For BC it can be empty but in this case the
+ * query will be slow because DB index will not be used.
+ * @param string $itemtype type of the tagged item
+ * @param int $itemid
+ * @param null|bool $official - true - official only, false - non official only, null - any (default)
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging
+ * @return core_tag_tag[] each object contains additional fields taginstanceid, taginstancecontextid and ordering
+ */
+ public static function get_item_tags($component, $itemtype, $itemid, $official = null, $tiuserid = 0) {
+ global $DB;
+
+ if (static::is_enabled($component, $itemtype) === false) {
+ // Tagging area is properly defined but not enabled - return empty array.
+ return array();
+ }
+
+ // Note: if the fields in this query are changed, you need to do the same changes in tag_get_correlated().
+ $sql = "SELECT ti.id AS taginstanceid, tg.id, tg.tagtype, tg.name, tg.rawname, tg.flag,
+ tg.tagcollid, ti.ordering, ti.contextid AS taginstancecontextid
+ FROM {tag_instance} ti
+ JOIN {tag} tg ON tg.id = ti.tagid
+ WHERE ti.itemtype = :itemtype AND ti.itemid = :itemid ".
+ ($component ? "AND ti.component = :component " : "").
+ ($tiuserid ? "AND ti.tiuserid = :tiuserid " : "").
+ (($official === true) ? "AND tg.tagtype = :official " : "").
+ (($official === false) ? "AND tg.tagtype <> :official " : "").
+ "ORDER BY ti.ordering ASC, ti.id";
+
+ $params = array();
+ $params['itemtype'] = $itemtype;
+ $params['itemid'] = $itemid;
+ $params['component'] = $component;
+ $params['official'] = 'official';
+ $params['tiuserid'] = $tiuserid;
+
+ $records = $DB->get_records_sql($sql, $params);
+ $result = array();
+ foreach ($records as $id => $record) {
+ $result[$id] = new static($record);
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the list of display names of the tags that are associated with an item
+ *
+ * This method is usually used to prefill the form data for the 'tags' form element
+ *
+ * @param string $component component responsible for tagging. For BC it can be empty but in this case the
+ * query will be slow because DB index will not be used.
+ * @param string $itemtype type of the tagged item
+ * @param int $itemid
+ * @param null|bool $official - true - official only, false - non official only, null - any (default)
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging
+ * @param bool $ashtml (default true) if true will return htmlspecialchars encoded tag names
+ * @return string[] array of tags display names
+ */
+ public static function get_item_tags_array($component, $itemtype, $itemid, $official = null, $tiuserid = 0, $ashtml = true) {
+ $tags = array();
+ foreach (static::get_item_tags($component, $itemtype, $itemid, $official, $tiuserid) as $tag) {
+ $tags[$tag->id] = $tag->get_display_name($ashtml);
+ }
+ return $tags;
+ }
+
+ /**
+ * Sets the list of tag instances for one item (table record).
+ *
+ * Extra exsisting instances are removed, new ones are added. New tags are created if needed.
+ *
+ * This method can not be used for setting tags relations, please use set_related_tags()
+ *
+ * @param string $component component responsible for tagging
+ * @param string $itemtype type of the tagged item
+ * @param int $itemid
+ * @param context $context
+ * @param array $tagnames
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging (such as core/course)
+ */
+ public static function set_item_tags($component, $itemtype, $itemid, context $context, $tagnames, $tiuserid = 0) {
+ if ($itemtype === 'tag') {
+ if ($tiuserid) {
+ throw new coding_exeption('Related tags can not have tag instance userid');
+ }
+ debugging('You can not use set_item_tags() for tagging a tag, please use set_related_tags()', DEBUG_DEVELOPER);
+ static::get($itemid, '*', MUST_EXIST)->set_related_tags($tagnames);
+ return;
+ }
+
+ if ($tagnames !== null && static::is_enabled($component, $itemtype) === false) {
+ // Tagging area is properly defined but not enabled - do nothing.
+ // Unless we are deleting the item tags ($tagnames === null), in which case proceed with deleting.
+ return;
+ }
+
+ // Apply clean_param() to all tags.
+ if ($tagnames) {
+ $tagcollid = core_tag_area::get_collection($component, $itemtype);
+ $tagobjects = static::create_if_missing($tagcollid, $tagnames);
+ } else {
+ $tagobjects = array();
+ }
+
+ $currenttags = static::get_item_tags($component, $itemtype, $itemid, null, $tiuserid);
+
+ // 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)) {
+ $taginstance = (object)array('id' => $currenttag->taginstanceid,
+ 'itemtype' => $itemtype, 'itemid' => $itemid,
+ 'contextid' => $currenttag->taginstancecontextid, 'tiuserid' => $tiuserid);
+ $currenttag->delete_instance_as_record($taginstance, false);
+ }
+ }
+
+ $ordering = -1;
+ foreach ($tagobjects as $name => $tag) {
+ $ordering++;
+ foreach ($currenttags as $currenttag) {
+ if (strval($currenttag->name) === strval($name)) {
+ if ($currenttag->ordering != $ordering) {
+ $currenttag->update_instance_ordering($currenttag->taginstanceid, $ordering);
+ }
+ continue 2;
+ }
+ }
+ $tag->add_instance($component, $itemtype, $itemid, $context, $ordering, $tiuserid);
+ }
+ }
+
+ /**
+ * Removes all tags from an item.
+ *
+ * All tags will be removed even if tagging is disabled in this area. This is
+ * usually called when the item itself has been deleted.
+ *
+ * @param string $component component responsible for tagging
+ * @param string $itemtype type of the tagged item
+ * @param int $itemid
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging (such as core/course)
+ */
+ public static function remove_all_item_tags($component, $itemtype, $itemid, $tiuserid = 0) {
+ $context = context_system::instance(); // Context will not be used.
+ static::set_item_tags($component, $itemtype, $itemid, $context, null, $tiuserid);
+ }
+
+ /**
+ * Adds a tag to an item, without overwriting the current tags.
+ *
+ * If the tag has already been added to the record, no changes are made.
+ *
+ * @param string $component the component that was tagged
+ * @param string $itemtype the type of record to tag ('post' for blogs, 'user' for users, etc.)
+ * @param int $itemid the id of the record to tag
+ * @param context $context the context of where this tag was assigned
+ * @param string $tagname the tag to add
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging (such as core/course)
+ * @return int id of tag_instance that was either created or already existed or null if tagging is not enabled
+ */
+ public static function add_item_tag($component, $itemtype, $itemid, context $context, $tagname, $tiuserid = 0) {
+ global $DB;
+
+ if (static::is_enabled($component, $itemtype) === false) {
+ // Tagging area is properly defined but not enabled - do nothing.
+ return null;
+ }
+
+ $rawname = clean_param($tagname, PARAM_TAG);
+ $normalisedname = core_text::strtolower($rawname);
+ $tagcollid = core_tag_area::get_collection($component, $itemtype);
+
+ $usersql = $tiuserid ? " AND ti.tiuserid = :tiuserid " : "";
+ $sql = 'SELECT t.*, ti.id AS taginstanceid
+ FROM {tag} t
+ LEFT JOIN {tag_instance} ti ON ti.tagid = t.id AND ti.itemtype = :itemtype '.
+ $usersql .
+ 'AND ti.itemid = :itemid AND ti.component = :component
+ WHERE t.name = :name AND t.tagcollid = :tagcollid';
+ $params = array('name' => $normalisedname, 'tagcollid' => $tagcollid, 'itemtype' => $itemtype,
+ 'itemid' => $itemid, 'component' => $component, 'tiuserid' => $tiuserid);
+ $record = $DB->get_record_sql($sql, $params);
+ if ($record) {
+ if ($record->taginstanceid) {
+ // Tag was already added to the item, nothing to do here.
+ return $record->taginstanceid;
+ }
+ $tag = new static($record);
+ } else {
+ // The tag does not exist yet, create it.
+ $tags = static::add($tagcollid, array($tagname));
+ $tag = reset($tags);
+ }
+
+ $ordering = $DB->get_field_sql('SELECT MAX(ordering) FROM {tag_instance} ti
+ WHERE ti.itemtype = :itemtype AND ti.itemid = itemid AND
+ ti.component = :component' . $usersql, $params);
+
+ return $tag->add_instance($component, $itemtype, $itemid, $context, $ordering + 1, $tiuserid);
+ }
+
+ /**
+ * Removes the tag from an item without changing the other tags
+ *
+ * @param string $component the component that was tagged
+ * @param string $itemtype the type of record to tag ('post' for blogs, 'user' for users, etc.)
+ * @param int $itemid the id of the record to tag
+ * @param string $tagname the tag to remove
+ * @param int $tiuserid tag instance user id, only needed for tag areas with user tagging (such as core/course)
+ */
+ public static function remove_item_tag($component, $itemtype, $itemid, $tagname, $tiuserid = 0) {
+ global $DB;
+
+ if (static::is_enabled($component, $itemtype) === false) {
+ // Tagging area is properly defined but not enabled - do nothing.
+ return array();
+ }
+
+ $rawname = clean_param($tagname, PARAM_TAG);
+ $normalisedname = core_text::strtolower($rawname);
+
+ $usersql = $tiuserid ? " AND tiuserid = :tiuserid " : "";
+ $componentsql = $component ? " AND ti.component = :component " : "";
+ $sql = 'SELECT t.*, ti.id AS taginstanceid, ti.contextid AS taginstancecontextid, ti.ordering
+ FROM {tag} t JOIN {tag_instance} ti ON ti.tagid = t.id ' . $usersql . '
+ WHERE t.name = :name AND ti.itemtype = :itemtype
+ AND ti.itemid = :itemid ' . $componentsql;
+ $params = array('name' => $normalisedname,
+ 'itemtype' => $itemtype, 'itemid' => $itemid, 'component' => $component,
+ 'tiuserid' => $tiuserid);
+ if ($record = $DB->get_record_sql($sql, $params)) {
+ $taginstance = (object)array('id' => $record->taginstanceid,
+ 'itemtype' => $itemtype, 'itemid' => $itemid,
+ 'contextid' => $record->taginstancecontextid, 'tiuserid' => $tiuserid);
+ $tag = new static($record);
+ $tag->delete_instance_as_record($taginstance, false);
+ $sql = "UPDATE {tag_instance} ti SET ordering = ordering - 1
+ WHERE ti.itemtype = :itemtype
+ AND ti.itemid = :itemid $componentsql $usersql
+ AND ti.ordering > :ordering";
+ $params['ordering'] = $record->ordering;
+ $DB->execute($sql, $params);
+ }
+ }
+
+ /**
+ * Allows to move all tag instances from one context to another
+ *
+ * @param string $component the component that was tagged
+ * @param string $itemtype the type of record to tag ('post' for blogs, 'user' for users, etc.)
+ * @param context $oldcontext
+ * @param context $newcontext
+ */
+ public static function move_context($component, $itemtype, $oldcontext, $newcontext) {
+ global $DB;
+ if ($oldcontext instanceof context) {
+ $oldcontext = $oldcontext->id;
+ }
+ if ($newcontext instanceof context) {
+ $newcontext = $newcontext->id;
+ }
+ $DB->set_field('tag_instance', 'contextid', $newcontext,
+ array('component' => $component, 'itemtype' => $itemtype, 'contextid' => $oldcontext));
+ }
+
+ /**
+ * Moves all tags of the specified items to the new context
+ *
+ * @param string $component the component that was tagged
+ * @param string $itemtype the type of record to tag ('post' for blogs, 'user' for users, etc.)
+ * @param array $itemids
+ * @param context|int $newcontext target context to move tags to
+ */
+ public static function change_items_context($component, $itemtype, $itemids, $newcontext) {
+ global $DB;
+ if (empty($itemids)) {
+ return;
+ }
+ if (!is_array($itemids)) {
+ $itemids = array($itemids);
+ }
+ list($sql, $params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
+ $params['component'] = $component;
+ $params['itemtype'] = $itemtype;
+ if ($newcontext instanceof context) {
+ $newcontext = $newcontext->id;
+ }
+ $DB->set_field_select('tag_instance', 'contextid', $newcontext,
+ 'component = :component AND itemtype = :itemtype AND itemid ' . $sql, $params);
+ }
+
+ /**
+ * Updates the information about the tag
+ *
+ * @param array|stdClass $data data to update, may contain: tagtype, description, descriptionformat, rawname
+ * @return bool whether the tag was updated. False may be returned if: all new values match the existing,
+ * or an invalid tagtype was supplied, or it was attempted to rename the tag to the name that is already used.
+ */
+ public function update($data) {
+ global $DB, $COURSE;
+
+ $allowedfields = array('tagtype', 'description', 'descriptionformat', 'rawname');
+
+ $data = (array)$data;
+ if ($extrafields = array_diff(array_keys($data), $allowedfields)) {
+ debugging('The field(s) '.join(', ', $extrafields).' will be ignored when updating the tag',
+ DEBUG_DEVELOPER);
+ }
+ $data = array_intersect_key($data, array_fill_keys($allowedfields, 1));
+ $this->ensure_fields_exist(array_merge(array('tagcollid', 'userid', 'name', 'rawname'), array_keys($data)), 'update');
+
+ // Validate the tag name.
+ if (array_key_exists('rawname', $data)) {
+ $data['rawname'] = clean_param($data['rawname'], PARAM_TAG);
+ $name = core_text::strtolower($data['rawname']);
+
+ if (!$name) {
+ unset($data['rawname']);
+ } else if ($existing = static::get_by_name($this->tagcollid, $name, 'id')) {
+ // Prevent the rename if a tag with that name already exists.
+ if ($existing->id != $this->id) {
+ debugging('New tag name already exists, you should check it before calling core_tag_tag::update()',
+ DEBUG_DEVELOPER);
+ unset($data['rawname']);
+ }
+ }
+ if (isset($data['rawname'])) {
+ $data['name'] = $name;
+ }
+ }
+
+ // Validate the tag type.
+ if (array_key_exists('tagtype', $data) && $data['tagtype'] !== 'official' && $data['tagtype'] !== 'default') {
+ debugging('Unrecognised tag type "'.$data['tagtype'].'" ignored when updating the tag', DEBUG_DEVELOPER);
+ unset($data['tagtype']);
+ }
+
+ // Find only the attributes that need to be changed.
+ $originalname = $this->name;
+ foreach ($data as $key => $value) {
+ if ($this->record->$key !== $value) {
+ $this->record->$key = $value;
+ } else {
+ unset($data[$key]);
+ }
+ }
+ if (empty($data)) {
+ return false;
+ }
+
+ $data['id'] = $this->id;
+ $data['timemodified'] = time();
+ $DB->update_record('tag', $data);
+
+ $event = \core\event\tag_updated::create(array(
+ 'objectid' => $this->id,
+ 'relateduserid' => $this->userid,
+ 'context' => context_system::instance(),
+ 'other' => array(
+ 'name' => $this->name,
+ 'rawname' => $this->rawname
+ )
+ ));
+ if (isset($data['rawname'])) {
+ $event->set_legacy_logdata(array($COURSE->id, 'tag', 'update', 'index.php?id='. $this->id,
+ $originalname . '->'. $this->name));
+ }
+ $event->trigger();
+ return true;
+ }
+
+ /**
+ * Flag a tag as inappropriate
+ */
+ public function flag() {
+ global $DB;
+
+ $this->ensure_fields_exist(array('name', 'userid', 'rawname', 'flag'), 'flag');
+
+ // Update all the tags to flagged.
+ $this->timemodified = time();
+ $this->flag++;
+ $DB->update_record('tag', array('timemodified' => $this->timemodified,
+ 'flag' => $this->flag, 'id' => $this->id));
+
+ $event = \core\event\tag_flagged::create(array(
+ 'objectid' => $this->id,
+ 'relateduserid' => $this->userid,
+ 'context' => context_system::instance(),
+ 'other' => array(
+ 'name' => $this->name,
+ 'rawname' => $this->rawname
+ )
+
+ ));
+ $event->trigger();
+ }
+
+ /**
+ * Remove the inappropriate flag on a tag.
+ */
+ public function reset_flag() {
+ global $DB;
+
+ $this->ensure_fields_exist(array('name', 'userid', 'rawname', 'flag'), 'flag');
+
+ if (!$this->flag) {
+ // Nothing to do.
+ return false;
+ }
+
+ $this->timemodified = time();
+ $this->flag = 0;
+ $DB->update_record('tag', array('timemodified' => $this->timemodified,
+ 'flag' => 0, 'id' => $this->id));
+
+ $event = \core\event\tag_unflagged::create(array(
+ 'objectid' => $this->id,
+ 'relateduserid' => $this->userid,
+ 'context' => context_system::instance(),
+ 'other' => array(
+ 'name' => $this->name,
+ 'rawname' => $this->rawname
+ )
+ ));
+ $event->trigger();
+ }
+
+ /**
+ * Sets the list of tags related to this one.
+ *
+ * Tag relations are recorded by two instances linking two tags to each other.
+ * For tag relations ordering is not used and may be random.
+ *
+ * @param array $tagnames
+ */
+ public function set_related_tags($tagnames) {
+ $context = context_system::instance();
+ $tagobjects = $tagnames ? static::create_if_missing($this->tagcollid, $tagnames) : array();
+ unset($tagobjects[$this->name]); // Never link to itself.
+
+ $currenttags = static::get_item_tags('core', 'tag', $this->id);
+
+ // 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)) {
+ $taginstance = (object)array('id' => $currenttag->taginstanceid,
+ 'itemtype' => 'tag', 'itemid' => $this->id,
+ 'contextid' => $context->id);
+ $currenttag->delete_instance_as_record($taginstance, false);
+ $this->delete_instance('core', 'tag', $currenttag->id);
+ }
+ }
+
+ foreach ($tagobjects as $name => $tag) {
+ foreach ($currenttags as $currenttag) {
+ if ($currenttag->name === $name) {
+ continue 2;
+ }
+ }
+ $this->add_instance('core', 'tag', $tag->id, $context, 0);
+ $tag->add_instance('core', 'tag', $this->id, $context, 0);
+ $currenttags[] = $tag;
+ }
+ }
+
+ /**
+ * Adds to the list of related tags without removing existing
+ *
+ * Tag relations are recorded by two instances linking two tags to each other.
+ * For tag relations ordering is not used and may be random.
+ *
+ * @param array $tagnames
+ */
+ public function add_related_tags($tagnames) {
+ $context = context_system::instance();
+ $tagobjects = static::create_if_missing($this->tagcollid, $tagnames);
+
+ $currenttags = static::get_item_tags('core', 'tag', $this->id);
+
+ foreach ($tagobjects as $name => $tag) {
+ foreach ($currenttags as $currenttag) {
+ if ($currenttag->name === $name) {
+ continue 2;
+ }
+ }
+ $this->add_instance('core', 'tag', $tag->id, $context, 0);
+ $tag->add_instance('core', 'tag', $this->id, $context, 0);
+ $currenttags[] = $tag;
+ }
+ }
+
+ /**
+ * Returns the correlated tags of a tag, retrieved from the tag_correlation table.
+ *
+ * Correlated tags are calculated in cron based on existing tag instances.
+ *
+ * @param bool $keepduplicates if true, will return one record for each existing
+ * tag instance which may result in duplicates of the actual tags
+ * @return core_tag_tag[] an array of tag objects
+ */
+ public function get_correlated_tags($keepduplicates = false) {
+ global $DB;
+
+ $correlated = $DB->get_field('tag_correlation', 'correlatedtags', array('tagid' => $this->id));
+
+ if (!$correlated) {
+ return array();
+ }
+ $correlated = preg_split('/\s*,\s*/', trim($correlated), -1, PREG_SPLIT_NO_EMPTY);
+ list($query, $params) = $DB->get_in_or_equal($correlated);
+
+ // This is (and has to) return the same fields as the query in core_tag_tag::get_item_tags().
+ $sql = "SELECT ti.id AS taginstanceid, tg.id, tg.tagtype, tg.name, tg.rawname, tg.flag,
+ tg.tagcollid, ti.ordering, ti.contextid AS taginstancecontextid
+ FROM {tag} tg
+ INNER JOIN {tag_instance} ti ON tg.id = ti.tagid
+ WHERE tg.id $query
+ ORDER BY ti.ordering ASC, ti.id";
+ $records = $DB->get_records_sql($sql, $params);
+ $seen = array();
+ $result = array();
+ foreach ($records as $id => $record) {
+ if (!$keepduplicates && !empty($seen[$record->id])) {
+ continue;
+ }
+ $result[$id] = new static($record);
+ $seen[$record->id] = true;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns tags that this tag was manually set as related to
+ *
+ * @return core_tag_tag[]
+ */
+ public function get_manual_related_tags() {
+ return self::get_item_tags('core', 'tag', $this->id);
+ }
+
+ /**
+ * Returns tags related to a tag
+ *
+ * Related tags of a tag come from two sources:
+ * - manually added related tags, which are tag_instance entries for that tag
+ * - correlated tags, which are calculated
+ *
+ * @return core_tag_tag[] an array of tag objects
+ */
+ public function get_related_tags() {
+ $manual = $this->get_manual_related_tags();
+ $automatic = $this->get_correlated_tags();
+ $relatedtags = array_merge($manual, $automatic);
+
+ // Remove duplicated tags (multiple instances of the same tag).
+ $seen = array();
+ foreach ($relatedtags as $instance => $tag) {
+ if (isset($seen[$tag->id])) {
+ unset($relatedtags[$instance]);
+ } else {
+ $seen[$tag->id] = 1;
+ }
+ }
+
+ return $relatedtags;
+ }
+
+ /**
+ * Find all items tagged with a tag of a given type ('post', 'user', etc.)
+ *
+ * @param string $component component responsible for tagging. For BC it can be empty but in this case the
+ * query will be slow because DB index will not be used.
+ * @param string $itemtype type to restrict search to
+ * @param int $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point.
+ * @param int $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records.
+ * @param string $subquery additional query to be appended to WHERE clause, refer to the itemtable as 'it'
+ * @param array $params additional parameters for the DB query
+ * @return array of matching objects, indexed by record id, from the table containing the type requested
+ */
+ public function get_tagged_items($component, $itemtype, $limitfrom = '', $limitnum = '', $subquery = '', $params = array()) {
+ global $DB;
+
+ if (empty($itemtype) || !$DB->get_manager()->table_exists($itemtype)) {
+ return array();
+ }
+ $params = $params ? $params : array();
+
+ $query = "SELECT it.*
+ FROM {".$itemtype."} it INNER JOIN {tag_instance} tt ON it.id = tt.itemid
+ WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid";
+ $params['itemtype'] = $itemtype;
+ $params['tagid'] = $this->id;
+ if ($component) {
+ $query .= ' AND tt.component = :component';
+ $params['component'] = $component;
+ }
+ if ($subquery) {
+ $query .= ' AND ' . $subquery;
+ }
+ $query .= ' ORDER BY it.id';
+
+ return $DB->get_records_sql($query, $params, $limitfrom, $limitnum);
+ }
+
+ /**
+ * Count how many items are tagged with a specific tag.
+ *
+ * @param string $component component responsible for tagging. For BC it can be empty but in this case the
+ * query will be slow because DB index will not be used.
+ * @param string $itemtype type to restrict search to
+ * @param string $subquery additional query to be appended to WHERE clause, refer to the itemtable as 'it'
+ * @param array $params additional parameters for the DB query
+ * @return int number of mathing tags.
+ */
+ public function count_tagged_items($component, $itemtype, $subquery = '', $params = array()) {
+ global $DB;
+
+ if (empty($itemtype) || !$DB->get_manager()->table_exists($itemtype)) {
+ return 0;
+ }
+ $params = $params ? $params : array();
+
+ $query = "SELECT COUNT(it.id)
+ FROM {".$itemtype."} it INNER JOIN {tag_instance} tt ON it.id = tt.itemid
+ WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid";
+ $params['itemtype'] = $itemtype;
+ $params['tagid'] = $this->id;
+ if ($component) {
+ $query .= ' AND tt.component = :component';
+ $params['component'] = $component;
+ }
+ if ($subquery) {
+ $query .= ' AND ' . $subquery;
+ }
+
+ return $DB->get_field_sql($query, $params);
+ }
+
+ /**
+ * Determine if an item is tagged with a specific tag
+ *
+ * Note that this is a static method and not a method of core_tag object because the tag might not exist yet,
+ * for example user searches for "php" and we offer him to add "php" to his interests.
+ *
+ * @param string $component component responsible for tagging. For BC it can be empty but in this case the
+ * query will be slow because DB index will not be used.
+ * @param string $itemtype the record type to look for
+ * @param int $itemid the record id to look for
+ * @param string $tagname a tag name
+ * @return int 1 if it is tagged, 0 otherwise
+ */
+ public static function is_item_tagged_with($component, $itemtype, $itemid, $tagname) {
+ global $DB;
+ $tagcollid = core_tag_area::get_collection($component, $itemtype);
+ $query = 'SELECT 1 FROM {tag} t
+ JOIN {tag_instance} ti ON ti.tagid = t.id
+ WHERE t.name = ? AND t.tagcollid = ? AND ti.itemtype = ? AND ti.itemid = ?';
+ $cleanname = core_text::strtolower(clean_param($tagname, PARAM_TAG));
+ $params = array($cleanname, $tagcollid, $itemtype, $itemid);
+ if ($component) {
+ $query .= ' AND ti.component = ?';
+ $params[] = $component;
+ }
+ return $DB->record_exists_sql($query, $params) ? 1 : 0;
+ }
+
+ /**
+ * Returns whether the tag area is enabled
+ *
+ * @param string $component component responsible for tagging
+ * @param string $itemtype what is being tagged, for example, 'post', 'course', 'user', etc.
+ * @return bool|null
+ */
+ public static function is_enabled($component, $itemtype) {
+ return core_tag_area::is_enabled($component, $itemtype);
+ }
+
+ /**
+ * Retrieves contents of tag area for the tag/index.php page
+ *
+ * @param stdClass $tagarea
+ * @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 $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param int $page 0-based number of page being displayed
+ * @return \core_tag\output\tagindex
+ */
+ public function get_tag_index($tagarea, $exclusivemode, $fromctx, $ctx, $rec, $page = 0) {
+ global $CFG;
+ if (!empty($tagarea->callback)) {
+ if (!empty($tagarea->callbackfile)) {
+ require_once($CFG->dirroot . '/' . ltrim($tagarea->callbackfile, '/'));
+ }
+ $callback = $tagarea->callback;
+ return $callback($this, $exclusivemode, $fromctx, $ctx, $rec, $page);
+ }
+ return null;
+ }
+
+ /**
+ * Returns formatted description of the tag
+ *
+ * @param array $options
+ * @return string
+ */
+ public function get_formatted_description($options = array()) {
+ $options = empty($options) ? array() : (array)$options;
+ $options += array('para' => false, 'overflowdiv' => true);
+ $description = file_rewrite_pluginfile_urls($this->description, 'pluginfile.php',
+ context_system::instance()->id, 'tag', 'description', $this->id);
+ return format_text($description, $this->descriptionformat, $options);
+ }
+
+ /**
+ * Returns the list of tag links available for the current user (edit, flag, etc.)
+ *
+ * @return array
+ */
+ public function get_links() {
+ global $USER;
+ $links = array();
+
+ if (!isloggedin() || isguestuser()) {
+ return $links;
+ }
+
+ $tagname = $this->get_display_name();
+ $systemcontext = context_system::instance();
+
+ // Add a link for users to add/remove this from their interests.
+ if (static::is_enabled('core', 'user') && core_tag_area::get_collection('core', 'user') == $this->tagcollid) {
+ if (static::is_item_tagged_with('core', 'user', $USER->id, $this->name)) {
+ $url = new moodle_url('/tag/user.php', array('action' => 'removeinterest',
+ 'sesskey' => sesskey(), 'tag' => $this->rawname));
+ $links[] = html_writer::link($url, get_string('removetagfrommyinterests', 'tag', $tagname),
+ array('class' => 'removefrommyinterests'));
+ } else {
+ $url = new moodle_url('/tag/user.php', array('action' => 'addinterest',
+ 'sesskey' => sesskey(), 'tag' => $this->rawname));
+ $links[] = html_writer::link($url, get_string('addtagtomyinterests', 'tag', $tagname),
+ array('class' => 'addtomyinterests'));
+ }
+ }
+
+ // Flag as inappropriate link. Only people with moodle/tag:flag capability.
+ if (has_capability('moodle/tag:flag', $systemcontext)) {
+ $url = new moodle_url('/tag/user.php', array('action' => 'flaginappropriate',
+ 'sesskey' => sesskey(), 'id' => $this->id));
+ $links[] = html_writer::link($url, get_string('flagasinappropriate', 'tag', $tagname),
+ array('class' => 'flagasinappropriate'));
+ }
+
+ // Edit tag: Only people with moodle/tag:edit capability who either have it as an interest or can manage tags.
+ if (has_capability('moodle/tag:edit', $systemcontext) ||
+ has_capability('moodle/tag:manage', $systemcontext)) {
+ $url = new moodle_url('/tag/edit.php', array('id' => $this->id));
+ $links[] = html_writer::link($url, get_string('edittag', 'tag'),
+ array('class' => 'edittag'));
+ }
+
+ return $links;
+ }
+
+ /**
+ * Delete one or more tag, and all their instances if there are any left.
+ *
+ * @param int|array $tagids one tagid (int), or one array of tagids to delete
+ * @return bool true on success, false otherwise
+ */
+ public static function delete_tags($tagids) {
+ global $DB;
+
+ if (!is_array($tagids)) {
+ $tagids = array($tagids);
+ }
+ if (empty($tagids)) {
+ return;
+ }
+
+ // Use the tagids to create a select statement to be used later.
+ list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids);
+
+ // Store the tags and tag instances we are going to delete.
+ $tags = $DB->get_records_select('tag', 'id ' . $tagsql, $tagparams);
+ $taginstances = $DB->get_records_select('tag_instance', 'tagid ' . $tagsql, $tagparams);
+
+ // Delete all the tag instances.
+ $select = 'WHERE tagid ' . $tagsql;
+ $sql = "DELETE FROM {tag_instance} $select";
+ $DB->execute($sql, $tagparams);
+
+ // Delete all the tag correlations.
+ $sql = "DELETE FROM {tag_correlation} $select";
+ $DB->execute($sql, $tagparams);
+
+ // Delete all the tags.
+ $select = 'WHERE id ' . $tagsql;
+ $sql = "DELETE FROM {tag} $select";
+ $DB->execute($sql, $tagparams);
+
+ // Fire an event that these items were untagged.
+ if ($taginstances) {
+ // Save the system context in case the 'contextid' column in the 'tag_instance' table is null.
+ $syscontextid = context_system::instance()->id;
+ // Loop through the tag instances and fire a 'tag_removed'' event.
+ foreach ($taginstances as $taginstance) {
+ // We can not fire an event with 'null' as the contextid.
+ if (is_null($taginstance->contextid)) {
+ $taginstance->contextid = $syscontextid;
+ }
+
+ // Trigger tag removed event.
+ \core\event\tag_removed::create_from_tag_instance($taginstance,
+ $tags[$taginstance->tagid]->name, $tags[$taginstance->tagid]->rawname,
+ true)->trigger();
+ }
+ }
+
+ // Fire an event that these tags were deleted.
+ if ($tags) {
+ $context = context_system::instance();
+ foreach ($tags as $tag) {
+ // Delete all files associated with this tag.
+ $fs = get_file_storage();
+ $files = $fs->get_area_files($context->id, 'tag', 'description', $tag->id);
+ foreach ($files as $file) {
+ $file->delete();
+ }
+
+ // Trigger an event for deleting this tag.
+ $event = \core\event\tag_deleted::create(array(
+ 'objectid' => $tag->id,
+ 'relateduserid' => $tag->userid,
+ 'context' => $context,
+ 'other' => array(
+ 'name' => $tag->name,
+ 'rawname' => $tag->rawname
+ )
+ ));
+ $event->add_record_snapshot('tag', $tag);
+ $event->trigger();
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/tag/edit.php b/tag/edit.php
index 16bf9e7b04e..a3087577c82 100644
--- a/tag/edit.php
+++ b/tag/edit.php
@@ -26,8 +26,8 @@ require_once('../config.php');
require_once('lib.php');
require_once('edit_form.php');
-$tag_id = optional_param('id', 0, PARAM_INT);
-$tag_name = optional_param('tag', '', PARAM_TAG);
+$tagid = optional_param('id', 0, PARAM_INT);
+$tagname = optional_param('tag', '', PARAM_TAG);
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
require_login();
@@ -40,34 +40,50 @@ if (empty($CFG->usetags)) {
$systemcontext = context_system::instance();
require_capability('moodle/tag:edit', $systemcontext);
-if ($tag_name) {
- $tag = tag_get('name', $tag_name, '*');
-} else if ($tag_id) {
- $tag = tag_get('id', $tag_id, '*');
+if ($tagname) {
+ $tagcollid = optional_param('tc', 0, PARAM_INT);
+ if (!$tagcollid) {
+ // Tag name specified but tag collection was not. Try to guess it.
+ $tags = core_tag_tag::guess_by_name($tagname, '*');
+ if (count($tags) > 1) {
+ // This tag was found in more than one collection, redirect to search.
+ redirect(new moodle_url('/tag/search.php', array('tag' => $tagname)));
+ } else if (count($tags) == 1) {
+ $tag = reset($tags);
+ }
+ } else {
+ if (!$tag = core_tag_tag::get_by_name($tagcollid, $tagname, '*')) {
+ redirect(new moodle_url('/tag/search.php', array('tagcollid' => $tagcollid)));
+ }
+ }
+} else if ($tagid) {
+ $tag = core_tag_tag::get($tagid, '*');
}
if (empty($tag)) {
- redirect($CFG->wwwroot.'/tag/search.php');
+ redirect(new moodle_url('/tag/search.php'));
}
-$PAGE->set_url('/tag/index.php', array('id' => $tag->id));
+$PAGE->set_url($tag->get_view_url());
$PAGE->set_subpage($tag->id);
$PAGE->set_context($systemcontext);
$PAGE->set_blocks_editing_capability('moodle/tag:editblocks');
$PAGE->set_pagelayout('standard');
-$tagname = tag_display_name($tag);
+$tagname = $tag->get_display_name();
+$tagcollid = $tag->tagcollid;
// set the relatedtags field of the $tag object that will be passed to the form
-$tag->relatedtags = tag_get_tags_array('tag', $tag->id);
+$data = $tag->to_object();
+$data->relatedtags = core_tag_tag::get_item_tags_array('core', 'tag', $tag->id);
$options = new stdClass();
$options->smiley = false;
$options->filter = false;
// convert and remove any XSS
-$tag->description = format_text($tag->description, $tag->descriptionformat, $options);
-$tag->descriptionformat = FORMAT_HTML;
+$data->description = format_text($tag->description, $tag->descriptionformat, $options);
+$data->descriptionformat = FORMAT_HTML;
$errorstring = '';
@@ -78,76 +94,37 @@ $editoroptions = array(
'context' => $systemcontext,
'subdirs' => file_area_contains_subdirs($systemcontext, 'tag', 'description', $tag->id),
);
-$tag = file_prepare_standard_editor($tag, 'description', $editoroptions, $systemcontext, 'tag', 'description', $tag->id);
+$data = file_prepare_standard_editor($data, 'description', $editoroptions, $systemcontext, 'tag', 'description', $data->id);
-$tagform = new tag_edit_form(null, compact('editoroptions'));
-if ( $tag->tagtype == 'official' ) {
- $tag->tagtype = '1';
-} else {
- $tag->tagtype = '0';
-}
+$tagform = new tag_edit_form(null, array('editoroptions' => $editoroptions, 'tag' => $tag));
+$data->tagtype = ($data->tagtype === 'official') ? '1' : '0';
+$data->returnurl = $returnurl;
-$tag->returnurl = $returnurl;
-$tagform->set_data($tag);
+$tagform->set_data($data);
-// If new data has been sent, update the tag record
if ($tagform->is_cancelled()) {
- redirect($returnurl ? new moodle_url($returnurl) :
- new moodle_url('/tag/index.php', array('tag' => $tag->name)));
+ redirect($returnurl ? new moodle_url($returnurl) : $tag->get_view_url());
} else if ($tagnew = $tagform->get_data()) {
+ // If new data has been sent, update the tag record.
+ $updatedata = array();
if (has_capability('moodle/tag:manage', $systemcontext)) {
- if (($tag->tagtype != 'default') && (!isset($tagnew->tagtype) || ($tagnew->tagtype != '1'))) {
- tag_type_set($tag->id, 'default');
-
- } elseif (($tag->tagtype != 'official') && ($tagnew->tagtype == '1')) {
- tag_type_set($tag->id, 'official');
- }
+ $updatedata['tagtype'] = empty($tagnew->tagtype) ? 'default' : 'official';
+ $updatedata['rawname'] = $tagnew->rawname;
}
- if (!has_capability('moodle/tag:manage', $systemcontext)) {
- unset($tagnew->name);
- unset($tagnew->rawname);
+ $tagnew = file_postupdate_standard_editor($tagnew, 'description', $editoroptions,
+ $systemcontext, 'tag', 'description', $tag->id);
+ $updatedata['description'] = $tagnew->description;
+ $updatedata['descriptionformat'] = $tagnew->descriptionformat;
- } else { // They might be trying to change the rawname, make sure it's a change that doesn't affect name
- $norm = tag_normalize($tagnew->rawname, TAG_CASE_LOWER);
- $tagnew->name = array_shift($norm);
+ // Update name, description and official type.
+ $tag->update($updatedata);
- if ($tag->rawname !== $tagnew->rawname) { // The name has changed, let's make sure it's not another existing tag
- if (($id = tag_get_id($tagnew->name)) && $id != $tag->id) { // Something exists already, so flag an error.
- $errorstring = s($tagnew->rawname).': '.get_string('namesalreadybeeingused', 'tag');
- }
- }
- }
+ // Updated related tags.
+ $tag->set_related_tags($tagnew->relatedtags);
- if (empty($errorstring)) { // All is OK, let's save it
-
- $tagnew = file_postupdate_standard_editor($tagnew, 'description', $editoroptions, $systemcontext, 'tag', 'description', $tag->id);
-
- if ($tag->description != $tagnew->description) {
- tag_description_set($tag_id, $tagnew->description, $tagnew->descriptionformat);
- }
-
- $tagnew->timemodified = time();
-
- if (has_capability('moodle/tag:manage', $systemcontext)) {
- // Check if we need to rename the tag.
- if (isset($tagnew->name) && ($tag->rawname != $tagnew->rawname)) {
- // Rename the tag.
- if (!tag_rename($tag->id, $tagnew->rawname)) {
- print_error('errorupdatingrecord', 'tag');
- }
- }
- }
-
- //updated related tags
- tag_set('tag', $tagnew->id, $tagnew->relatedtags, 'core', $systemcontext->id);
- //print_object($tagnew); die();
-
- $tagname = isset($tagnew->rawname) ? $tagnew->rawname : $tag->rawname;
- redirect($returnurl ? new moodle_url($returnurl) :
- new moodle_url('/tag/index.php', array('tag' => $tagname)));
- }
+ redirect($returnurl ? new moodle_url($returnurl) : $tag->get_view_url());
}
navigation_node::override_active_url(new moodle_url('/tag/search.php'));
diff --git a/tag/edit_form.php b/tag/edit_form.php
index c2fcdc381c0..ef2fc8a32f1 100644
--- a/tag/edit_form.php
+++ b/tag/edit_form.php
@@ -68,10 +68,36 @@ class tag_edit_form extends moodleform {
$mform->addElement('checkbox', 'tagtype', get_string('officialtag', 'tag'));
}
- $mform->addElement('tags', 'relatedtags', get_string('relatedtags','tag'));
+ $mform->addElement('tags', 'relatedtags', get_string('relatedtags', 'tag'),
+ array('tagcollid' => $this->_customdata['tag']->tagcollid));
$this->add_action_buttons(true, get_string('updatetag', 'tag'));
}
+ /**
+ * Custom form validation
+ *
+ * @param array $data
+ * @param array $files
+ * @return array
+ */
+ public function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+
+ if (isset($data['rawname'])) {
+ $newname = core_text::strtolower($data['rawname']);
+ $tag = $this->_customdata['tag'];
+ if ($tag->name != $newname) {
+ // The name has changed, let's make sure it's not another existing tag.
+ if (core_tag_tag::get_by_name($tag->tagcollid, $newname)) {
+ // Something exists already, so flag an error.
+ $errors['rawname'] = get_string('namesalreadybeeingused', 'tag');
+ }
+ }
+ }
+
+ return $errors;
+ }
+
}
diff --git a/tag/index.php b/tag/index.php
index 8934d87753d..b0373168bd6 100644
--- a/tag/index.php
+++ b/tag/index.php
@@ -23,10 +23,8 @@
*/
require_once('../config.php');
-require_once('lib.php');
-require_once('locallib.php');
-require_once($CFG->dirroot.'/lib/weblib.php');
-require_once($CFG->dirroot.'/blog/lib.php');
+require_once($CFG->dirroot . '/lib/weblib.php');
+require_once($CFG->dirroot . '/blog/lib.php');
require_login();
@@ -36,166 +34,93 @@ if (empty($CFG->usetags)) {
$tagid = optional_param('id', 0, PARAM_INT); // tag id
$tagname = optional_param('tag', '', PARAM_TAG); // tag
+$tagareaid = optional_param('ta', 0, PARAM_INT); // Tag area id.
+$exclusivemode = optional_param('excl', 0, PARAM_BOOL); // Exclusive mode (show entities in one tag area only).
+$page = optional_param('page', 0, PARAM_INT); // Page to display.
+$fromctx = optional_param('from', null, PARAM_INT);
+$ctx = optional_param('ctx', null, PARAM_INT);
+$rec = optional_param('rec', 1, PARAM_INT);
$edit = optional_param('edit', -1, PARAM_BOOL);
-$userpage = optional_param('userpage', 0, PARAM_INT); // which page to show
-$perpage = optional_param('perpage', 24, PARAM_INT);
$systemcontext = context_system::instance();
if ($tagname) {
- $tag = tag_get('name', $tagname, '*');
+ $tagcollid = optional_param('tc', 0, PARAM_INT);
+ if (!$tagcollid) {
+ // Tag name specified but tag collection was not. Try to guess it.
+ $tags = core_tag_tag::guess_by_name($tagname, '*');
+ if (count($tags) > 1) {
+ // This tag was found in more than one collection, redirect to search.
+ redirect(new moodle_url('/tag/search.php', array('query' => $tagname)));
+ } else if (count($tags) == 1) {
+ $tag = reset($tags);
+ }
+ } else {
+ if (!$tag = core_tag_tag::get_by_name($tagcollid, $tagname, '*')) {
+ redirect(new moodle_url('/tag/search.php', array('tc' => $tagcollid, 'query' => $tagname)));
+ }
+ }
} else if ($tagid) {
- $tag = tag_get('id', $tagid, '*');
+ $tag = core_tag_tag::get($tagid, '*');
}
unset($tagid);
if (empty($tag)) {
- redirect($CFG->wwwroot.'/tag/search.php');
+ redirect(new moodle_url('/tag/search.php'));
}
-$PAGE->set_url('/tag/index.php', array('id' => $tag->id));
+if ($ctx && ($context = context::instance_by_id($ctx, IGNORE_MISSING)) && $context->contextlevel >= CONTEXT_COURSE) {
+ list($context, $course, $cm) = get_context_info_array($context->id);
+ require_login($course, false, $cm, false, true);
+} else {
+ $PAGE->set_context($systemcontext);
+}
+
+$tagcollid = $tag->tagcollid;
+
+$PAGE->set_url($tag->get_view_url($exclusivemode, $fromctx, $ctx, $rec));
$PAGE->set_subpage($tag->id);
-$PAGE->set_context($systemcontext);
$tagnode = $PAGE->navigation->find('tags', null);
$tagnode->make_active();
$PAGE->set_pagelayout('standard');
$PAGE->set_blocks_editing_capability('moodle/tag:editblocks');
-if (($edit != -1) and $PAGE->user_allowed_editing()) {
- $USER->editing = $edit;
+$buttons = '';
+if (has_capability('moodle/tag:manage', context_system::instance())) {
+ $buttons .= $OUTPUT->single_button(new moodle_url('/tag/manage.php'),
+ get_string('managetags', 'tag'), 'GET');
}
-
-$tagname = tag_display_name($tag);
-$title = get_string('tag', 'tag') .' - '. $tagname;
-
-$button = '';
-if ($PAGE->user_allowed_editing() ) {
- $button = $OUTPUT->edit_button(new moodle_url("$CFG->wwwroot/tag/index.php", array('id' => $tag->id)));
+if ($PAGE->user_allowed_editing()) {
+ if ($edit != -1) {
+ $USER->editing = $edit;
+ }
+ $buttons .= $OUTPUT->edit_button(clone($PAGE->url));
}
$PAGE->navbar->add($tagname);
-$PAGE->set_title($title);
+$PAGE->set_title(get_string('tag', 'tag') .' - '. $tag->get_display_name());
$PAGE->set_heading($COURSE->fullname);
-$PAGE->set_button($button);
-$courserenderer = $PAGE->get_renderer('core', 'course');
+$PAGE->set_button($buttons);
+
+// Find all areas in this collection and their items tagged with this tag.
+$tagareas = core_tag_collection::get_areas($tagcollid);
+if ($tagareaid) {
+ $tagareas = array_intersect_key($tagareas, array($tagareaid => 1));
+}
+if (!$tagareaid && count($tagareas) == 1) {
+ // Automatically set "exclusive" mode for tag collection with one tag area only.
+ $exclusivemode = 1;
+}
+$entities = array();
+foreach ($tagareas as $ta) {
+ $entities[] = $tag->get_tag_index($ta, $exclusivemode, $fromctx, $ctx, $rec, $page);
+}
+$entities = array_filter($entities);
+
+$tagrenderer = $PAGE->get_renderer('core', 'tag');
+$pagecontents = $tagrenderer->tag_index_page($tag, array_filter($entities), $tagareaid,
+ $exclusivemode, $fromctx, $ctx, $rec, $page);
+
echo $OUTPUT->header();
-
-// Manage all tags links
-if (has_capability('moodle/tag:manage', $systemcontext)) {
- echo '' ;
-}
-
-$tagname = tag_display_name($tag);
-
-if ($tag->flag > 0 && has_capability('moodle/tag:manage', $systemcontext)) {
- $tagname = '' . $tagname . '';
-}
-
-echo $OUTPUT->heading($tagname, 2);
-tag_print_management_box($tag);
-tag_print_description_box($tag);
-// Check what type of results are avaialable
-$courses = $courserenderer->tagged_courses($tag->id);
-
-if (!empty($CFG->enableblogs) && has_capability('moodle/blog:view', $systemcontext)) {
- require_once($CFG->dirroot.'/blog/lib.php');
- require_once($CFG->dirroot.'/blog/locallib.php');
-
- $bloglisting = new blog_listing(array('tag' => $tag->id));
- $limit = 10;
- $start = 0;
- $blogs = $bloglisting->get_entries($start, $limit);
-}
-$usercount = tag_record_count('user', $tag->id);
-
-// Only include 's to those anchors that actually will be shown
-$relatedpageslink = "";
-$countanchors = 0;
-if (!empty($courses)) {
- $relatedpageslink = ''.get_string('courses').'';
- $countanchors++;
-}
-if (!empty($blogs)) {
- if ($countanchors > 0) {
- $relatedpageslink .= ' | ';
- }
- $relatedpageslink .= ''.get_string('relatedblogs', 'tag').'';
- $countanchors++;
-}
-if ($usercount > 0) {
- if ($countanchors > 0) {
- $relatedpageslink .= ' | ';
- }
- $relatedpageslink .= ''.get_string('users').'';
- $countanchors++;
-}
-// If only one anchor is present, no is needed
-if ($countanchors == 0) {
- echo ''.get_string('noresultsfor', 'tag', $tagname).'
';
-} elseif ($countanchors > 1) {
- echo '';
-}
-
-// Display courses tagged with the tag
-if (!empty($courses)) {
-
- echo $OUTPUT->box_start('generalbox', 'tag-blogs'); //could use an id separate from tag-blogs, but would have to copy the css style to make it look the same
-
- echo "";
- echo $courses;
-
- echo $OUTPUT->box_end();
-}
-
-// Print up to 10 previous blogs entries
-
-if (!empty($blogs)) {
- echo $OUTPUT->box_start('generalbox', 'tag-blogs');
- $heading = get_string('relatedblogs', 'tag', $tagname). ' ' . get_string('taggedwith', 'tag', $tagname);
- echo "";
- echo $OUTPUT->heading($heading, 3);
-
- echo '';
- foreach ($blogs as $blog) {
- if ($blog->publishstate == 'draft') {
- $class = 'class="dimmed"';
- } else {
- $class = '';
- }
- echo '- ';
- echo '';
- echo format_string($blog->subject);
- echo '';
- echo ' - ';
- echo '';
- echo fullname($blog);
- echo '';
- echo ', '. userdate($blog->lastmodified);
- echo '
';
- }
- echo '
';
-
- $allblogsurl = new moodle_url('/blog/index.php', array('tagid' => $tag->id));
- echo ''.get_string('seeallblogs', 'tag', $tagname).'
';
-
- echo $OUTPUT->box_end();
-}
-
-if ($usercount > 0) {
-
- //user table box
- echo $OUTPUT->box_start('generalbox', 'tag-user-table');
-
- $heading = get_string('users'). ' ' . get_string('taggedwith', 'tag', $tagname) . ': ' . $usercount;
- echo "";
- echo $OUTPUT->heading($heading, 3);
-
- $baseurl = new moodle_url('/tag/index.php', array('id' => $tag->id));
- $pagingbar = new paging_bar($usercount, $userpage, $perpage, $baseurl);
- $pagingbar->pagevar = 'userpage';
- echo $OUTPUT->render($pagingbar);
- tag_print_tagged_users_table($tag, $userpage * $perpage, $perpage);
- echo $OUTPUT->box_end();
-}
-
+echo $pagecontents;
echo $OUTPUT->footer();
diff --git a/tag/lib.php b/tag/lib.php
index 15dc38e3316..c399650dad1 100644
--- a/tag/lib.php
+++ b/tag/lib.php
@@ -14,1544 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-
/**
- * Moodle tag library
+ * Functions for component core_tag
*
- * Tag strings : you can use any character in tags, except the comma (which is the separator) and
- * the '\' (backslash). Note that many spaces (or other blank characters) will get "compressed"
- * into one. A tag string is always a rawurlencode'd string. This is the same behavior as
- * http://del.icio.us.
- *
- * A "record" is a php array (note that an object will work too) that contains the following
- * variables :
- * - type: The database table containing the record that we are tagging (eg: for a blog, this is
- * the table named 'post', and for a user it is the table name 'user')
- * - id: The id of the record
- *
- * BASIC INSTRUCTIONS :
- * - to "tag a blog post" (for example):
- * tag_set('post', $blog_post->id, $array_of_tags, 'core', $thecontext);
- *
- * - to "remove all the tags on a blog post":
- * tag_set('post', $blog_post->id, array(), 'core', $thecontext);
- *
- * Tag set will create tags that need to be created.
+ * To set or get item tags refer to the class {@link core_tag_tag}
*
* @package core_tag
- * @category tag
- * @todo MDL-31090 turn this into a full-fledged categorization system. This could start by
- * modifying (removing, probably) the 'tag type' to use another table describing the
- * relationship between tags (parents, sibling, etc.), which could then be merged with
- * the 'course categorization' system.
- * @see http://www.php.net/manual/en/function.urlencode.php
* @copyright 2007 Luiz Cruz
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-/**
- * Used to require that the return value from a function is an array.
- * @see tag_set()
- */
-define('TAG_RETURN_ARRAY', 0);
-/**
- * Used to require that the return value from a function is an object.
- * @see tag_set()
- */
-define('TAG_RETURN_OBJECT', 1);
-/**
- * Use to specify that HTML free text is expected to be returned from a function.
- * @see tag_display_name()
- */
-define('TAG_RETURN_TEXT', 2);
-/**
- * Use to specify that encoded HTML is expected to be returned from a function.
- * @see tag_display_name()
- */
-define('TAG_RETURN_HTML', 3);
-
-/**
- * Used to specify that we wish a lowercased string to be returned
- * @see tag_normal()
- */
-define('TAG_CASE_LOWER', 0);
-/**
- * Used to specify that we do not wish the case of the returned string to change
- * @see tag_normal()
- */
-define('TAG_CASE_ORIGINAL', 1);
-
-/**
- * Used to specify that we want all related tags returned, no matter how they are related.
- * @see tag_get_related_tags()
- */
-define('TAG_RELATED_ALL', 0);
-/**
- * Used to specify that we only want back tags that were manually related.
- * @see tag_get_related_tags()
- */
-define('TAG_RELATED_MANUAL', 1);
-/**
- * Used to specify that we only want back tags where the relationship was automatically correlated.
- * @see tag_get_related_tags()
- */
-define('TAG_RELATED_CORRELATED', 2);
-
-///////////////////////////////////////////////////////
-/////////////////// PUBLIC TAG API ////////////////////
-
-/// Functions for settings tags //////////////////////
-
-/**
- * Set the tags assigned to a record. This overwrites the current tags.
- *
- * This function is meant to be fed the string coming up from the user interface, which contains all tags assigned to a record.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $record_type the type of record to tag ('post' for blogs, 'user' for users, 'tag' for tags, etc.)
- * @param int $record_id the id of the record to tag
- * @param array $tags the array of tags to set on the record. If given an empty array, all tags will be removed.
- * @param string|null $component the component that was tagged
- * @param int|null $contextid the context id of where this tag was assigned
- * @return bool|null
- */
-function tag_set($record_type, $record_id, $tags, $component = null, $contextid = null) {
-
- static $in_recursion_semaphore = false; // this is to prevent loops when tagging a tag
-
- if ( $record_type == 'tag' && !$in_recursion_semaphore) {
- $current_tagged_tag_name = tag_get_name($record_id);
- }
-
- $tags_ids = tag_get_id($tags, TAG_RETURN_ARRAY); // force an array, even if we only have one tag.
- $cleaned_tags = tag_normalize($tags);
- //echo 'tags-in-tag_set'; var_dump($tags); var_dump($tags_ids); var_dump($cleaned_tags);
-
- $current_ids = tag_get_tags_ids($record_type, $record_id);
- //var_dump($current_ids);
-
- // for data coherence reasons, it's better to remove deleted tags
- // before adding new data: ordering could be duplicated.
- foreach($current_ids as $current_id) {
- if (!in_array($current_id, $tags_ids)) {
- tag_delete_instance($record_type, $record_id, $current_id);
- if ( $record_type == 'tag' && !$in_recursion_semaphore) {
- // if we are removing a tag-on-a-tag (manually related tag),
- // we need to remove the opposite relationship as well.
- tag_delete_instance('tag', $current_id, $record_id);
- }
- }
- }
-
- if (empty($tags)) {
- return true;
- }
-
- foreach($tags as $ordering => $tag) {
- $tag = trim($tag);
- if (!$tag) {
- continue;
- }
-
- $clean_tag = $cleaned_tags[$tag];
- $tag_current_id = $tags_ids[$clean_tag];
-
- if ( is_null($tag_current_id) ) {
- // create new tags
- //echo "call to add tag $tag\n";
- $new_tag = tag_add($tag);
- $tag_current_id = $new_tag[$clean_tag];
- }
-
- tag_assign($record_type, $record_id, $tag_current_id, $ordering, 0, $component, $contextid);
-
- // if we are tagging a tag (adding a manually-assigned related tag), we
- // need to create the opposite relationship as well.
- if ( $record_type == 'tag' && !$in_recursion_semaphore) {
- $in_recursion_semaphore = true;
- tag_set_add('tag', $tag_current_id, $current_tagged_tag_name, $component, $contextid);
- $in_recursion_semaphore = false;
- }
- }
-}
-
-/**
- * Adds a tag to a record, without overwriting the current tags.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $record_type the type of record to tag ('post' for blogs, 'user' for users, etc.)
- * @param int $record_id the id of the record to tag
- * @param string $tag the tag to add
- * @param string|null $component the component that was tagged
- * @param int|null $contextid the context id of where this tag was assigned
- * @return bool|null
- */
-function tag_set_add($record_type, $record_id, $tag, $component = null, $contextid = null) {
-
- $new_tags = array();
- foreach( tag_get_tags($record_type, $record_id) as $current_tag ) {
- $new_tags[] = $current_tag->rawname;
- }
- $new_tags[] = $tag;
-
- return tag_set($record_type, $record_id, $new_tags, $component, $contextid);
-}
-
-/**
- * Removes a tag from a record, without overwriting other current tags.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $record_type the type of record to tag ('post' for blogs, 'user' for users, etc.)
- * @param int $record_id the id of the record to tag
- * @param string $tag the tag to delete
- * @param string|null $component the component that was tagged
- * @param int|null $contextid the context id of where this tag was assigned
- * @return bool|null
- */
-function tag_set_delete($record_type, $record_id, $tag, $component = null, $contextid = null) {
-
- $new_tags = array();
- foreach( tag_get_tags($record_type, $record_id) as $current_tag ) {
- if ($current_tag->name != $tag) { // Keep all tags but the one specified
- $new_tags[] = $current_tag->name;
- }
- }
-
- return tag_set($record_type, $record_id, $new_tags, $component, $contextid);
-}
-
-/**
- * Set the type of a tag. At this time (version 2.2) the possible values are 'default' or 'official'. Official tags will be
- * displayed separately "at tagging time" (while selecting the tags to apply to a record).
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $tagid tagid to modify
- * @param string $type either 'default' or 'official'
- * @return bool true on success, false otherwise
- */
-function tag_type_set($tagid, $type) {
- global $DB;
-
- if ($tag = $DB->get_record('tag', array('id' => $tagid), 'id, userid, name, rawname')) {
- $tag->tagtype = $type;
- $tag->timemodified = time();
- $DB->update_record('tag', $tag);
-
- $event = \core\event\tag_updated::create(array(
- 'objectid' => $tag->id,
- 'relateduserid' => $tag->userid,
- 'context' => context_system::instance(),
- 'other' => array(
- 'name' => $tag->name,
- 'rawname' => $tag->rawname
- )
- ));
- $event->trigger();
-
- return true;
- }
- return false;
-}
-
-/**
- * Set the description of a tag
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param int $tagid the id of the tag
- * @param string $description the tag's description string to be set
- * @param int $descriptionformat the moodle text format of the description
- * {@link http://docs.moodle.org/dev/Text_formats_2.0#Database_structure}
- * @return bool true on success, false otherwise
- */
-function tag_description_set($tagid, $description, $descriptionformat) {
- global $DB;
-
- if ($tag = $DB->get_record('tag', array('id' => $tagid), 'id, userid, name, rawname')) {
- $tag->description = $description;
- $tag->descriptionformat = $descriptionformat;
- $tag->timemodified = time();
- $DB->update_record('tag', $tag);
-
- $event = \core\event\tag_updated::create(array(
- 'objectid' => $tag->id,
- 'relateduserid' => $tag->userid,
- 'context' => context_system::instance(),
- 'other' => array(
- 'name' => $tag->name,
- 'rawname' => $tag->rawname
- )
- ));
- $event->trigger();
-
- return true;
- }
-
- return false;
-}
-
-
-
-
-
-
-/// Functions for getting information about tags //////
-
-/**
- * Simple function to just return a single tag object when you know the name or something
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $field which field do we use to identify the tag: id, name or rawname
- * @param string $value the required value of the aforementioned field
- * @param string $returnfields which fields do we want returned. This is a comma seperated string containing any combination of
- * 'id', 'name', 'rawname' or '*' to include all fields.
- * @return mixed tag object
- */
-function tag_get($field, $value, $returnfields='id, name, rawname') {
- global $DB;
-
- if ($field == 'name') {
- $value = core_text::strtolower($value); // To cope with input that might just be wrong case
- }
- return $DB->get_record('tag', array($field=>$value), $returnfields);
-}
-
-
-/**
- * Get the array of db record of tags associated to a record (instances). Use {@see tag_get_tags_csv()} if you wish to get the same
- * data in a comma-separated string, for instances such as needing to simply display a list of tags to the end user. This should
- * really be called tag_get_tag_instances().
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $record_type the record type for which we want to get the tags
- * @param int $record_id the record id for which we want to get the tags
- * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned.
- * @param int $userid (optional) only required for course tagging
- * @return array the array of tags
- */
-function tag_get_tags($record_type, $record_id, $type=null, $userid=0) {
- global $CFG, $DB;
-
- $params = array();
-
- if ($type) {
- $sql_type = "AND tg.tagtype = :type";
- $params['type'] = $type;
- } else {
- $sql_type = '';
- }
-
- $u = null;
- if ($userid) {
- $u = "AND ti.tiuserid = :userid ";
- $params['userid'] = $userid;
- }
-
- $sql = "SELECT ti.id AS taginstanceid, tg.id, tg.tagtype, tg.name, tg.rawname, tg.flag, ti.ordering
- FROM {tag_instance} ti
- JOIN {tag} tg ON tg.id = ti.tagid
- WHERE ti.itemtype = :recordtype AND ti.itemid = :recordid $u $sql_type
- ORDER BY ti.ordering ASC";
- $params['recordtype'] = $record_type;
- $params['recordid'] = $record_id;
-
- // if the fields in this query are changed, you need to do the same changes in tag_get_correlated_tags
- return $DB->get_records_sql($sql, $params);
- // This version of the query, reversing the ON clause, "correctly" returns
- // a row with NULL values for instances that are still in the DB even though
- // the tag has been deleted. This shouldn't happen, but if it did, using
- // this query could help "clean it up". This causes bugs at this time.
- //$tags = $DB->get_records_sql("SELECT ti.tagid, tg.tagtype, tg.name, tg.rawname, tg.flag, ti.ordering ".
- // "FROM {tag_instance} ti LEFT JOIN {tag} tg ON ti.tagid = tg.id ".
- // "WHERE ti.itemtype = '{$record_type}' AND ti.itemid = '{$record_id}' {$type} ".
- // "ORDER BY ti.ordering ASC");
-}
-
-/**
- * Get the array of tags display names, indexed by id.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $record_type the record type for which we want to get the tags
- * @param int $record_id the record id for which we want to get the tags
- * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned.
- * @return array the array of tags (with the value returned by tag_display_name), indexed by id
- */
-function tag_get_tags_array($record_type, $record_id, $type=null) {
- $tags = array();
- foreach(tag_get_tags($record_type, $record_id, $type) as $tag) {
- $tags[$tag->id] = tag_display_name($tag);
- }
- return $tags;
-}
-
-/**
- * Get a comma-separated string of tags associated to a record. Use {@see tag_get_tags()} to get the same information in an array.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $record_type the record type for which we want to get the tags
- * @param int $record_id the record id for which we want to get the tags
- * @param int $html either TAG_RETURN_HTML or TAG_RETURN_TEXT, depending on the type of output desired
- * @param string $type either 'official' or 'default', if null, all tags are returned
- * @return string the comma-separated list of tags.
- */
-function tag_get_tags_csv($record_type, $record_id, $html=TAG_RETURN_HTML, $type=null) {
- global $CFG;
-
- $tags_names = array();
- foreach(tag_get_tags($record_type, $record_id, $type) as $tag) {
- if ($html == TAG_RETURN_TEXT) {
- $tags_names[] = tag_display_name($tag, TAG_RETURN_TEXT);
- } else { // TAG_RETURN_HTML
- $tags_names[] = ''. tag_display_name($tag) .'';
- }
- }
- return implode(', ', $tags_names);
-}
-
-/**
- * Get an array of tag ids associated to a record.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @todo MDL-31150 Update ordering property
- * @param string $record_type the record type for which we want to get the tags
- * @param int $record_id the record id for which we want to get the tags
- * @return array tag ids, indexed and sorted by 'ordering'
- */
-function tag_get_tags_ids($record_type, $record_id) {
- $tag_ids = array();
- foreach (tag_get_tags($record_type, $record_id) as $tag) {
- if ( array_key_exists($tag->ordering, $tag_ids) ) {
- // until we can add a unique constraint, in table tag_instance,
- // on (itemtype, itemid, ordering), this is needed to prevent a bug
- // TODO MDL-31150 modify database in 2.0
- $tag->ordering++;
- }
- $tag_ids[$tag->ordering] = $tag->id;
- }
- ksort($tag_ids);
- return $tag_ids;
-}
-
-/**
- * Returns the database ID of a set of tags.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @todo MDL-31152 Test the commented MDL-31152 todo in this function to see if it helps performance
- * without breaking anything.
- * @param mixed $tags one tag, or array of tags, to look for.
- * @param bool $return_value specify the type of the returned value. Either TAG_RETURN_OBJECT, or TAG_RETURN_ARRAY (default).
- * If TAG_RETURN_ARRAY is specified, an array will be returned even if only one tag was passed in $tags.
- * @return mixed tag-indexed array of ids (or objects, if second parameter is TAG_RETURN_OBJECT), or only an int, if only one tag
- * is given *and* the second parameter is null. No value for a key means the tag wasn't found.
- */
-function tag_get_id($tags, $return_value=null) {
- global $CFG, $DB;
-
- static $tag_id_cache = array();
-
- $return_an_int = false;
- if (!is_array($tags)) {
- if(is_null($return_value) || $return_value == TAG_RETURN_OBJECT) {
- $return_an_int = true;
- }
- $tags = array($tags);
- }
-
- $result = array();
-
- //TODO MDL-31152 test this and see if it helps performance without breaking anything
- //foreach($tags as $key => $tag) {
- // $clean_tag = core_text::strtolower($tag);
- // if ( array_key_exists($clean_tag), $tag_id_cache) ) {
- // $result[$clean_tag] = $tag_id_cache[$clean_tag];
- // $tags[$key] = ''; // prevent further processing for this one.
- // }
- //}
-
- $tags = array_values(tag_normalize($tags));
- foreach($tags as $key => $tag) {
- $tags[$key] = core_text::strtolower($tag);
- $result[core_text::strtolower($tag)] = null; // key must exists : no value for a key means the tag wasn't found.
- }
-
- if (empty($tags)) {
- return array();
- }
-
- list($tag_string, $params) = $DB->get_in_or_equal($tags);
-
- $rs = $DB->get_recordset_sql("SELECT * FROM {tag} WHERE name $tag_string ORDER BY name", $params);
- foreach ($rs as $record) {
- if ($return_value == TAG_RETURN_OBJECT) {
- $result[$record->name] = $record;
- } else { // TAG_RETURN_ARRAY
- $result[$record->name] = $record->id;
- }
- }
- $rs->close();
-
- if ($return_an_int) {
- return array_pop($result);
- }
-
- return $result;
-}
-
-
-/**
- * Returns tags related to a tag
- *
- * Related tags of a tag come from two sources:
- * - manually added related tags, which are tag_instance entries for that tag
- * - correlated tags, which are calculated
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $tagid is a single **normalized** tag name or the id of a tag
- * @param int $type the function will return either manually (TAG_RELATED_MANUAL) related tags or correlated
- * (TAG_RELATED_CORRELATED) tags. Default is TAG_RELATED_ALL, which returns everything.
- * @param int $limitnum (optional) return a subset comprising this many records, the default is 10
- * @return array an array of tag objects
- */
-function tag_get_related_tags($tagid, $type=TAG_RELATED_ALL, $limitnum=10) {
-
- $related_tags = array();
-
- if ( $type == TAG_RELATED_ALL || $type == TAG_RELATED_MANUAL) {
- //gets the manually added related tags
- $related_tags = tag_get_tags('tag', $tagid);
- }
-
- if ( $type == TAG_RELATED_ALL || $type == TAG_RELATED_CORRELATED ) {
- //gets the correlated tags
- $automatic_related_tags = tag_get_correlated($tagid);
- $related_tags = array_merge($related_tags, $automatic_related_tags);
- }
-
- // Remove duplicated tags (multiple instances of the same tag).
- $seen = array();
- foreach ($related_tags as $instance => $tag) {
- if (isset($seen[$tag->id])) {
- unset($related_tags[$instance]);
- } else {
- $seen[$tag->id] = 1;
- }
- }
-
- return array_slice($related_tags, 0 , $limitnum);
-}
-
-/**
- * Get a comma-separated list of tags related to another tag.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param array $related_tags the array returned by tag_get_related_tags
- * @param int $html either TAG_RETURN_HTML (default) or TAG_RETURN_TEXT : return html links, or just text.
- * @return string comma-separated list
- */
-function tag_get_related_tags_csv($related_tags, $html=TAG_RETURN_HTML) {
- global $CFG;
-
- $tags_names = array();
- foreach($related_tags as $tag) {
- if ( $html == TAG_RETURN_TEXT) {
- $tags_names[] = tag_display_name($tag, TAG_RETURN_TEXT);
- } else {
- // TAG_RETURN_HTML
- $tags_names[] = ''. tag_display_name($tag) .'';
- }
- }
-
- return implode(', ', $tags_names);
-}
-
-/**
- * Change the "value" of a tag, and update the associated 'name'.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param int $tagid the id of the tag to modify
- * @param string $newrawname the new rawname
- * @return bool true on success, false otherwise
- */
-function tag_rename($tagid, $newrawname) {
- global $COURSE, $DB;
-
- $norm = tag_normalize($newrawname, TAG_CASE_ORIGINAL);
- if (! $newrawname_clean = array_shift($norm) ) {
- return false;
- }
-
- if (! $newname_clean = core_text::strtolower($newrawname_clean)) {
- return false;
- }
-
- // Prevent the rename if a tag with that name already exists
- if ($existing = tag_get('name', $newname_clean, 'id, name, rawname')) {
- if ($existing->id != $tagid) { // Another tag already exists with this name
- return false;
- }
- }
-
- if ($tag = tag_get('id', $tagid, 'id, userid, name, rawname')) {
- // Store the name before we change it.
- $oldname = $tag->name;
-
- $tag->rawname = $newrawname_clean;
- $tag->name = $newname_clean;
- $tag->timemodified = time();
- $DB->update_record('tag', $tag);
-
- $event = \core\event\tag_updated::create(array(
- 'objectid' => $tag->id,
- 'relateduserid' => $tag->userid,
- 'context' => context_system::instance(),
- 'other' => array(
- 'name' => $newname_clean,
- 'rawname' => $newrawname_clean
- )
- ));
- $event->set_legacy_logdata(array($COURSE->id, 'tag', 'update', 'index.php?id='. $tag->id, $oldname . '->'. $tag->name));
- $event->trigger();
-
- return true;
- }
- return false;
-}
-
-
-/**
- * Delete one or more tag, and all their instances if there are any left.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param mixed $tagids one tagid (int), or one array of tagids to delete
- * @return bool true on success, false otherwise
- */
-function tag_delete($tagids) {
- global $DB;
-
- if (!is_array($tagids)) {
- $tagids = array($tagids);
- }
-
- // Use the tagids to create a select statement to be used later.
- list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids);
-
- // Store the tags and tag instances we are going to delete.
- $tags = $DB->get_records_select('tag', 'id ' . $tagsql, $tagparams);
- $taginstances = $DB->get_records_select('tag_instance', 'tagid ' . $tagsql, $tagparams);
-
- // Delete all the tag instances.
- $select = 'WHERE tagid ' . $tagsql;
- $sql = "DELETE FROM {tag_instance} $select";
- $DB->execute($sql, $tagparams);
-
- // Delete all the tag correlations.
- $sql = "DELETE FROM {tag_correlation} $select";
- $DB->execute($sql, $tagparams);
-
- // Delete all the tags.
- $select = 'WHERE id ' . $tagsql;
- $sql = "DELETE FROM {tag} $select";
- $DB->execute($sql, $tagparams);
-
- // Fire an event that these items were untagged.
- if ($taginstances) {
- // Save the system context in case the 'contextid' column in the 'tag_instance' table is null.
- $syscontextid = context_system::instance()->id;
- // Loop through the tag instances and fire a 'tag_removed'' event.
- foreach ($taginstances as $taginstance) {
- // We can not fire an event with 'null' as the contextid.
- if (is_null($taginstance->contextid)) {
- $taginstance->contextid = $syscontextid;
- }
-
- // Trigger tag removed event.
- $event = \core\event\tag_removed::create(array(
- 'objectid' => $taginstance->id,
- 'contextid' => $taginstance->contextid,
- 'other' => array(
- 'tagid' => $taginstance->tagid,
- 'tagname' => $tags[$taginstance->tagid]->name,
- 'tagrawname' => $tags[$taginstance->tagid]->rawname,
- 'itemid' => $taginstance->itemid,
- 'itemtype' => $taginstance->itemtype
- )
- ));
- $event->add_record_snapshot('tag_instance', $taginstance);
- $event->trigger();
- }
- }
-
- // Fire an event that these tags were deleted.
- if ($tags) {
- $context = context_system::instance();
- foreach ($tags as $tag) {
- // Delete all files associated with this tag
- $fs = get_file_storage();
- $files = $fs->get_area_files($context->id, 'tag', 'description', $tag->id);
- foreach ($files as $file) {
- $file->delete();
- }
-
- // Trigger an event for deleting this tag.
- $event = \core\event\tag_deleted::create(array(
- 'objectid' => $tag->id,
- 'relateduserid' => $tag->userid,
- 'context' => $context,
- 'other' => array(
- 'name' => $tag->name,
- 'rawname' => $tag->rawname
- )
- ));
- $event->add_record_snapshot('tag', $tag);
- $event->trigger();
- }
- }
-
- return true;
-}
-
-/**
- * Deletes all the tag instances given a component and an optional contextid.
- *
- * @param string $component
- * @param int $contextid if null, then we delete all tag instances for the $component
- */
-function tag_delete_instances($component, $contextid = null) {
- global $DB;
-
- $sql = "SELECT ti.*, t.name, t.rawname
- FROM {tag_instance} ti
- JOIN {tag} t
- ON ti.tagid = t.id ";
- if (is_null($contextid)) {
- $params = array('component' => $component);
- $sql .= "WHERE ti.component = :component";
- } else {
- $params = array('component' => $component, 'contextid' => $contextid);
- $sql .= "WHERE ti.component = :component
- AND ti.contextid = :contextid";
- }
- if ($taginstances = $DB->get_records_sql($sql, $params)) {
- // Now remove all the tag instances.
- $DB->delete_records('tag_instance',$params);
- // Save the system context in case the 'contextid' column in the 'tag_instance' table is null.
- $syscontextid = context_system::instance()->id;
- // Loop through the tag instances and fire an 'tag_removed' event.
- foreach ($taginstances as $taginstance) {
- // We can not fire an event with 'null' as the contextid.
- if (is_null($taginstance->contextid)) {
- $taginstance->contextid = $syscontextid;
- }
-
- // Trigger tag removed event.
- $event = \core\event\tag_removed::create(array(
- 'objectid' => $taginstance->id,
- 'contextid' => $taginstance->contextid,
- 'other' => array(
- 'tagid' => $taginstance->tagid,
- 'tagname' => $taginstance->name,
- 'tagrawname' => $taginstance->rawname,
- 'itemid' => $taginstance->itemid,
- 'itemtype' => $taginstance->itemtype
- )
- ));
- $event->add_record_snapshot('tag_instance', $taginstance);
- $event->trigger();
- }
- }
-}
-
-/**
- * Delete one instance of a tag. If the last instance was deleted, it will also delete the tag, unless its type is 'official'.
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $record_type the type of the record for which to remove the instance
- * @param int $record_id the id of the record for which to remove the instance
- * @param int $tagid the tagid that needs to be removed
- * @param int $userid (optional) the userid
- * @return bool true on success, false otherwise
- */
-function tag_delete_instance($record_type, $record_id, $tagid, $userid = null) {
- global $DB;
-
- if (is_null($userid)) {
- $taginstance = $DB->get_record('tag_instance', array('tagid' => $tagid, 'itemtype' => $record_type, 'itemid' => $record_id));
- } else {
- $taginstance = $DB->get_record('tag_instance', array('tagid' => $tagid, 'itemtype' => $record_type, 'itemid' => $record_id,
- 'tiuserid' => $userid));
- }
- if ($taginstance) {
- // Get the tag.
- $tag = $DB->get_record('tag', array('id' => $tagid));
-
- $DB->delete_records('tag_instance', array('id' => $taginstance->id));
-
- // We can not fire an event with 'null' as the contextid.
- if (is_null($taginstance->contextid)) {
- $taginstance->contextid = context_system::instance()->id;
- }
-
- // Trigger tag removed event.
- $event = \core\event\tag_removed::create(array(
- 'objectid' => $taginstance->id,
- 'contextid' => $taginstance->contextid,
- 'other' => array(
- 'tagid' => $tag->id,
- 'tagname' => $tag->name,
- 'tagrawname' => $tag->rawname,
- 'itemid' => $taginstance->itemid,
- 'itemtype' => $taginstance->itemtype
- )
- ));
- $event->add_record_snapshot('tag_instance', $taginstance);
- $event->trigger();
-
- // If there are no other instances of the tag then consider deleting the tag as well.
- if (!$DB->record_exists('tag_instance', array('tagid' => $tagid))) {
- // If the tag is a personal tag then delete it - don't delete official tags.
- if ($tag->tagtype == 'default') {
- tag_delete($tagid);
- }
- }
- } else {
- return false;
- }
-
- return true;
-}
-
-
-/**
- * Function that returns the name that should be displayed for a specific tag
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param object $tagobject a line out of tag table, as returned by the adobd functions
- * @param int $html TAG_RETURN_HTML (default) will return htmlspecialchars encoded string, TAG_RETURN_TEXT will not encode.
- * @return string
- */
-function tag_display_name($tagobject, $html=TAG_RETURN_HTML) {
- global $CFG;
-
- if (!isset($tagobject->name)) {
- return '';
- }
-
- if (empty($CFG->keeptagnamecase)) {
- //this is the normalized tag name
- $tagname = core_text::strtotitle($tagobject->name);
- } else {
- //original casing of the tag name
- $tagname = $tagobject->rawname;
- }
-
- // clean up a bit just in case the rules change again
- $tagname = clean_param($tagname, PARAM_TAG);
-
- if ($html == TAG_RETURN_TEXT) {
- return $tagname;
- } else { // TAG_RETURN_HTML
- return htmlspecialchars($tagname);
- }
-}
-
-/**
- * Find all records tagged with a tag of a given type ('post', 'user', etc.)
- *
- * @package core_tag
- * @category tag
- * @access public
- * @param string $tag tag to look for
- * @param string $type type to restrict search to. If null, every matching record will be returned
- * @param int $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point.
- * @param int $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records.
- * @return array of matching objects, indexed by record id, from the table containing the type requested
- */
-function tag_find_records($tag, $type, $limitfrom='', $limitnum='') {
- global $CFG, $DB;
-
- if (!$tag || !$type) {
- return array();
- }
-
- $tagid = tag_get_id($tag);
-
- $query = "SELECT it.*
- FROM {".$type."} it INNER JOIN {tag_instance} tt ON it.id = tt.itemid
- WHERE tt.itemtype = ? AND tt.tagid = ?";
- $params = array($type, $tagid);
-
- return $DB->get_records_sql($query, $params, $limitfrom, $limitnum);
-}
-
-
-
-
-///////////////////////////////////////////////////////
-/////////////////// PRIVATE TAG API ///////////////////
-
-/**
- * Adds one or more tag in the database. This function should not be called directly : you should
- * use tag_set.
- *
- * @package core_tag
- * @access private
- * @param mixed $tags one tag, or an array of tags, to be created
- * @param string $type type of tag to be created ("default" is the default value and "official" is the only other supported
- * value at this time). An official tag is kept even if there are no records tagged with it.
- * @return array $tags ids indexed by their lowercase normalized names. Any boolean false in the array indicates an error while
- * adding the tag.
- */
-function tag_add($tags, $type="default") {
- global $USER, $DB;
-
- if (!is_array($tags)) {
- $tags = array($tags);
- }
-
- $tag_object = new StdClass;
- $tag_object->tagtype = $type;
- $tag_object->userid = $USER->id;
- $tag_object->timemodified = time();
-
- $clean_tags = tag_normalize($tags, TAG_CASE_ORIGINAL);
-
- $tags_ids = array();
- foreach($clean_tags as $tag) {
- $tag = trim($tag);
- if (!$tag) {
- $tags_ids[$tag] = false;
- } else {
- // note that the difference between rawname and name is only
- // capitalization : the rawname is NOT the same at the rawtag.
- $tag_object->rawname = $tag;
- $tag_name_lc = core_text::strtolower($tag);
- $tag_object->name = $tag_name_lc;
- //var_dump($tag_object);
- $tags_ids[$tag_name_lc] = $DB->insert_record('tag', $tag_object);
-
- $event = \core\event\tag_created::create(array(
- 'objectid' => $tags_ids[$tag_name_lc],
- 'relateduserid' => $tag_object->userid,
- 'context' => context_system::instance(),
- 'other' => array(
- 'name' => $tag_object->name,
- 'rawname' => $tag_object->rawname
- )
- ));
- $event->trigger();
- }
- }
-
- return $tags_ids;
-}
-
-/**
- * Assigns a tag to a record; if the record already exists, the time and ordering will be updated.
- *
- * @package core_tag
- * @access private
- * @param string $record_type the type of the record that will be tagged
- * @param int $record_id the id of the record that will be tagged
- * @param string $tagid the tag id to set on the record.
- * @param int $ordering the order of the instance for this record
- * @param int $userid (optional) only required for course tagging
- * @param string|null $component the component that was tagged
- * @param int|null $contextid the context id of where this tag was assigned
- * @return bool true on success, false otherwise
- */
-function tag_assign($record_type, $record_id, $tagid, $ordering, $userid = 0, $component = null, $contextid = null) {
- global $DB;
-
- if ($component === null || $contextid === null) {
- debugging('You should specify the component and contextid of the item being tagged in your call to tag_assign.',
- DEBUG_DEVELOPER);
- }
-
- // Get the tag.
- $tag = $DB->get_record('tag', array('id' => $tagid), 'name, rawname', MUST_EXIST);
-
- if ( $tag_instance_object = $DB->get_record('tag_instance', array('tagid'=>$tagid, 'itemtype'=>$record_type, 'itemid'=>$record_id, 'tiuserid'=>$userid), 'id')) {
- $tag_instance_object->ordering = $ordering;
- $tag_instance_object->timemodified = time();
-
- $DB->update_record('tag_instance', $tag_instance_object);
- } else {
- $tag_instance_object = new StdClass;
- $tag_instance_object->tagid = $tagid;
- $tag_instance_object->component = $component;
- $tag_instance_object->itemid = $record_id;
- $tag_instance_object->itemtype = $record_type;
- $tag_instance_object->contextid = $contextid;
- $tag_instance_object->ordering = $ordering;
- $tag_instance_object->timecreated = time();
- $tag_instance_object->timemodified = $tag_instance_object->timecreated;
- $tag_instance_object->tiuserid = $userid;
-
- $tag_instance_object->id = $DB->insert_record('tag_instance', $tag_instance_object);
- }
-
- // We can not fire an event with 'null' as the contextid.
- if (is_null($contextid)) {
- $contextid = context_system::instance()->id;
- }
-
- // Trigger tag added event.
- $event = \core\event\tag_added::create(array(
- 'objectid' => $tag_instance_object->id,
- 'contextid' => $contextid,
- 'other' => array(
- 'tagid' => $tagid,
- 'tagname' => $tag->name,
- 'tagrawname' => $tag->rawname,
- 'itemid' => $record_id,
- 'itemtype' => $record_type
- )
- ));
- $event->trigger();
-
- return true;
-}
-
-/**
- * Clean up the tag tables, making sure all tagged object still exists.
- *
- * This should normally not be necessary, but in case related tags are not deleted when the tagged record is removed, this should be
- * done once in a while, perhaps on an occasional cron run. On a site with lots of tags, this could become an expensive function to
- * call: don't run at peak time.
- *
- * @package core_tag
- * @access private
- * @todo MDL-31212 Update tag cleanup sql so that it supports multiple types of tags
- */
-function tag_cleanup() {
- global $DB;
-
- // Get ids to delete from instances where the tag has been deleted. This should never happen apparently.
- $sql = "SELECT ti.id
- FROM {tag_instance} ti
- LEFT JOIN {tag} t ON t.id = ti.tagid
- WHERE t.id IS null";
- $tagids = $DB->get_records_sql($sql);
- $tagarray = array();
- foreach ($tagids as $tagid) {
- $tagarray[] = $tagid->id;
- }
-
- // Next get ids from instances that have an owner that has been deleted.
- $sql = "SELECT ti.id
- FROM {tag_instance} ti, {user} u
- WHERE ti.itemid = u.id
- AND ti.itemtype = 'user'
- AND u.deleted = 1";
- $tagids = $DB->get_records_sql($sql);
- foreach ($tagids as $tagid) {
- $tagarray[] = $tagid->id;
- }
-
- // Get the other itemtypes.
- $sql = "SELECT itemtype
- FROM {tag_instance}
- WHERE itemtype <> 'user'
- GROUP BY itemtype";
- $tagitemtypes = $DB->get_records_sql($sql);
- foreach ($tagitemtypes as $key => $notused) {
- $sql = 'SELECT ti.id
- FROM {tag_instance} ti
- LEFT JOIN {' . $key . '} it ON it.id = ti.itemid
- WHERE it.id IS null
- AND ti.itemtype = \'' . $key . '\'';
- $tagids = $DB->get_records_sql($sql);
- foreach ($tagids as $tagid) {
- $tagarray[] = $tagid->id;
- }
- }
-
- // Get instances for each of the ids to be deleted.
- if (count($tagarray) > 0) {
- list($sqlin, $params) = $DB->get_in_or_equal($tagarray);
- $sql = "SELECT ti.*, COALESCE(t.name, 'deleted') AS name, COALESCE(t.rawname, 'deleted') AS rawname
- FROM {tag_instance} ti
- LEFT JOIN {tag} t ON t.id = ti.tagid
- WHERE ti.id $sqlin";
- $instances = $DB->get_records_sql($sql, $params);
- tag_bulk_delete_instances($instances);
- }
-
- // TODO MDL-31212 this will only clean tags of type 'default'. This is good as
- // it won't delete 'official' tags, but the day we get more than two
- // types, we need to fix this.
- $unused_tags = $DB->get_recordset_sql("SELECT tg.id
- FROM {tag} tg
- WHERE tg.tagtype = 'default'
- AND NOT EXISTS (
- SELECT 'x'
- FROM {tag_instance} ti
- WHERE ti.tagid = tg.id
- )");
-
- // cleanup tags
- foreach ($unused_tags as $unused_tag) {
- tag_delete($unused_tag->id);
- //debugging('deleting unused tag #'. $unused_tag->id, DEBUG_DEVELOPER);
- }
- $unused_tags->close();
-}
-
-/**
- * This function will delete numerous tag instances efficiently.
- * This removes tag instances only. It doesn't check to see if it is the last use of a tag.
- *
- * @param array $instances An array of tag instance objects with the addition of the tagname and tagrawname
- * (used for recording a delete event).
- */
-function tag_bulk_delete_instances($instances) {
- global $DB;
-
- $instanceids = array();
- foreach ($instances as $instance) {
- $instanceids[] = $instance->id;
- }
-
- // This is a multi db compatible method of creating the correct sql when using the 'IN' value.
- // $insql is the sql statement, $params are the id numbers.
- list($insql, $params) = $DB->get_in_or_equal($instanceids);
- $sql = 'id ' . $insql;
- $DB->delete_records_select('tag_instance', $sql, $params);
-
- // Now go through and record each tag individually with the event system.
- foreach ($instances as $instance) {
- // Trigger tag removed event (i.e. The tag instance has been removed).
- $event = \core\event\tag_removed::create(array(
- 'objectid' => $instance->id,
- 'contextid' => $instance->contextid,
- 'other' => array(
- 'tagid' => $instance->tagid,
- 'tagname' => $instance->name,
- 'tagrawname' => $instance->rawname,
- 'itemid' => $instance->itemid,
- 'itemtype' => $instance->itemtype
- )
- ));
- unset($instance->name);
- unset($instance->rawname);
- $event->add_record_snapshot('tag_instance', $instance);
- $event->trigger();
- }
-}
-
-/**
- * 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.
- *
- * @package core_tag
- * @access private
- * @param int $mincorrelation Only tags with more than $mincorrelation correlations will be identified.
- */
-function tag_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_instance} tb ON (ta.itemtype = tb.itemtype AND ta.itemid = tb.itemid AND ta.tagid <> tb.tagid)
- GROUP BY ta.tagid, tb.tagid
- HAVING COUNT(*) > :mincorrelation
- ) pairs
- LEFT JOIN {tag_correlation} co ON co.tagid = pairs.tagid
- ORDER BY pairs.tagid ASC, pairs.ocurrences DESC, pairs.correlation ASC';
- $rs = $DB->get_recordset_sql($sql, array('mincorrelation' => $mincorrelation));
-
- // Set up an empty tag correlation object
- $tagcorrelation = new stdClass;
- $tagcorrelation->id = null;
- $tagcorrelation->tagid = null;
- $tagcorrelation->correlatedtags = array();
-
- // We store each correlation id in this array so we can remove any correlations
- // that no longer exist.
- $correlations = array();
-
- // Iterate each row of the result set and build them into tag correlations.
- // We add all of a tag's correlations to $tagcorrelation->correlatedtags[]
- // then save the $tagcorrelation object
- foreach ($rs as $row) {
- if ($row->tagid != $tagcorrelation->tagid) {
- // The tag id has changed so we have all of the correlations for this tag
- $tagcorrelationid = tag_process_computed_correlation($tagcorrelation);
- if ($tagcorrelationid) {
- $correlations[] = $tagcorrelationid;
- }
- // Now we reset the tag correlation object so we can reuse it and set it
- // up for the current record.
- $tagcorrelation = new stdClass;
- $tagcorrelation->id = $row->correlationid;
- $tagcorrelation->tagid = $row->tagid;
- $tagcorrelation->correlatedtags = array();
- }
- //Save the correlation on the tag correlation object
- $tagcorrelation->correlatedtags[] = $row->correlation;
- }
- // Update the current correlation after the last record.
- $tagcorrelationid = tag_process_computed_correlation($tagcorrelation);
- if ($tagcorrelationid) {
- $correlations[] = $tagcorrelationid;
- }
-
-
- // Close the recordset
- $rs->close();
-
- // Remove any correlations that weren't just identified
- if (empty($correlations)) {
- //there are no tag correlations
- $DB->delete_records('tag_correlation');
- } else {
- list($sql, $params) = $DB->get_in_or_equal($correlations, SQL_PARAMS_NAMED, 'param0000', false);
- $DB->delete_records_select('tag_correlation', 'id '.$sql, $params);
- }
-}
-
-/**
- * This function processes a tag correlation and makes changes in the database as required.
- *
- * The tag correlation object needs have both a tagid property and a correlatedtags property that is an array.
- *
- * @package core_tag
- * @access private
- * @param stdClass $tagcorrelation
- * @return int/bool The id of the tag correlation that was just processed or false.
- */
-function tag_process_computed_correlation(stdClass $tagcorrelation) {
- global $DB;
-
- // You must provide a tagid and correlatedtags must be set and be an array
- if (empty($tagcorrelation->tagid) || !isset($tagcorrelation->correlatedtags) || !is_array($tagcorrelation->correlatedtags)) {
- return false;
- }
-
- $tagcorrelation->correlatedtags = join(',', $tagcorrelation->correlatedtags);
- if (!empty($tagcorrelation->id)) {
- // The tag correlation already exists so update it
- $DB->update_record('tag_correlation', $tagcorrelation);
- } else {
- // This is a new correlation to insert
- $tagcorrelation->id = $DB->insert_record('tag_correlation', $tagcorrelation);
- }
- return $tagcorrelation->id;
-}
-
-/**
- * Tasks that should be performed at cron time
- *
- * @package core_tag
- * @access private
- */
-function tag_cron() {
- tag_compute_correlations();
- tag_cleanup();
-}
-
-/**
- * Search for tags with names that match some text
- *
- * @package core_tag
- * @access private
- * @param string $text escaped string that the tag names will be matched against
- * @param bool $ordered If true, tags are ordered by their popularity. If false, no ordering.
- * @param int/string $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point.
- * @param int/string $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records.
- * @return array/boolean an array of objects, or false if no records were found or an error occured.
- */
-function tag_find_tags($text, $ordered=true, $limitfrom='', $limitnum='') {
- global $DB;
-
- $norm = tag_normalize($text, TAG_CASE_LOWER);
- $text = array_shift($norm);
-
- if ($ordered) {
- $query = "SELECT tg.id, tg.name, tg.rawname, COUNT(ti.id) AS count
- FROM {tag} tg LEFT JOIN {tag_instance} ti ON tg.id = ti.tagid
- WHERE tg.name LIKE ?
- GROUP BY tg.id, tg.name, tg.rawname
- ORDER BY count DESC";
- } else {
- $query = "SELECT tg.id, tg.name, tg.rawname
- FROM {tag} tg
- WHERE tg.name LIKE ?";
- }
- $params = array("%{$text}%");
- return $DB->get_records_sql($query, $params, $limitfrom , $limitnum);
-}
-
-/**
- * Get the name of a tag
- *
- * @package core_tag
- * @access private
- * @param mixed $tagids the id of the tag, or an array of ids
- * @return mixed string name of one tag, or id-indexed array of strings
- */
-function tag_get_name($tagids) {
- global $DB;
-
- if (!is_array($tagids)) {
- if ($tag = $DB->get_record('tag', array('id'=>$tagids))) {
- return $tag->name;
- }
- return false;
- }
-
- $tag_names = array();
- foreach($DB->get_records_list('tag', 'id', $tagids) as $tag) {
- $tag_names[$tag->id] = $tag->name;
- }
-
- return $tag_names;
-}
-
-/**
- * Returns the correlated tags of a tag, retrieved from the tag_correlation table. Make sure cron runs, otherwise the table will be
- * empty and this function won't return anything.
- *
- * Correlated tags are calculated in cron based on existing tag instances.
- *
- * This function will return as many entries as there are existing tag instances,
- * which means that there will be duplicates for each tag.
- *
- * If you need only one record for each correlated tag please call:
- * tag_get_related_tags($tag_id, TAG_RELATED_CORRELATED);
- *
- * @package core_tag
- * @access private
- * @param int $tag_id is a single tag id
- * @param int $notused this argument is no longer used
- * @return array an array of tag objects or an empty if no correlated tags are found
- */
-function tag_get_correlated($tag_id, $notused = null) {
- global $DB;
-
- $tag_correlation = $DB->get_record('tag_correlation', array('tagid'=>$tag_id));
-
- if (!$tag_correlation || empty($tag_correlation->correlatedtags)) {
- return array();
- }
-
- // this is (and has to) return the same fields as the query in tag_get_tags
- $sql = "SELECT ti.id AS taginstanceid, tg.id, tg.tagtype, tg.name, tg.rawname, tg.flag, ti.ordering
- FROM {tag} tg
- INNER JOIN {tag_instance} ti ON tg.id = ti.tagid
- WHERE tg.id IN ({$tag_correlation->correlatedtags})
- ORDER BY ti.ordering ASC";
- return $DB->get_records_sql($sql);
-}
-
-/**
- * Function that normalizes a list of tag names.
- *
- * @package core_tag
- * @access private
- * @param array/string $rawtags array of tags, or a single tag.
- * @param int $case case to use for returned value (default: lower case). Either TAG_CASE_LOWER (default) or TAG_CASE_ORIGINAL
- * @return array lowercased normalized tags, indexed by the normalized tag, in the same order as the original array.
- * (Eg: 'Banana' => 'banana').
- */
-function tag_normalize($rawtags, $case = TAG_CASE_LOWER) {
-
- // cache normalized tags, to prevent costly repeated calls to clean_param
- static $cleaned_tags_lc = array(); // lower case - use for comparison
- static $cleaned_tags_mc = array(); // mixed case - use for saving to database
-
- if ( !is_array($rawtags) ) {
- $rawtags = array($rawtags);
- }
-
- $result = array();
- foreach($rawtags as $rawtag) {
- $rawtag = trim($rawtag);
- if (!$rawtag) {
- continue;
- }
- if ( !array_key_exists($rawtag, $cleaned_tags_lc) ) {
- $cleaned_tags_lc[$rawtag] = core_text::strtolower( clean_param($rawtag, PARAM_TAG) );
- $cleaned_tags_mc[$rawtag] = clean_param($rawtag, PARAM_TAG);
- }
- if ( $case == TAG_CASE_LOWER ) {
- $result[$rawtag] = $cleaned_tags_lc[$rawtag];
- } else { // TAG_CASE_ORIGINAL
- $result[$rawtag] = $cleaned_tags_mc[$rawtag];
- }
- }
-
- return $result;
-}
-
-/**
- * Count how many records are tagged with a specific tag.
- *
- * @package core_tag
- * @access private
- * @param string $record_type record to look for ('post', 'user', etc.)
- * @param int $tagid is a single tag id
- * @return int number of mathing tags.
- */
-function tag_record_count($record_type, $tagid) {
- global $DB;
- return $DB->count_records('tag_instance', array('itemtype'=>$record_type, 'tagid'=>$tagid));
-}
-
-/**
- * Determine if a record is tagged with a specific tag
- *
- * @package core_tag
- * @access private
- * @param string $record_type the record type to look for
- * @param int $record_id the record id to look for
- * @param string $tag a tag name
- * @return bool/int true if it is tagged, 0 (false) otherwise
- */
-function tag_record_tagged_with($record_type, $record_id, $tag) {
- global $DB;
- if ($tagid = tag_get_id($tag)) {
- return $DB->count_records('tag_instance', array('itemtype'=>$record_type, 'itemid'=>$record_id, 'tagid'=>$tagid));
- } else {
- return 0; // tag doesn't exist
- }
-}
-
-/**
- * Flag a tag as inappropriate.
- *
- * @param int|array $tagids a single tagid, or an array of tagids
- */
-function tag_set_flag($tagids) {
- global $DB;
-
- $tagids = (array) $tagids;
-
- // Use the tagids to create a select statement to be used later.
- list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids, SQL_PARAMS_NAMED);
-
- // Update all the tags to flagged.
- $sql = "UPDATE {tag}
- SET flag = flag + 1, timemodified = :time
- WHERE id $tagsql";
-
- // Update all the tags.
- $DB->execute($sql, array_merge(array('time' => time()), $tagparams));
-
- // Get all the tags.
- if ($tags = $DB->get_records_select('tag', 'id '. $tagsql, $tagparams, 'id ASC')) {
- // Loop through and fire an event for each tag that it was flagged.
- foreach ($tags as $tag) {
- $event = \core\event\tag_flagged::create(array(
- 'objectid' => $tag->id,
- 'relateduserid' => $tag->userid,
- 'context' => context_system::instance(),
- 'other' => array(
- 'name' => $tag->name,
- 'rawname' => $tag->rawname
- )
-
- ));
- $event->add_record_snapshot('tag', $tag);
- $event->trigger();
- }
- }
-}
-
-/**
- * Remove the inappropriate flag on a tag.
- *
- * @param int|array $tagids a single tagid, or an array of tagids
- */
-function tag_unset_flag($tagids) {
- global $DB;
-
- $tagids = (array) $tagids;
-
- // Use the tagids to create a select statement to be used later.
- list($tagsql, $tagparams) = $DB->get_in_or_equal($tagids, SQL_PARAMS_NAMED);
-
- // Update all the tags to unflagged.
- $sql = "UPDATE {tag}
- SET flag = 0, timemodified = :time
- WHERE id $tagsql";
-
- // Update all the tags.
- $DB->execute($sql, array_merge(array('time' => time()), $tagparams));
-
- // Get all the tags.
- if ($tags = $DB->get_records_select('tag', 'id '. $tagsql, $tagparams, 'id ASC')) {
- // Loop through and fire an event for each tag that it was unflagged.
- foreach ($tags as $tag) {
- $event = \core\event\tag_unflagged::create(array(
- 'objectid' => $tag->id,
- 'relateduserid' => $tag->userid,
- 'context' => context_system::instance(),
- 'other' => array(
- 'name' => $tag->name,
- 'rawname' => $tag->rawname
- )
- ));
- $event->add_record_snapshot('tag', $tag);
- $event->trigger();
- }
- }
-}
-
/**
* Return a list of page types
*
* @package core_tag
- * @access private
* @param string $pagetype current page type
* @param stdClass $parentcontext Block's parent context
* @param stdClass $currentcontext Current context of block
diff --git a/tag/locallib.php b/tag/locallib.php
index 55cd31e278a..a56a8fb48e7 100644
--- a/tag/locallib.php
+++ b/tag/locallib.php
@@ -23,421 +23,5 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
-require_once($CFG->dirroot.'/tag/lib.php');
-require_once($CFG->libdir.'/filelib.php');
-
-/**
- * Prints or returns a HTML tag cloud with varying classes styles depending on the popularity and type of each tag.
- *
- * @package core_tag
- * @access public
- * @category tag
- * @param array $tagset Array of tags to display
- * @param int $nr_of_tags Limit for the number of tags to return/display, used if $tagset is null
- * @param bool $return if true the function will return the generated tag cloud instead of displaying it.
- * @param string $sort (optional) selected sorting, default is alpha sort (name) also timemodified or popularity
- * @return string|null a HTML string or null if this function does the output
- */
-function tag_print_cloud($tagset=null, $nr_of_tags=150, $return=false, $sort='') {
- global $CFG, $DB;
-
- $can_manage_tags = has_capability('moodle/tag:manage', context_system::instance());
-
- if (is_null($tagset)) {
- // No tag set received, so fetch tags from database
- if ( !$tagsincloud = $DB->get_records_sql('SELECT tg.rawname, tg.id, tg.name, tg.tagtype, COUNT(ti.id) AS count, tg.flag
- FROM {tag_instance} ti JOIN {tag} tg ON tg.id = ti.tagid
- WHERE ti.itemtype <> \'tag\'
- GROUP BY tg.id, tg.rawname, tg.name, tg.flag, tg.tagtype
- ORDER BY count DESC, tg.name ASC', null, 0, $nr_of_tags) ) {
- $tagsincloud = array();
- }
- } else {
- $tagsincloud = $tagset;
- }
-
- $tagkeys = array_keys($tagsincloud);
- if (!empty($tagkeys)) {
- $firsttagkey = $tagkeys[0];
- $maxcount = $tagsincloud[$firsttagkey]->count;
- }
-
- $etags = array();
-
- foreach ($tagsincloud as $tag) {
- $size = (int) (( $tag->count / $maxcount) * 20);
- $tag->class = "$tag->tagtype s$size";
- $etags[] = $tag;
- }
-
- // Set up sort global - used to pass sort type into tag_cloud_sort through usort() avoiding multiple sort functions.
- // TODO make calling functions pass 'count' or 'timemodified' not 'popularity' or 'date'.
- $oldsort = empty($CFG->tagsort) ? null : $CFG->tagsort;
- if ($sort == 'popularity') {
- $CFG->tagsort = 'count';
- } else if ($sort == 'date') {
- $CFG->tagsort = 'timemodified';
- } else {
- $CFG->tagsort = 'name';
- }
- usort($etags, "tag_cloud_sort");
- $CFG->tagsort = $oldsort;
-
- $output = '';
- $output .= "\n\n";
- foreach ($etags as $tag) {
- if ($tag->flag > 0 && $can_manage_tags) {
- $tagname = ''. tag_display_name($tag) .'';
- } else {
- $tagname = tag_display_name($tag);
- }
-
- $link = $CFG->wwwroot .'/tag/index.php?tag='. rawurlencode($tag->name);
- $output .= '- count) .'">'.
- $tagname .'
';
- }
- $output .= "\n
\n";
-
- if ($return) {
- return $output;
- } else {
- echo $output;
- }
-}
-
-/**
- * This function is used by print_tag_cloud, to usort() the tags in the cloud. See php.net/usort for the parameters documentation.
- * This was originally in blocks/blog_tags/block_blog_tags.php, named blog_tags_sort().
- *
- * @package core_tag
- * @access private
- * @param string $a Tag name to compare against $b
- * @param string $b Tag name to compare against $a
- * @return int The result of the comparison/validation 1, 0 or -1
- */
-function tag_cloud_sort($a, $b) {
- global $CFG;
-
- if (empty($CFG->tagsort)) {
- $tagsort = 'name'; // by default, sort by name
- } else {
- $tagsort = $CFG->tagsort;
- }
-
- if (is_numeric($a->$tagsort)) {
- return ($a->$tagsort == $b->$tagsort) ? 0 : ($a->$tagsort > $b->$tagsort) ? 1 : -1;
- } elseif (is_string($a->$tagsort)) {
- return strcmp($a->$tagsort, $b->$tagsort);
- } else {
- return 0;
- }
-}
-
-/**
- * Prints a box with the description of a tag and its related tags
- *
- * @package core_tag
- * @access public
- * @todo MDL-31149 create a system setting for $max_tags_displayed, instead of using an in code literal
- * @param stdClass $tag_object
- * @param bool $return if true the function will return the generated tag cloud instead of displaying it.
- * @return string/null a HTML box showing a description of the tag object and it's relationsips or null if output is done directly
- * in the function.
- */
-function tag_print_description_box($tag_object, $return=false) {
-
- global $USER, $CFG, $OUTPUT;
-
- $max_tags_displayed = 10;
-
- $tagname = tag_display_name($tag_object);
- $related_tags = tag_get_related_tags($tag_object->id, TAG_RELATED_ALL, $max_tags_displayed+1); // this gets one more than we want
-
- $content = !empty($tag_object->description) || $related_tags;
- $output = '';
-
- if ($content) {
- $output .= $OUTPUT->box_start('generalbox', 'tag-description');
- }
-
- if (!empty($tag_object->description)) {
- $options = new stdClass();
- $options->para = false;
- $options->overflowdiv = true;
- $tag_object->description = file_rewrite_pluginfile_urls($tag_object->description, 'pluginfile.php', context_system::instance()->id, 'tag', 'description', $tag_object->id);
- $output .= format_text($tag_object->description, $tag_object->descriptionformat, $options);
- }
-
- if ($related_tags) {
- $more_links = false;
- if (count($related_tags) > $max_tags_displayed) {
- array_pop($related_tags);
- $more_links = true;
- }
- $output .= '
'. get_string('relatedtags', 'tag') .': '. tag_get_related_tags_csv($related_tags);
- if ($more_links) {
- $output .= ' ...';
- }
- }
-
- if ($content) {
- $output .= $OUTPUT->box_end();
- }
-
- if ($return) {
- return $output;
- } else {
- echo $output;
- }
-}
-
-/**
- * Prints a box that contains the management links of a tag
- *
- * @access public
- * @param stdClass $tag_object
- * @param bool $return if true the function will return the generated tag cloud instead of displaying it.
- * @return string|null a HTML string or null if this function does the output
- */
-function tag_print_management_box($tag_object, $return=false) {
-
- global $USER, $CFG, $OUTPUT;
-
- $tagname = tag_display_name($tag_object);
- $output = '';
-
- if (!isguestuser()) {
- $output .= $OUTPUT->box_start('box','tag-management-box');
- $systemcontext = context_system::instance();
- $links = array();
-
- // Add a link for users to add/remove this from their interests
- if (tag_record_tagged_with('user', $USER->id, $tag_object->name)) {
- $links[] = ''. get_string('removetagfrommyinterests', 'tag', $tagname) .'';
- } else {
- $links[] = ''. get_string('addtagtomyinterests', 'tag', $tagname) .'';
- }
-
- // Flag as inappropriate link. Only people with moodle/tag:flag capability.
- if (has_capability('moodle/tag:flag', $systemcontext)) {
- $links[] = ''. get_string('flagasinappropriate', 'tag', rawurlencode($tagname)) .'';
- }
-
- // Edit tag: Only people with moodle/tag:edit capability who either have it as an interest or can manage tags
- if (has_capability('moodle/tag:edit', $systemcontext) ||
- has_capability('moodle/tag:manage', $systemcontext)) {
- $links[] = ''. get_string('edittag', 'tag') .'';
- }
-
- $output .= implode(' | ', $links);
- $output .= $OUTPUT->box_end();
- }
-
- if ($return) {
- return $output;
- } else {
- echo $output;
- }
-}
-
-/**
- * Prints the tag search box
- *
- * @access public
- * @param bool $return if true return html string
- * @return string|null a HTML string or null if this function does the output
- */
-function tag_print_search_box($return=false) {
- global $CFG, $OUTPUT;
-
- $output = $OUTPUT->box_start('','tag-search-box');
- $output .= '';
- $output .= $OUTPUT->box_end();
-
- if ($return) {
- return $output;
- }
- else {
- echo $output;
- }
-}
-
-/**
- * Prints the tag search results
- *
- * @access public
- * @param string $query text that tag names will be matched against
- * @param int $page current page
- * @param int $perpage nr of users displayed per page
- * @param bool $return if true return html string
- * @return string|null a HTML string or null if this function does the output
- */
-function tag_print_search_results($query, $page, $perpage, $return=false) {
-
- global $CFG, $USER, $OUTPUT;
-
- $norm = tag_normalize($query, TAG_CASE_ORIGINAL);
- $query = array_shift($norm);
-
- $count = sizeof(tag_find_tags($query, false));
- $tags = array();
-
- if ( $found_tags = tag_find_tags($query, true, $page * $perpage, $perpage) ) {
- $tags = array_values($found_tags);
- }
-
- $baseurl = $CFG->wwwroot.'/tag/search.php?query='. rawurlencode($query);
- $output = '';
-
- // link "Add $query to my interests"
- $addtaglink = '';
- if( !tag_record_tagged_with('user', $USER->id, $query) ) {
- $addtaglink = '';
- $addtaglink .= get_string('addtagtomyinterests', 'tag', htmlspecialchars($query)) .'';
- }
-
- if ( !empty($tags) ) { // there are results to display!!
- $output .= $OUTPUT->heading(get_string('searchresultsfor', 'tag', htmlspecialchars($query)) ." : {$count}", 3, 'main');
-
- //print a link "Add $query to my interests"
- if (!empty($addtaglink)) {
- $output .= $OUTPUT->box($addtaglink, 'box', 'tag-management-box');
- }
-
- $nr_of_lis_per_ul = 6;
- $nr_of_uls = ceil( sizeof($tags) / $nr_of_lis_per_ul );
-
- $output .= '';
- for($i = 0; $i < $nr_of_uls; $i++) {
- $output .= '- ';
- foreach (array_slice($tags, $i * $nr_of_lis_per_ul, $nr_of_lis_per_ul) as $tag) {
- $tag_link = ' '. tag_display_name($tag) .'';
- $output .= '•'. $tag_link .'
';
- }
- $output .= ' ';
- }
- $output .= '
';
- $output .= '
'; // <-- small layout hack in order to look good in Firefox
-
- $output .= $OUTPUT->paging_bar($count, $page, $perpage, $baseurl);
- }
- else { //no results were found!!
- $output .= $OUTPUT->heading(get_string('noresultsfor', 'tag', htmlspecialchars($query)), 3, 'main');
-
- //print a link "Add $query to my interests"
- if (!empty($addtaglink)) {
- $output .= $OUTPUT->box($addtaglink, 'box', 'tag-management-box');
- }
- }
-
- if ($return) {
- return $output;
- }
- else {
- echo $output;
- }
-}
-
-/**
- * Prints a table of the users tagged with the tag passed as argument
- *
- * @param int $tag_object the tag we wish to return data for
- * @param int $limitfrom (optional, required if $limitnum is set) prints users starting at this point.
- * @param int $limitnum (optional, required if $limitfrom is set) prints this many users.
- * @param bool $return if true return html string
- * @return string|null a HTML string or null if this function does the output
- */
-function tag_print_tagged_users_table($tag_object, $limitfrom='', $limitnum='', $return=false) {
-
- //List of users with this tag
- $userlist = tag_find_records($tag_object->name, 'user', $limitfrom, $limitnum);
-
- $output = tag_print_user_list($userlist, true);
-
- if ($return) {
- return $output;
- }
- else {
- echo $output;
- }
-}
-
-/**
- * Prints an individual user box
- *
- * @param user_object $user (contains the following fields: id, firstname, lastname and picture)
- * @param bool $return if true return html string
- * @return string|null a HTML string or null if this function does the output
- */
-function tag_print_user_box($user, $return=false) {
- global $CFG, $OUTPUT;
-
- $usercontext = context_user::instance($user->id);
- $profilelink = '';
-
- if ($usercontext and (has_capability('moodle/user:viewdetails', $usercontext) || has_coursecontact_role($user->id))) {
- $profilelink = $CFG->wwwroot .'/user/view.php?id='. $user->id;
- }
-
- $output = $OUTPUT->box_start('user-box', 'user'. $user->id);
- $fullname = fullname($user);
- $alt = '';
-
- if (!empty($profilelink)) {
- $output .= '';
- $alt = $fullname;
- }
-
- $output .= $OUTPUT->user_picture($user, array('size'=>100));
- $output .= '
';
-
- if (!empty($profilelink)) {
- $output .= '';
- }
-
- //truncate name if it's too big
- if (core_text::strlen($fullname) > 26) {
- $fullname = core_text::substr($fullname, 0, 26) .'...';
- }
-
- $output .= ''. $fullname .'';
- $output .= $OUTPUT->box_end();
-
- if ($return) {
- return $output;
- }
- else {
- echo $output;
- }
-}
-
-/**
- * Prints a list of users
- *
- * @param array $userlist an array of user objects
- * @param bool $return if true return html string, otherwise output the result
- * @return string|null a HTML string or null if this function does the output
- */
-function tag_print_user_list($userlist, $return=false) {
-
- $output = '';
-
- foreach ($userlist as $user){
- $output .= '- '. tag_print_user_box($user, true) ."
\n";
- }
- $output .= "
\n";
-
- if ($return) {
- return $output;
- }
- else {
- echo $output;
- }
-}
+debugging('All functions from /tags/locallib.php were deprecated and it will be removed soon, '.
+ 'do not include this file in your code', DEBUG_DEVELOPER);
diff --git a/tag/manage.php b/tag/manage.php
index d83aeca8918..3deb5c4e75c 100644
--- a/tag/manage.php
+++ b/tag/manage.php
@@ -1,5 +1,4 @@
.
/**
- * @package core
- * @subpackage tag
+ * Managing tags, tag areas and tags collections
+ *
+ * @package core_tag
* @copyright 2007 Luiz Cruz
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -37,12 +37,8 @@ $action = optional_param('action', '', PARAM_ALPHA);
$perpage = optional_param('perpage', DEFAULT_PAGE_SIZE, PARAM_INT);
$page = optional_param('page', 0, PARAM_INT);
$notice = optional_param('notice', '', PARAM_ALPHA);
-
-require_login();
-
-if (empty($CFG->usetags)) {
- print_error('tagsaredisabled', 'tag');
-}
+$tagcollid = optional_param('tc', 0, PARAM_INT);
+$tagareaid = optional_param('ta', null, PARAM_INT);
$params = array();
if ($perpage != DEFAULT_PAGE_SIZE) {
@@ -51,60 +47,168 @@ if ($perpage != DEFAULT_PAGE_SIZE) {
if ($page > 0) {
$params['page'] = $page;
}
+
admin_externalpage_setup('managetags', '', $params, '', array('pagelayout' => 'report'));
+if (empty($CFG->usetags)) {
+ print_error('tagsaredisabled', 'tag');
+}
+
+$tagobject = null;
+if ($tagid) {
+ $tagobject = core_tag_tag::get($tagid, '*', MUST_EXIST);
+ $tagcollid = $tagobject->tagcollid;
+}
+$tagcoll = core_tag_collection::get_by_id($tagcollid);
+$tagarea = core_tag_area::get_by_id($tagareaid);
+$manageurl = new moodle_url('/tag/manage.php');
+if ($tagcoll) {
+ // We are inside a tag collection - add it to the page url and the breadcrumb.
+ $PAGE->set_url(new moodle_url($PAGE->url, array('tc' => $tagcoll->id)));
+ $PAGE->navbar->add(core_tag_collection::display_name($tagcoll),
+ new moodle_url($manageurl, array('tc' => $tagcoll->id)));
+}
+
$PAGE->set_blocks_editing_capability('moodle/tag:editblocks');
switch($action) {
+ case 'colladd':
+ case 'colledit':
+ if ($action === 'colladd' || ($action === 'colledit' && $tagcoll && empty($tagcoll->component))) {
+ $form = new core_tag_collection_form($manageurl, $tagcoll);
+ if ($form->is_cancelled()) {
+ redirect($manageurl);
+ } else if ($data = $form->get_data()) {
+ if ($action === 'colladd') {
+ core_tag_collection::create($data);
+ } else {
+ core_tag_collection::update($tagcoll, $data);
+ }
+ redirect($manageurl);
+ } else {
+ $title = ($action === 'colladd') ?
+ get_string('addtagcoll', 'tag') :
+ get_string('edittagcoll', 'tag', core_tag_collection::display_name($tagcoll));
+ $PAGE->navbar->add($title);
+ echo $OUTPUT->header();
+ echo $OUTPUT->heading($title, 2);
+ $form->display();
+ echo $OUTPUT->footer();
+ exit;
+ }
+ }
+ break;
+
+ case 'colldelete':
+ $confirm = optional_param('confirm', false, PARAM_BOOL);
+ if (!$confirm) {
+ echo $OUTPUT->header();
+ $strconfirm = get_string('suredeletecoll', 'tag', core_tag_collection::display_name($tagcoll));
+ $params = array('tc' => $tagcoll->id, 'confirm' => 1, 'sesskey' => sesskey(), 'action' => 'colldelete');
+ $formcontinue = new single_button(new moodle_url($manageurl, $params), get_string('yes'));
+ $formcancel = new single_button($manageurl, get_string('no'), 'get');
+ echo $OUTPUT->confirm($strconfirm, $formcontinue, $formcancel);
+ echo $OUTPUT->footer();
+ die;
+ }
+ if ($tagcoll && !$tagcoll->component) {
+ require_sesskey();
+ core_tag_collection::delete($tagcoll);
+ redirect(new moodle_url($manageurl, array('notice' => 'changessaved')));
+ }
+ redirect($manageurl);
+ break;
+
+ case 'collmoveup':
+ if ($tagcoll) {
+ require_sesskey();
+ core_tag_collection::change_sortorder($tagcoll, -1);
+ redirect(new moodle_url($manageurl, array('notice' => 'changessaved')));
+ }
+ redirect($manageurl);
+ break;
+
+ case 'collmovedown':
+ if ($tagcoll) {
+ require_sesskey();
+ core_tag_collection::change_sortorder($tagcoll, 1);
+ redirect(new moodle_url($manageurl, array('notice' => 'changessaved')));
+ }
+ redirect($manageurl);
+ break;
+
+ case 'areaenable':
+ case 'areadisable':
+ if ($tagarea) {
+ require_sesskey();
+ $data = array('enabled' => ($action === 'areaenable') ? 1 : 0);
+ core_tag_area::update($tagarea, $data);
+ redirect(new moodle_url($manageurl, array('notice' => 'changessaved')));
+ }
+ redirect($manageurl);
+ break;
+
+ case 'areasetcoll':
+ if ($tagarea) {
+ require_sesskey();
+ if ($newtagcollid = optional_param('areacollid', null, PARAM_INT)) {
+ core_tag_area::update($tagarea, array('tagcollid' => $newtagcollid));
+ redirect(new moodle_url($manageurl, array('notice' => 'changessaved')));
+ }
+ }
+ redirect($manageurl);
+ break;
+
case 'delete':
require_sesskey();
if (!$tagschecked && $tagid) {
$tagschecked = array($tagid);
}
- tag_delete($tagschecked);
- redirect(new moodle_url($PAGE->url, array('notice' => 'deleted')));
+ core_tag_tag::delete_tags($tagschecked);
+ redirect(new moodle_url($PAGE->url, $tagschecked ? array('notice' => 'deleted') : null));
break;
case 'setflag':
require_sesskey();
- tag_set_flag($tagid);
- redirect(new moodle_url($PAGE->url, array('notice' => 'flagged')));
+ if ($tagid) {
+ $tagobject->flag();
+ redirect(new moodle_url($PAGE->url, array('notice' => 'flagged')));
+ }
+ redirect($PAGE->url);
break;
case 'resetflag':
require_sesskey();
- tag_unset_flag($tagid);
- redirect(new moodle_url($PAGE->url, array('notice' => 'resetflag')));
+ if ($tagid) {
+ $tagobject->reset_flag();
+ redirect(new moodle_url($PAGE->url, array('notice' => 'resetflag')));
+ }
+ redirect($PAGE->url);
break;
case 'changetype':
require_sesskey();
- if ($tagtype === 'official' || $tagtype === 'default') {
- if (tag_type_set($tagid, $tagtype)) {
- redirect(new moodle_url($PAGE->url, array('notice' => 'typechanged')));
- }
+ if ($tagid && $tagobject->update(array('tagtype' => $tagtype))) {
+ redirect(new moodle_url($PAGE->url, array('notice' => 'typechanged')));
}
redirect($PAGE->url);
break;
case 'addofficialtag':
require_sesskey();
- $otagsadd = optional_param('otagsadd', '', PARAM_RAW);
- $newtags = preg_split('/\s*,\s*/', trim($otagsadd), -1, PREG_SPLIT_NO_EMPTY);
- $newtags = array_filter(tag_normalize($newtags, TAG_CASE_ORIGINAL));
- if (!$newtags) {
- redirect($PAGE->url);
+ $tagobjects = null;
+ if ($tagcoll) {
+ $otagsadd = optional_param('otagsadd', '', PARAM_RAW);
+ $newtags = preg_split('/\s*,\s*/', trim($otagsadd), -1, PREG_SPLIT_NO_EMPTY);
+ $tagobjects = core_tag_tag::create_if_missing($tagcoll->id, $newtags, true);
}
- foreach ($newtags as $newotag) {
- if ($newotagid = tag_get_id($newotag) ) {
- // Tag exists, change the type.
- tag_type_set($newotagid, 'official');
- } else {
- tag_add($newotag, 'official');
+ foreach ($tagobjects as $tagobject) {
+ if ($tagobject->tagtype !== 'official') {
+ $tagobject->update(array('tagtype' => 'official'));
}
}
- redirect(new moodle_url($PAGE->url, array('notice' => 'added')));
+ redirect(new moodle_url($PAGE->url, $tagobjects ? array('notice' => 'added') : null));
break;
}
@@ -114,8 +218,28 @@ if ($notice && get_string_manager()->string_exists($notice, 'tag')) {
echo $OUTPUT->notification(get_string($notice, 'tag'), 'notifysuccess');
}
+if (!$tagcoll) {
+ // Tag collection is not specified. Display the overview of tag collections and tag areas.
+ $tagareastable = new core_tag_areas_table($manageurl);
+ $colltable = new core_tag_collections_table($manageurl);
+
+ echo $OUTPUT->heading(get_string('tagcollections', 'core_tag'), 3);
+ echo html_writer::table($colltable);
+ $url = new moodle_url($manageurl, array('action' => 'colladd'));
+ echo html_writer::div(html_writer::link($url, get_string('addtagcoll', 'tag')), 'mdl-right addtagcoll');
+
+ echo $OUTPUT->heading(get_string('tagareas', 'core_tag'), 3);
+ echo html_writer::table($tagareastable);
+
+ echo $OUTPUT->footer();
+ exit;
+}
+
+// Tag collection is specified. Manage tags in this collection.
+
// Small form to add an official tag.
print('