MDL-50851 core_tag: introduce tag collections

This commit is contained in:
Marina Glancy
2016-01-10 15:25:43 +08:00
parent e65dfd9f28
commit c026a28d59
70 changed files with 7467 additions and 2670 deletions
+1 -2
View File
@@ -1648,7 +1648,6 @@ function course_delete_module($cmid) {
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/blog/lib.php');
require_once($CFG->dirroot.'/calendar/lib.php');
require_once($CFG->dirroot.'/tag/lib.php');
// Get the course module.
if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
@@ -1717,7 +1716,7 @@ function course_delete_module($cmid) {
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
// Delete all tag instances associated with the instance of this module.
tag_delete_instances('mod_' . $modulename, $modcontext->id);
core_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
// Delete the context.
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
+1
View File
@@ -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';
+1
View File
@@ -20,3 +20,4 @@ updated,core_tag
withselectedtags,core_tag
tag:create,core_role
categoriesanditems,core_grades
taggedwith,core_tag
+1
View File
@@ -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}';
+39 -2
View File
@@ -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}"';
+2 -3
View File
@@ -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);
+1 -1
View File
@@ -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())})}})}}});
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())})}})}}});
+30 -2
View File
@@ -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() {
+2 -2
View File
@@ -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());
+28
View File
@@ -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().
*
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tag collection created event.
*
* @package core
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Tag collection created event class.
*
* @package core
* @since Moodle 3.0
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_collection_created extends base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['objecttable'] = 'tag_coll';
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Utility method to create new event.
*
* @param object $tagcoll
* @return user_graded
*/
public static function create_from_record($tagcoll) {
$event = self::create(array(
'objectid' => $tagcoll->id,
'context' => \context_system::instance(),
));
$event->add_record_snapshot('tag_coll', $tagcoll);
return $event;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventtagcollcreated', 'core_tag');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' created the tag collection with id '$this->objectid'";
}
}
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tag collection deleted event.
*
* @package core
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Tag collection deleted event class.
*
* @package core
* @since Moodle 3.0
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_collection_deleted extends base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['objecttable'] = 'tag_coll';
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Utility method to create new event.
*
* @param object $tagcoll
* @return user_graded
*/
public static function create_from_record($tagcoll) {
$event = self::create(array(
'objectid' => $tagcoll->id,
'context' => \context_system::instance(),
));
$event->add_record_snapshot('tag_coll', $tagcoll);
return $event;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventtagcolldeleted', 'core_tag');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the tag collection with id '$this->objectid'";
}
}
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tag collection updated event.
*
* @package core
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Tag collection updated event class.
*
* @package core
* @since Moodle 3.0
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tag_collection_updated extends base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['objecttable'] = 'tag_coll';
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Utility method to create new event.
*
* @param object $tagcoll
* @return user_graded
*/
public static function create_from_record($tagcoll) {
$event = self::create(array(
'objectid' => $tagcoll->id,
'context' => \context_system::instance(),
));
$event->add_record_snapshot('tag_coll', $tagcoll);
return $event;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventtagcollupdated', 'core_tag');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' updated the tag collection with id '$this->objectid'";
}
}
+20
View File
@@ -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.
*
+28
View File
@@ -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.
*
+219 -2
View File
@@ -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();
}
}
}
+6
View File
@@ -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,
)
);
+41 -5
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20150922" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20160111" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@@ -1958,10 +1958,43 @@
<KEY NAME="importer" TYPE="foreign" FIELDS="importer" REFTABLE="user" REFFIELDS="id" COMMENT="user who is importing"/>
</KEYS>
</TABLE>
<TABLE NAME="tag_coll" COMMENT="Defines different set of tags">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="isdefault" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="searchable" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Whether the tag collection is searchable"/>
<FIELD NAME="customurl" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Custom URL for the tag page instead of /tag/index.php"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="tag_area" COMMENT="Defines various tag areas, one area is identified by component and itemtype">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="itemtype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="enabled" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="callback" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="callbackfile" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="tagcollid" TYPE="foreign" FIELDS="tagcollid" REFTABLE="tag_coll" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="compitemtype" UNIQUE="true" FIELDS="component, itemtype"/>
</INDEXES>
</TABLE>
<TABLE NAME="tag" COMMENT="Tag table - this generic table will replace the old &quot;tags&quot; table.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="rawname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The raw, unnormalised name for the tag as entered by users"/>
<FIELD NAME="tagtype" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
@@ -1973,9 +2006,11 @@
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="tagcollid" TYPE="foreign" FIELDS="tagcollid" REFTABLE="tag_coll" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="name" UNIQUE="true" FIELDS="name" COMMENT="tag names are unique"/>
<INDEX NAME="tagcollname" UNIQUE="true" FIELDS="tagcollid, name"/>
<INDEX NAME="tagcolltype" UNIQUE="false" FIELDS="tagcollid, tagtype"/>
</INDEXES>
</TABLE>
<TABLE NAME="tag_correlation" COMMENT="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">
@@ -1993,8 +2028,8 @@
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="tagid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Defines the Moodle component which the tag was added to"/>
<FIELD NAME="itemtype" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="Defines the Moodle component which the tag was added to"/>
<FIELD NAME="itemtype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The context id of the item that was tagged"/>
<FIELD NAME="tiuserid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
@@ -2008,7 +2043,8 @@
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="itemtype-itemid-tagid-tiuserid" UNIQUE="true" FIELDS="itemtype, itemid, tagid, tiuserid"/>
<INDEX NAME="taggeditem" UNIQUE="true" FIELDS="component, itemtype, itemid, tiuserid, tagid"/>
<INDEX NAME="taglookup" UNIQUE="false" FIELDS="itemtype, component, tagid, contextid"/>
</INDEXES>
</TABLE>
<TABLE NAME="groups" COMMENT="Each record represents a group.">
+8
View File
@@ -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(
+80
View File
@@ -0,0 +1,80 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tag 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',
),
);
+2 -2
View File
@@ -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' => '*'
+186
View File
@@ -4609,5 +4609,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;
}
+1261 -31
View File
File diff suppressed because it is too large Load Diff
+122 -33
View File
@@ -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);
}
}
+17
View File
@@ -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));
}
}
/**
+4
View File
@@ -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';
}
+1 -1
View File
@@ -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);
+3 -2
View File
@@ -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 ===
+11
View File
@@ -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);
+1
View File
@@ -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
+450
View File
@@ -0,0 +1,450 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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');
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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';
}
}
}
}
+419
View File
@@ -0,0 +1,419 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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;
}
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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++;
}
}
}
+91 -22
View File
@@ -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'
);
}
}
+17 -7
View File
@@ -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()) {
+10 -4
View File
@@ -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(),
+121
View File
@@ -0,0 +1,121 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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,
);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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);
}
}
+161
View File
@@ -0,0 +1,161 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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;
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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,
);
}
}
+180
View File
@@ -0,0 +1,180 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 .= '<div class="tag-search-empty">' . get_string('notagsfound', 'tag', s($query)) . '</div>';
}
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 = '<span class="flagged-tag">' . $tagname . '</span>';
}
$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[] = '<li><a href="#'.$entity->anchor.'">' .
core_tag_area::display_name($entity->component, $entity->itemtype) . '</a></li>';
}
}
if (count($entitylinks) > 1) {
$rv .= '<div class="tag-index-toc"><ul class="inline-list">' . join('', $entitylinks) . '</ul></div>';
} else if (!$entitylinks) {
$rv .= '<div class="tag-noresults">' . get_string('noresultsfor', 'tag', $tagname) . '</div>';
}
// 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 .= '<div class="tag-backtoallitems">' .
html_writer::link($url, get_string('backtoallitems', 'tag', $tag->get_display_name())) .
'</div>';
}
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 = '<ul class="inline-list"><li>' . implode('</li> <li>', $links) . '</li></ul>';
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 = '<form action="' . $searchurl . '">';
$output .= '<label class="accesshide" for="searchform_query">' . get_string('searchtags', 'tag') . '</label>';
$output .= '<input id="searchform_query" name="query" type="text" size="40" value="' . s($query) . '" />';
$tagcolls = core_tag_collection::get_collections_menu(false, true, get_string('inalltagcoll', 'tag'));
if (count($tagcolls) > 1) {
$output .= '<label class="accesshide" for="searchform_tc">' . get_string('selectcoll', 'tag') . '</label>';
$output .= html_writer::select($tagcolls, 'tc', $tagcollid, null, array('id' => 'searchform_tc'));
}
$output .= '<input name="go" type="submit" size="40" value="' . s(get_string('search', 'tag')) . '" />';
$output .= '</form>';
return $output;
}
}
+1414
View File
File diff suppressed because it is too large Load Diff
+47 -70
View File
@@ -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'));
+27 -1
View File
@@ -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;
}
}
+67 -142
View File
@@ -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 '<div class="managelink"><a href="'. $CFG->wwwroot .'/tag/manage.php">'. get_string('managetags', 'tag') .'</a></div>' ;
}
$tagname = tag_display_name($tag);
if ($tag->flag > 0 && has_capability('moodle/tag:manage', $systemcontext)) {
$tagname = '<span class="flagged-tag">' . $tagname . '</span>';
}
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 <a href />'s to those anchors that actually will be shown
$relatedpageslink = "";
$countanchors = 0;
if (!empty($courses)) {
$relatedpageslink = '<a href="#course">'.get_string('courses').'</a>';
$countanchors++;
}
if (!empty($blogs)) {
if ($countanchors > 0) {
$relatedpageslink .= ' | ';
}
$relatedpageslink .= '<a href="#blog">'.get_string('relatedblogs', 'tag').'</a>';
$countanchors++;
}
if ($usercount > 0) {
if ($countanchors > 0) {
$relatedpageslink .= ' | ';
}
$relatedpageslink .= '<a href="#user">'.get_string('users').'</a>';
$countanchors++;
}
// If only one anchor is present, no <a href /> is needed
if ($countanchors == 0) {
echo '<div class="relatedpages"><p>'.get_string('noresultsfor', 'tag', $tagname).'</p></div>';
} elseif ($countanchors > 1) {
echo '<div class="relatedpages"><p>'.$relatedpageslink.'</p></div>';
}
// 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 "<a name='course'></a>";
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 "<a name='blog'></a>";
echo $OUTPUT->heading($heading, 3);
echo '<ul id="tagblogentries">';
foreach ($blogs as $blog) {
if ($blog->publishstate == 'draft') {
$class = 'class="dimmed"';
} else {
$class = '';
}
echo '<li '.$class.'>';
echo '<a '.$class.' href="'.$CFG->wwwroot.'/blog/index.php?entryid='.$blog->id.'">';
echo format_string($blog->subject);
echo '</a>';
echo ' - ';
echo '<a '.$class.' href="'.$CFG->wwwroot.'/user/view.php?id='.$blog->userid.'">';
echo fullname($blog);
echo '</a>';
echo ', '. userdate($blog->lastmodified);
echo '</li>';
}
echo '</ul>';
$allblogsurl = new moodle_url('/blog/index.php', array('tagid' => $tag->id));
echo '<p class="moreblogs"><a href="'.$allblogsurl->out().'">'.get_string('seeallblogs', 'tag', $tagname).'</a></p>';
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 "<a name='user'></a>";
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();
+2 -1526
View File
File diff suppressed because it is too large Load Diff
+2 -418
View File
@@ -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<ul class='tag_cloud inline-list'>\n";
foreach ($etags as $tag) {
if ($tag->flag > 0 && $can_manage_tags) {
$tagname = '<span class="flagged-tag">'. tag_display_name($tag) .'</span>';
} else {
$tagname = tag_display_name($tag);
}
$link = $CFG->wwwroot .'/tag/index.php?tag='. rawurlencode($tag->name);
$output .= '<li><a href="'. $link .'" class="'. $tag->class .'" '.
'title="'. get_string('numberofentries', 'blog', $tag->count) .'">'.
$tagname .'</a></li> ';
}
$output .= "\n</ul>\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 .= '<br /><br /><strong>'. get_string('relatedtags', 'tag') .': </strong>'. 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[] = '<a href="'. $CFG->wwwroot .'/tag/user.php?action=removeinterest&amp;sesskey='. sesskey() .'&amp;tag='. rawurlencode($tag_object->name) .'">'. get_string('removetagfrommyinterests', 'tag', $tagname) .'</a>';
} else {
$links[] = '<a href="'. $CFG->wwwroot .'/tag/user.php?action=addinterest&amp;sesskey='. sesskey() .'&amp;tag='. rawurlencode($tag_object->name) .'">'. get_string('addtagtomyinterests', 'tag', $tagname) .'</a>';
}
// Flag as inappropriate link. Only people with moodle/tag:flag capability.
if (has_capability('moodle/tag:flag', $systemcontext)) {
$links[] = '<a href="'. $CFG->wwwroot .'/tag/user.php?action=flaginappropriate&amp;sesskey='. sesskey() .'&amp;tag='. rawurlencode($tag_object->name) .'">'. get_string('flagasinappropriate', 'tag', rawurlencode($tagname)) .'</a>';
}
// 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[] = '<a href="'. $CFG->wwwroot .'/tag/edit.php?tag='. rawurlencode($tag_object->name) .'">'. get_string('edittag', 'tag') .'</a>';
}
$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 .= '<form action="'.$CFG->wwwroot.'/tag/search.php" style="display:inline">';
$output .= '<div>';
$output .= '<label class="accesshide" for="searchform_search">'.get_string('searchtags', 'tag').'</label>';
$output .= '<input id="searchform_search" name="query" type="text" size="40" />';
$output .= '<button id="searchform_button" type="submit">'. get_string('search', 'tag') .'</button><br />';
$output .= '</div>';
$output .= '</form>';
$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 = '<a href="'. $CFG->wwwroot .'/tag/user.php?action=addinterest&amp;sesskey='. sesskey() .'&amp;tag='. rawurlencode($query) .'">';
$addtaglink .= get_string('addtagtomyinterests', 'tag', htmlspecialchars($query)) .'</a>';
}
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 .= '<ul id="tag-search-results">';
for($i = 0; $i < $nr_of_uls; $i++) {
$output .= '<li>';
foreach (array_slice($tags, $i * $nr_of_lis_per_ul, $nr_of_lis_per_ul) as $tag) {
$tag_link = ' <a href="'. $CFG->wwwroot .'/tag/index.php?id='. $tag->id .'">'. tag_display_name($tag) .'</a>';
$output .= '&#8226;'. $tag_link .'<br/>';
}
$output .= '</li>';
}
$output .= '</ul>';
$output .= '<div>&nbsp;</div>'; // <-- 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 .= '<a href="'. $profilelink .'">';
$alt = $fullname;
}
$output .= $OUTPUT->user_picture($user, array('size'=>100));
$output .= '<br />';
if (!empty($profilelink)) {
$output .= '</a>';
}
//truncate name if it's too big
if (core_text::strlen($fullname) > 26) {
$fullname = core_text::substr($fullname, 0, 26) .'...';
}
$output .= '<strong>'. $fullname .'</strong>';
$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 = '<ul class="inline-list">';
foreach ($userlist as $user){
$output .= '<li>'. tag_print_user_box($user, true) ."</li>\n";
}
$output .= "</ul>\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);
+163 -36
View File
@@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -16,8 +15,9 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package core
* @subpackage tag
* Managing tags, tag areas and tags collections
*
* @package core_tag
* @copyright 2007 Luiz Cruz <[email protected]>
* @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('<form class="tag-addtags-form" method="post" action="'.$CFG->wwwroot.'/tag/manage.php">');
print('<input type="hidden" name="tc" value="'.$tagcollid.'" />');
print('<input type="hidden" name="action" value="addofficialtag" />');
print('<input type="hidden" name="perpage" value="'.$perpage.'" />');
print('<input type="hidden" name="page" value="'.$page.'" />');
@@ -128,18 +252,21 @@ print('<div class="tag-management-form generalbox"><label class="accesshide" for
'</div>');
print('</form>');
$table = new core_tag_manage_table();
$table = new core_tag_manage_table($tagcollid);
echo '<form class="tag-management-form" method="post" action="'.$CFG->wwwroot.'/tag/manage.php">';
echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'tc', 'value' => $tagcollid));
echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'delete'));
echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'perpage', 'value' => $perpage));
echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'page', 'value' => $page));
echo $table->out($perpage, true);
echo html_writer::start_tag('p');
echo html_writer::tag('button', get_string('deleteselected', 'tag'),
array('id' => 'tag-management-delete', 'type' => 'submit', 'class' => 'tagdeleteselected'));
echo html_writer::end_tag('p');
if ($table->rawdata) {
echo html_writer::start_tag('p');
echo html_writer::tag('button', get_string('deleteselected', 'tag'),
array('id' => 'tag-management-delete', 'type' => 'submit', 'class' => 'tagdeleteselected'));
echo html_writer::end_tag('p');
}
echo '</form>';
$totalcount = $table->totalcount;
+24 -33
View File
@@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -23,8 +22,6 @@
*/
require_once('../config.php');
require_once('lib.php');
require_once('locallib.php');
require_login();
@@ -32,47 +29,41 @@ if (empty($CFG->usetags)) {
print_error('tagsaredisabled', 'tag');
}
$query = optional_param('query', '', PARAM_RAW);
$page = optional_param('page', 0, PARAM_INT); // which page to show
$perpage = optional_param('perpage', 18, PARAM_INT);
$query = optional_param('query', '', PARAM_RAW);
$tagcollid = optional_param('tc', 0, PARAM_INT);
$edit = optional_param('edit', -1, PARAM_BOOL);
$params = array();
if ($query !== '') {
$params['query'] = $query;
}
if ($page !== 0) {
$params['page'] = $page;
}
if ($perpage !== 18) {
$params['perpage'] = $perpage;
if ($tagcollid) {
$params['tc'] = $tagcollid;
}
$PAGE->set_url(new moodle_url('/tag/search.php', $params));
$PAGE->set_context(context_system::instance());
$PAGE->set_pagelayout('standard');
$systemcontext = context_system::instance();
$manage_link = '&nbsp;';
$PAGE->set_title(get_string('tags', 'tag'));
$PAGE->set_heading($SITE->fullname);
$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');
}
if ($PAGE->user_allowed_editing()) {
if ($edit != -1) {
$USER->editing = $edit;
}
$buttons .= $OUTPUT->edit_button(clone($PAGE->url));
}
$PAGE->set_button($buttons);
$tagrenderer = $PAGE->get_renderer('core', 'tag');
$pagecontents = $tagrenderer->tag_search_page($query, $tagcollid);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('searchtags', 'tag'), 2);
if ( has_capability('moodle/tag:manage',$systemcontext) ) {
echo '<div class="managelink"><a href="'. $CFG->wwwroot .'/tag/manage.php">' . get_string('managetags', 'tag') . '</a></div>' ;
}
tag_print_search_box();
if(!empty($query)) {
tag_print_search_results($query, $page, $perpage);
}
echo '<br/><br/>';
echo $OUTPUT->box_start('generalbox', 'big-tag-cloud-box');
tag_print_cloud(null, 150);
echo $OUTPUT->box_end();
echo $pagecontents;
echo $OUTPUT->footer();
+68
View File
@@ -0,0 +1,68 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_tag/index
Displays items in one tag area tagged with the specific tag
Classes required for JS:
* tagarea
Data attributes required for JS:
* data-quickload
* data-ta
Context variables required for this template:
* none
Example context (json):
{
"component" : "core",
"itemtype" : "course",
"nextpageurl" : "http://moodle.org",
"prevpageurl" : "http://moodle.org",
"exclusiveurl" : "http://moodle.org",
"exclusivetext" : "Show all Courses tagged with \"Sometag\"",
"title" : "Courses tagged with \"Sometag\"",
"content": "Course1, Course2",
"hascontent": 1,
"anchor": "core_course",
"ta": 2,
"tagid": 15
}
}}
{{#hascontent}}
<div class="box generalbox tagarea" id="tagarea-{{component}}-{{itemtype}}" data-ta="{{ta}}">
<a name="{{anchor}}"></a>
<h3>{{{title}}}</h3>
<div class="taggeditems">
{{{content}}}
</div>
<div class="controls controls-bottom">
{{#prevpageurl}}
<a class="gotopage prevpage" href="{{prevpageurl}}" data-quickload="1">{{#str}}prevpage,tag{{/str}}</a>
{{/prevpageurl}}
{{#nextpageurl}}
<a class="gotopage nextpage" href="{{nextpageurl}}" data-quickload="1">{{#str}}nextpage,tag{{/str}}</a>
{{/nextpageurl}}
{{#exclusiveurl}}
<a class="exclusivemode" href="{{exclusiveurl}}">{{exclusivetext}}</a>
{{/exclusiveurl}}
</div>
</div>
{{/hascontent}}
+67
View File
@@ -0,0 +1,67 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_tag/tagcloud
Displays a tag cloud
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{
"tags": [
{"id":1,"name":"Cats","viewurl":"http://moodle.org/tag/index.php?tag=Cats","tagtype":"official","count":12,"size":20,"flag":0},
{"id":1,"name":"Dogs","viewurl":"http://moodle.org/tag/index.php?tag=Dogs","tagtype":"default","size":2,"flag":1},
{"id":1,"name":"Mice","viewurl":"http://moodle.org/tag/index.php?tag=Mice","tagtype":"default","count":4,"size":8,"flag":0}
],
"tagscount": 3,
"totalcount": 5,
"overflow": 1
}
}}
{{#tagscount}}
<div class="tag_cloud">
<ul class="inline-list">
{{#tags}}
<li>
<a href="{{viewurl}}" class="{{tagtype}} s{{size}}"
{{#count}}title="{{#str}}numberofentries, blog, {{count}}{{/str}}{{/count}}">
{{#flag}}
<span class="flagged-tag">{{name}}</span></a>
{{/flag}}
{{^flag}}
{{name}}</a>
{{/flag}}
</li>
{{/tags}}
</ul>
{{#overflow}}
<p class="tag_overflow">
{{#str}} showingfirsttags, core_tag, {{tagscount}} {{/str}}
</p>
{{/overflow}}
</div>
{{/tagscount}}
+63
View File
@@ -0,0 +1,63 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_tag/tagfeed
Displays list of items tagged with a tag
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{
"items": [
{"img":"<img class=\"smallicon\" src=\"https://moodle.org/pix/i/siteevent.png\">","heading":"<a href=\"#\">First post</a>","details":"Posted by user 1"},
{"img":"<img class=\"smallicon\" src=\"https://moodle.org/pix/i/siteevent.png\">","heading":"<a href=\"#\">Second post without details</a>"},
{"heading":"<a href=\"#\">Third post without image</a>","details":"Posted by <a href=\"#\">user 3</a>"}
]
}
}}
<ul class="tag_feed media-list">
{{#items}}
<li class="media">
{{#img}}
<div class="itemimage">
{{{img}}}
</div>
{{/img}}
<div class="media-body">
{{#heading}}
<div class="media-heading">
{{{heading}}}
</div>
{{/heading}}
{{#details}}
<div class="muted">
{{{details}}}
</div>
{{/details}}
</div>
</li>
{{/items}}
</ul>
+85
View File
@@ -0,0 +1,85 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_tag/taglist
Displays a tag list, usually the list of tags some entry is tagged with
Classes required for JS:
* hideoverlimit
* tagmorelink
* taglesslink
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{
"tags": [
{"id":1,"name":"Cats","viewurl":"http://moodle.org/tag/index.php?tag=Cats","tagtype":"official","flag":0},
{"id":1,"name":"Dogs","viewurl":"http://moodle.org/tag/index.php?tag=Dogs","tagtype":"default","flag":1},
{"id":1,"name":"Mice","viewurl":"http://moodle.org/tag/index.php?tag=Mice","tagtype":"default","flag":0}
],
"label": "Tags",
"tagscount": 3,
"overflow": 1,
"classes": "someadditionalclass"
}
}}
{{#tagscount}}
<div class="tag_list hideoverlimit {{classes}}">
{{#label}}
<b>{{label}}:</b>
{{/label}}
<ul class="inline-list">
{{#tags}}
<li {{#overlimit}}class="overlimit"{{/overlimit}}>
<a href="{{viewurl}}" class="label label-info {{tagtype}}" >
{{#flag}}
<span class="flagged-tag">{{name}}</span></a>
{{/flag}}
{{^flag}}
{{name}}</a>
{{/flag}}
</li>
{{/tags}}
{{#overflow}}
<li class="visibleifjs">
<a href="#" class="tagmorelink">{{#str}}moretags, core_tag{{/str}}</a>
</li>
<li class="visibleifjs">
<a href="#" class="taglesslink">{{#str}}lesstags, core_tag{{/str}}</a>
</li>
{{/overflow}}
</ul>
</div>
{{/tagscount}}
{{#js}}
require(['jquery'], function($) {
$('.tag_list .tagmorelink').click(function() {
$(this).closest('.tag_list').removeClass('hideoverlimit');
});
$('.tag_list .taglesslink').click(function() {
$(this).closest('.tag_list').addClass('hideoverlimit');
});
});
{{/js}}
+135
View File
@@ -0,0 +1,135 @@
@core @core_tag
Feature: Managers can create and manage tag collections
In order to use tags effectively
As a manager
I need to be able to manage tag collections
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| manager1 | Manager | 1 | manager1@example.com |
| user1 | User | 1 | user1@example.com |
And the following "system role assigns" exist:
| user | course | role |
| manager1 | Acceptance test site | manager |
And the following "tags" exist:
| name | tagtype |
| Tag0 | official |
| Tag1 | official |
| Tag2 | official |
| Tag3 | official |
And I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Add tag collection"
And I set the following fields to these values:
| Name | Hobbies |
And I press "Save changes"
Scenario: Adding tag collections
When I follow "Hobbies"
Then I should see "Nothing to display"
And I log out
Scenario: Editing tag collections
When I follow "Edit tag collection Hobbies"
And I set the following fields to these values:
| Name | Newname |
And I press "Save changes"
Then I should not see "Hobbies"
And I should see "Newname"
And I log out
Scenario: Resorting tag collections
When I follow "Add tag collection"
And I set the following fields to these values:
| Name | Blogging |
And I press "Save changes"
Then "Blogging" "link" should appear after "Hobbies" "link"
And I click on "Move up" "link" in the "Blogging" "table_row"
And "Blogging" "link" should appear before "Hobbies" "link"
And I click on "Move down" "link" in the "Blogging" "table_row"
And "Blogging" "link" should appear after "Hobbies" "link"
And I log out
Scenario: Deleting tag collections
When I click on "Delete" "link" in the "Hobbies" "table_row"
Then I should see "Are you sure you want to delete tag collection \"Hobbies\"?"
And I press "Yes"
And I should not see "Hobbies"
And I log out
@javascript
Scenario: Assigning tag area to tag collection
And "User interests" "text" should exist in the "//table[contains(@class,'tag-collections-table')]//tr[contains(.,'Default collection')]" "xpath_element"
And "User interests" "text" should not exist in the "//table[contains(@class,'tag-collections-table')]//tr[contains(.,'Hobbies')]" "xpath_element"
When I set the field "Change tag collection of area User interests" to "Hobbies"
Then "User interests" "text" should not exist in the "//table[contains(@class,'tag-collections-table')]//tr[contains(.,'Default collection')]" "xpath_element"
And "User interests" "text" should exist in the "//table[contains(@class,'tag-collections-table')]//tr[contains(.,'Hobbies')]" "xpath_element"
And the field "Change tag collection of area User interests" matches value "Hobbies"
And I log out
Scenario: Disabling tag areas
And "Change tag collection of area User interests" "select" should exist
When I click on "Disable" "link" in the "//table[contains(@class,'tag-areas-table')]//tr[contains(.,'User interests')]" "xpath_element"
Then "Change tag collection of area User interests" "select" should not exist
And "User interests" "text" should not exist in the "table.tag-collections-table" "css_element"
And I click on "Enable" "link" in the "//table[contains(@class,'tag-areas-table')]//tr[contains(.,'User interests')]" "xpath_element"
And "User interests" "text" should exist in the "//table[contains(@class,'tag-collections-table')]//tr[contains(.,'Default collection')]" "xpath_element"
And "Change tag collection of area User interests" "select" should exist
And I log out
Scenario: Deleting non-empty tag collections
When I set the field "Change tag collection of area User interests" to "Hobbies"
And I click on "Delete" "link" in the "Hobbies" "table_row"
Then I should see "Are you sure you want to delete tag collection \"Hobbies\"?"
And I press "Yes"
And I should not see "Hobbies"
And "User interests" "text" should exist in the "//table[contains(@class,'tag-collections-table')]//tr[contains(.,'Default collection')]" "xpath_element"
And I log out
@javascript
Scenario: Moving tags when changing tag collections
And I follow "Preferences" in the user menu
And I follow "Edit profile"
And I expand all fieldsets
And I set the field "List of interests" to "Swimming, Tag0, Tag3"
And I press "Update profile"
And I navigate to "Manage tags" node in "Site administration > Appearance"
When I set the field "Change tag collection of area User interests" to "Hobbies"
And I follow "Hobbies"
Then I should see "Swimming"
And I should see "Tag0"
And I should see "Tag3"
And I should not see "Tag1"
And I should not see "Tag2"
And I follow "Manage tags"
And I follow "Default collection"
# Tag "Swimming" was not official and was moved completely.
And I should not see "Swimming"
# Official tag was not removed.
And I should see "Tag0"
And I should see "Tag3"
And I should see "Tag1"
And I should see "Tag2"
And I log out
@javascript
Scenario: Creating searchable and non-searchable tag collections
And I follow "Add tag collection"
And I set the following fields to these values:
| Name | Hiddencoll |
| Searchable | 0 |
And I press "Save changes"
And "Yes" "text" should not exist in the "//table[contains(@class,'tag-collections-table')]//tr[contains(.,'Hiddencoll')]" "xpath_element"
And I navigate to "Tags" node in "Site pages"
Then the "Select tag collection" select box should contain "Default collection"
And the "Select tag collection" select box should contain "Hobbies"
And the "Select tag collection" select box should not contain "Hiddencoll"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I click on "Edit" "link" in the "Hobbies" "table_row"
And I set the following fields to these values:
| Searchable | 0 |
And I press "Save changes"
And I navigate to "Tags" node in "Site pages"
And "Select tag collection" "select" should not exist
And I log out
+6 -2
View File
@@ -19,6 +19,7 @@ Feature: Manager is able to delete tags
Scenario: Deleting a tag with javascript disabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I click on "Delete" "link" in the "Dog" "table_row"
And I should see "Tag(s) deleted"
Then I should not see "Dog"
@@ -31,6 +32,7 @@ Feature: Manager is able to delete tags
Scenario: Deleting multiple tags with javascript disabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I set the following fields to these values:
| Select tag Dog | 1 |
| Select tag Neverusedtag | 1 |
@@ -48,6 +50,7 @@ Feature: Manager is able to delete tags
Scenario: Deleting a tag with javascript enabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I click on "Delete" "link" in the "Turtle" "table_row"
Then I should see "Are you sure you want to delete this tag?"
And I press "No"
@@ -58,7 +61,7 @@ Feature: Manager is able to delete tags
And I press "Yes"
And I should see "Tag(s) deleted"
And I should not see "Dog"
And I follow "Manage tags"
And I follow "Default collection"
And I should not see "Dog"
And I navigate to "Participants" node in "Site pages"
And I follow "User 1"
@@ -70,6 +73,7 @@ Feature: Manager is able to delete tags
Scenario: Deleting multiple tags with javascript enabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I press "Delete selected"
And I should not see "Are you sure"
And I should not see "Tag(s) deleted"
@@ -90,7 +94,7 @@ Feature: Manager is able to delete tags
And I should see "Tag(s) deleted"
And I should not see "Dog"
And I should not see "Neverusedtag"
And I follow "Manage tags"
And I follow "Default collection"
And I should not see "Dog"
And I should not see "Neverusedtag"
And I navigate to "Participants" node in "Site pages"
+30 -16
View File
@@ -42,8 +42,11 @@ Feature: Users can edit tags to add description or rename
| Related tags | Dog, Turtle,Fish |
And I press "Update"
Then "Cat" "text" should exist in the ".breadcrumb-nav" "css_element"
And "Description of tag 1" "text" should exist in the "#tag-description" "css_element"
And I should see "Related tags: Dog, Turtle, Fish"
And "Description of tag 1" "text" should exist in the ".tag-description" "css_element"
And I should see "Related tags:" in the ".tag_list" "css_element"
And I should see "Dog" in the ".tag_list" "css_element"
And I should see "Turtle" in the ".tag_list" "css_element"
And I should see "Fish" in the ".tag_list" "css_element"
And I log out
@javascript
@@ -60,14 +63,19 @@ Feature: Users can edit tags to add description or rename
| Official | 0 |
And I press "Update"
Then "Kitten" "text" should exist in the ".breadcrumb-nav" "css_element"
And "Description of tag 1" "text" should exist in the "#tag-description" "css_element"
And I should see "Related tags: Dog, Turtle, Fish"
And "Description of tag 1" "text" should exist in the ".tag-description" "css_element"
And I should see "Related tags:" in the ".tag_list" "css_element"
And I should see "Dog" in the ".tag_list" "css_element"
And I should see "Turtle" in the ".tag_list" "css_element"
And I should see "Fish" in the ".tag_list" "css_element"
And I follow "Edit this tag"
And I click on "× Dog" "text"
And I press "Update"
Then "Kitten" "text" should exist in the ".breadcrumb-nav" "css_element"
And "Description of tag 1" "text" should exist in the "#tag-description" "css_element"
And I should see "Related tags: Turtle, Fish"
And "Description of tag 1" "text" should exist in the ".tag-description" "css_element"
And I should see "Related tags:" in the ".tag_list" "css_element"
And I should see "Turtle" in the ".tag_list" "css_element"
And I should see "Fish" in the ".tag_list" "css_element"
And I should not see "Dog"
And I log out
@@ -80,7 +88,7 @@ Feature: Users can edit tags to add description or rename
And I set the following fields to these values:
| Tag name | DOG |
And I press "Update"
And I should see "DOG: Tag names already being used"
And I should see "Tag names already being used"
And I set the following fields to these values:
| Tag name | Kitten |
And I press "Update"
@@ -96,6 +104,7 @@ Feature: Users can edit tags to add description or rename
Scenario: Manager can change tag description and rename the tag from tag manage page
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I click on "Edit this tag" "link" in the "Cat" "table_row"
And I set the following fields to these values:
| Tag name | Kitten |
@@ -103,29 +112,33 @@ Feature: Users can edit tags to add description or rename
| Related tags | Dog, Turtle,Fish |
| Official | 0 |
And I press "Update"
Then "Manage tags" "link" should exist in the ".breadcrumb-nav" "css_element"
Then "Default collection" "link" should exist in the ".breadcrumb-nav" "css_element"
And I follow "Kitten"
And "Description of tag 1" "text" should exist in the "#tag-description" "css_element"
And I should see "Related tags: Dog, Turtle, Fish"
And "Description of tag 1" "text" should exist in the ".tag-description" "css_element"
And I should see "Related tags:" in the ".tag_list" "css_element"
And I should see "Dog" in the ".tag_list" "css_element"
And I should see "Turtle" in the ".tag_list" "css_element"
And I should see "Fish" in the ".tag_list" "css_element"
And I log out
Scenario: Renaming the tag in edit tag form from tag manage page
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I click on "Edit this tag" "link" in the "Cat" "table_row"
And I set the following fields to these values:
| Tag name | DOG |
And I press "Update"
And I should see "DOG: Tag names already being used"
And I should see "Tag names already being used"
And I set the following fields to these values:
| Tag name | Kitten |
And I press "Update"
Then "Manage tags" "text" should exist in the ".breadcrumb-nav" "css_element"
Then "Default collection" "text" should exist in the ".breadcrumb-nav" "css_element"
And I click on "Edit this tag" "link" in the "Kitten" "table_row"
And I set the following fields to these values:
| Tag name | KITTEN |
And I press "Update"
And "Manage tags" "text" should exist in the ".breadcrumb-nav" "css_element"
And "Default collection" "text" should exist in the ".breadcrumb-nav" "css_element"
And I should see "KITTEN"
And I should not see "Kitten"
And I log out
@@ -134,6 +147,7 @@ Feature: Users can edit tags to add description or rename
Scenario: Renaming the tag using quick edit field on tag manage page
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
# Renaming tag to a valid name
And I click on "Edit tag name" "link" in the "Cat" "table_row"
And I set the field "New name for tag Cat" to "Kitten"
@@ -141,7 +155,7 @@ Feature: Users can edit tags to add description or rename
Then I should not see "Cat"
And "New name for tag" "field" should not be visible
And I wait until "Kitten" "link" exists
And I follow "Manage tags"
And I follow "Default collection"
And I should see "Kitten"
And I should not see "Cat"
# Renaming tag to an invalid name
@@ -154,7 +168,7 @@ Feature: Users can edit tags to add description or rename
And I should see "Turtle"
And I should see "Dog"
And I should not see "DOG"
And I follow "Manage tags"
And I follow "Default collection"
And I should see "Turtle"
And I should see "Dog"
And I should not see "DOG"
@@ -165,7 +179,7 @@ Feature: Users can edit tags to add description or rename
And "New name for tag" "field" should not be visible
And I should see "Turtle"
And I should not see "Penguin"
And I follow "Manage tags"
And I follow "Default collection"
And I should see "Turtle"
And I should not see "Penguin"
And I log out
+3 -1
View File
@@ -49,6 +49,7 @@ Feature: Users can flag tags and manager can reset flags
Scenario: Managing tag flags with javascript disabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
Then "Sweartag" "link" should appear before "Badtag" "link"
And "Badtag" "link" should appear before "Nicetag" "link"
And "(2)" "text" should exist in the "//tr[contains(.,'Sweartag')]//td[contains(@class,'col-flag')]" "xpath_element"
@@ -69,6 +70,7 @@ Feature: Users can flag tags and manager can reset flags
Scenario: Managing tag flags with javascript enabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
Then "Sweartag" "link" should appear before "Badtag" "link"
And "Badtag" "link" should appear before "Nicetag" "link"
And "(2)" "text" should exist in the "//tr[contains(.,'Sweartag')]//td[contains(@class,'col-flag')]" "xpath_element"
@@ -84,7 +86,7 @@ Feature: Users can flag tags and manager can reset flags
And "(1)" "text" should exist in the "//tr[contains(.,'Nicetag')]//td[contains(@class,'col-flag')]" "xpath_element"
And "(" "text" should not exist in the "//tr[contains(.,'Badtag')]//td[contains(@class,'col-flag')]" "xpath_element"
And "(" "text" should not exist in the "//tr[contains(.,'Neverusedtag')]//td[contains(@class,'col-flag')]" "xpath_element"
And I follow "Manage tags"
And I follow "Default collection"
And "Nicetag" "link" should appear before "Sweartag" "link"
And "Sweartag" "link" should appear before "Badtag" "link"
And "(1)" "text" should exist in the "//tr[contains(.,'Sweartag')]//td[contains(@class,'col-flag')]" "xpath_element"
+5 -1
View File
@@ -22,6 +22,7 @@ Feature: Manager can add official tags and change the tag type of existing tags
Scenario: Adding official tags
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
Then "Make official" "link" should exist in the "Tag0" "table_row"
And "Make official" "link" should exist in the "Tag1" "table_row"
And "Make official" "link" should exist in the "Tag2" "table_row"
@@ -47,6 +48,7 @@ Feature: Manager can add official tags and change the tag type of existing tags
Scenario: Changing tag type with javascript disabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I click on "Make official" "link" in the "Tag0" "table_row"
And I should see "Tag type changed"
And I click on "Make official" "link" in the "Tag1" "table_row"
@@ -65,6 +67,7 @@ Feature: Manager can add official tags and change the tag type of existing tags
Scenario: Changing tag type with javascript enabled
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I click on "Make official" "link" in the "Tag0" "table_row"
And I click on "Make official" "link" in the "Tag1" "table_row"
And I wait until "//tr[contains(.,'Tag0')]//a[contains(@title,'Remove from official tags')]" "xpath_element" exists
@@ -77,7 +80,7 @@ Feature: Manager can add official tags and change the tag type of existing tags
And "Remove from official tags" "link" should exist in the "Tag1" "table_row"
And "Make official" "link" should exist in the "Tag2" "table_row"
And "Make official" "link" should exist in the "Tag3" "table_row"
And I follow "Manage tags"
And I follow "Default collection"
And "Make official" "link" should exist in the "Tag0" "table_row"
And "Remove from official tags" "link" should exist in the "Tag1" "table_row"
And "Make official" "link" should exist in the "Tag2" "table_row"
@@ -87,6 +90,7 @@ Feature: Manager can add official tags and change the tag type of existing tags
Scenario: Changing tag type in edit form
When I log in as "manager1"
And I navigate to "Manage tags" node in "Site administration > Appearance"
And I follow "Default collection"
And I click on "Edit this tag" "link" in the "Tag1" "table_row"
And I set the following fields to these values:
| Official | 1 |
+105
View File
@@ -0,0 +1,105 @@
@core @core_tag
Feature: Browsing tagged items
In order to search by tag
As a user
I need to be able to browse tagged items
Background:
Given the following "users" exist:
| username | firstname | lastname | email | interests |
| user1 | User | 1 | user1@example.com | Cat |
| user2 | User | 2 | user1@example.com | Cat, Dog |
| user3 | User | 3 | user1@example.com | Dog |
And the following "courses" exist:
| fullname | shortname | tags |
| Course 1 | c1 | Cat, Dog |
| Course 2 | c2 | Cat |
| Course 3 | c3 | Cat |
| Course 4 | c4 | Cat |
| Course 5 | c5 | Cat |
| Course 6 | c6 | Cat |
| Course 7 | c7 | Cat |
Scenario: Browse tag index with javascript disabled
When I log in as "user1"
And I navigate to "Tags" node in "Site pages"
And I follow "Cat"
Then I should see "Courses" in the ".tag-index-items h3" "css_element"
And I should see "User 1" in the "#tagarea-core-user" "css_element"
And I should see "Course 7"
And I should see "Course 3"
And I should not see "Course 2"
And I should not see "Course 1"
And I should see "User 1" in the "#tagarea-core-user" "css_element"
And I should see "User 2"
And I should not see "User 3"
And I click on "More" "link" in the "#tagarea-core-course" "css_element"
And I should see "Courses" in the "#tagarea-core-course" "css_element"
And "#tagarea-core-user" "css_element" should not exist
And I should not see "Course 7"
And I should not see "Course 3"
And I should see "Course 2"
And I should see "Course 1"
And I click on "Back" "link" in the "#tagarea-core-course" "css_element"
And I should see "Courses" in the ".tag-index-items h3" "css_element"
And "#tagarea-core-user" "css_element" should not exist
And I should see "Course 7"
And I should see "Course 3"
And I should not see "Course 2"
And I should not see "Course 1"
And I follow "Show only tagged Courses"
And I should see "Courses tagged with \"Cat\""
And "#tagarea-core-user" "css_element" should not exist
And I should see "Course 7"
And I should see "Course 3"
And I should see "Course 2"
And I should see "Course 1"
And I follow "Back to all items tagged with \"Cat\""
And I should see "Courses" in the "#tagarea-core-course" "css_element"
And I should see "User interests" in the "#tagarea-core-user" "css_element"
And I should see "Course 7"
And I should see "Course 3"
And I should not see "Course2"
And I should not see "Course1"
And I log out
@javascript
Scenario: Browse tag index with javascript enabled
When I log in as "user1"
And I navigate to "Tags" node in "Site pages"
And I follow "Cat"
Then I should see "Courses" in the "#tagarea-core-course" "css_element"
And I should see "User interests" in the "#tagarea-core-user" "css_element"
And I should see "Course 7"
And I should see "Course 3"
And I should not see "Course 2"
And I should not see "Course 1"
And I click on "More" "link" in the "#tagarea-core-course" "css_element"
And I should see "Courses" in the "#tagarea-core-course" "css_element"
And I should see "User interests" in the "#tagarea-core-user" "css_element"
And I should not see "Course 7"
And I should not see "Course 3"
And I should see "Course 2"
And I should see "Course 1"
And I click on "Back" "link" in the "#tagarea-core-course" "css_element"
And I should see "Courses" in the "#tagarea-core-course" "css_element"
And I should see "User interests" in the "#tagarea-core-user" "css_element"
And I should see "Course 7"
And I should see "Course 3"
And I should not see "Course 2"
And I should not see "Course 1"
And I follow "Show only tagged Courses"
And I should see "Courses" in the "#tagarea-core-course" "css_element"
And "#tagarea-core-user" "css_element" should not exist
And I should see "Course 7"
And I should see "Course 3"
And I should see "Course 2"
And I should see "Course 1"
And I follow "Back to all items tagged with \"Cat\""
And I should see "Courses" in the "#tagarea-core-course" "css_element"
And I should see "User interests" in the "#tagarea-core-user" "css_element"
And I should see "Course 7"
And I should see "Course 3"
And I should not see "Course2"
And I should not see "Course1"
And I log out
+96 -12
View File
@@ -27,7 +27,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/tag/lib.php');
// Used to create a wiki page to tag.
require_once($CFG->dirroot . '/mod/wiki/locallib.php');
@@ -61,6 +60,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event when renaming a tag.
$sink = $this->redirectEvents();
tag_rename($tag->id, 'newname');
$this->assertDebuggingCalled();
// Update the tag's name since we have renamed it.
$tag->name = 'newname';
$events = $sink->get_events();
@@ -75,6 +75,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event when setting the type of a tag.
$sink = $this->redirectEvents();
tag_type_set($tag->id, 'official');
$this->assertDebuggingCalled();
$events = $sink->get_events();
$event = reset($events);
@@ -87,6 +88,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event for setting the description of a tag.
$sink = $this->redirectEvents();
tag_description_set($tag->id, 'description', FORMAT_MOODLE);
$this->assertDebuggingCalled();
$events = $sink->get_events();
$event = reset($events);
@@ -108,7 +110,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event for tagging a course.
$sink = $this->redirectEvents();
tag_set('course', $course->id, array('A tag'), 'core', context_course::instance($course->id)->id);
core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), array('A tag'));
$events = $sink->get_events();
$event = $events[1];
@@ -127,7 +129,8 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event for tagging a question.
$this->assertEquals(1, $DB->count_records('tag_instance'));
$sink = $this->redirectEvents();
tag_set('question', $question->id, array('A tag'), 'core_question', $cat->contextid);
core_tag_tag::set_item_tags('core_question', 'question', $question->id,
context::instance_by_id($cat->contextid), array('A tag'));
$events = $sink->get_events();
$event = reset($events);
@@ -161,6 +164,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Assign a tag to a course.
tag_assign('course', $course->id, $tag->id, 1, 2, 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Trigger and capture the event for untagging a course.
$sink = $this->redirectEvents();
@@ -179,10 +183,12 @@ class core_tag_events_testcase extends advanced_testcase {
// Assign a tag to a wiki this time.
tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id);
$this->assertDebuggingCalled();
// Trigger and capture the event for deleting this tag instance.
$sink = $this->redirectEvents();
tag_delete_instance('wiki_pages', $wikipageid, $tag->id);
$this->assertDebuggingCalled();
$events = $sink->get_events();
$event = reset($events);
@@ -196,11 +202,13 @@ class core_tag_events_testcase extends advanced_testcase {
// Assign a tag to the wiki again.
tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id);
$this->assertDebuggingCalled();
// Now we want to delete this tag, and because there is only one tag instance
// associated with it, it should get deleted as well.
$sink = $this->redirectEvents();
tag_delete($tag->id);
$this->assertDebuggingCalled();
$events = $sink->get_events();
$event = reset($events);
@@ -214,10 +222,11 @@ class core_tag_events_testcase extends advanced_testcase {
// Assign a tag to the wiki again.
tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id);
$this->assertDebuggingCalled();
// Delete all tag instances for this wiki instance.
$sink = $this->redirectEvents();
tag_delete_instances('mod_wiki', context_module::instance($wiki->cmid)->id);
core_tag_tag::delete_instances('mod_wiki', 'wiki_pages', context_module::instance($wiki->cmid)->id);
$events = $sink->get_events();
$event = reset($events);
@@ -233,11 +242,13 @@ class core_tag_events_testcase extends advanced_testcase {
// Assign a tag to both wiki pages.
tag_assign('wiki_pages', $wikipageid, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki->cmid)->id);
$this->assertDebuggingCalled();
tag_assign('wiki_pages', $wikipageid2, $tag->id, 1, 2, 'mod_wiki', context_module::instance($wiki2->cmid)->id);
$this->assertDebuggingCalled();
// Now remove all tag_instances associated with all wikis.
$sink = $this->redirectEvents();
tag_delete_instances('mod_wiki');
core_tag_tag::delete_instances('mod_wiki');
$events = $sink->get_events();
// There will be two events - one for each wiki instance removed.
@@ -271,6 +282,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event for setting the flag of a tag.
$sink = $this->redirectEvents();
tag_set_flag($tag->id);
$this->assertDebuggingCalled();
$events = $sink->get_events();
$event = reset($events);
@@ -286,10 +298,12 @@ class core_tag_events_testcase extends advanced_testcase {
// Unset the flag for both (though by default tag2 should have been created with 0 already).
tag_unset_flag(array($tag->id, $tag2->id));
$this->assertDebuggingCalled();
// Trigger and capture the event for setting the flag for multiple tags.
$sink = $this->redirectEvents();
tag_set_flag(array($tag->id, $tag2->id));
$this->assertDebuggingCalled();
$events = $sink->get_events();
// Check that the flags were updated.
@@ -326,10 +340,12 @@ class core_tag_events_testcase extends advanced_testcase {
// Flag it.
tag_set_flag($tag->id);
$this->assertDebuggingCalled();
// Trigger and capture the event for unsetting the flag of a tag.
$sink = $this->redirectEvents();
tag_unset_flag($tag->id);
$this->assertDebuggingCalled();
$events = $sink->get_events();
$event = reset($events);
@@ -343,10 +359,12 @@ class core_tag_events_testcase extends advanced_testcase {
// Set the flag back for both.
tag_set_flag(array($tag->id, $tag2->id));
$this->assertDebuggingCalled();
// Trigger and capture the event for unsetting the flag for multiple tags.
$sink = $this->redirectEvents();
tag_unset_flag(array($tag->id, $tag2->id));
$this->assertDebuggingCalled();
$events = $sink->get_events();
// Check that the flags were updated.
@@ -366,15 +384,16 @@ class core_tag_events_testcase extends advanced_testcase {
}
/**
* Test the tag deleted event.
* Test the tag deleted event
*/
public function test_tag_deleted() {
global $DB;
$this->setAdminUser();
// Create a course.
// Create a course and a user.
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
// Create tag we are going to delete.
$tag = $this->getDataGenerator()->create_tag();
@@ -382,6 +401,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event for deleting a tag.
$sink = $this->redirectEvents();
tag_delete($tag->id);
$this->assertDebuggingCalled();
$events = $sink->get_events();
$event = reset($events);
@@ -397,6 +417,7 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the events for deleting multiple tags.
$sink = $this->redirectEvents();
tag_delete(array($tag->id, $tag2->id));
$this->assertDebuggingCalled();
$events = $sink->get_events();
// Check that the tags were deleted and the events data is valid.
@@ -406,11 +427,70 @@ class core_tag_events_testcase extends advanced_testcase {
$this->assertEquals(context_system::instance(), $event->get_context());
}
// Add a tag instance to a course.
core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'cat', $user->id);
// Trigger and capture the event for deleting a personal tag for a user for a course.
$sink = $this->redirectEvents();
core_tag_tag::remove_item_tag('core', 'course', $course->id, 'cat', $user->id);
$events = $sink->get_events();
$event = $events[1];
// Check that the tag was deleted and the event data is valid.
$this->assertEquals(0, $DB->count_records('tag'));
$this->assertInstanceOf('\core\event\tag_deleted', $event);
$this->assertEquals(context_system::instance(), $event->get_context());
// Add the tag instance to the course again as it was deleted.
core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'dog', $user->id);
// Trigger and capture the event for deleting all tags in a course.
$sink = $this->redirectEvents();
core_tag_tag::remove_all_item_tags('core', 'course', $course->id);
$events = $sink->get_events();
$event = $events[1];
// Check that the tag was deleted and the event data is valid.
$this->assertEquals(0, $DB->count_records('tag'));
$this->assertInstanceOf('\core\event\tag_deleted', $event);
$this->assertEquals(context_system::instance(), $event->get_context());
// Add multiple tag instances now and check that it still works.
core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id),
array('fish', 'hamster'), $user->id);
// Trigger and capture the event for deleting all tags in a course.
$sink = $this->redirectEvents();
core_tag_tag::remove_all_item_tags('core', 'course', $course->id);
$events = $sink->get_events();
$events = array($events[1], $events[3]);
// Check that the tags were deleted and the events data is valid.
$this->assertEquals(0, $DB->count_records('tag'));
foreach ($events as $event) {
$this->assertInstanceOf('\core\event\tag_deleted', $event);
$this->assertEquals(context_system::instance(), $event->get_context());
}
}
/**
* Test the tag deleted event while calling deprecated functions.
* Remove the test when the functions are removed.
*/
public function test_tag_deleted_deprecated() {
global $DB;
$this->setAdminUser();
// Create a course.
$course = $this->getDataGenerator()->create_course();
// Create another tag to delete.
$tag = $this->getDataGenerator()->create_tag();
// Add a tag instance to a course.
tag_assign('course', $course->id, $tag->id, 0, 2, 'course', context_course::instance($course->id)->id);
tag_assign('course', $course->id, $tag->id, 0, 2, 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Trigger and capture the event for deleting a personal tag for a user for a course.
$sink = $this->redirectEvents();
@@ -428,7 +508,8 @@ class core_tag_events_testcase extends advanced_testcase {
$tag = $this->getDataGenerator()->create_tag();
// Add the tag instance to the course again as it was deleted.
tag_assign('course', $course->id, $tag->id, 0, 2, 'course', context_course::instance($course->id)->id);
tag_assign('course', $course->id, $tag->id, 0, 2, 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Trigger and capture the event for deleting all tags in a course.
$sink = $this->redirectEvents();
@@ -447,8 +528,10 @@ class core_tag_events_testcase extends advanced_testcase {
$tag2 = $this->getDataGenerator()->create_tag();
// Add multiple tag instances now and check that it still works.
tag_assign('course', $course->id, $tag->id, 0, 2, 'course', context_course::instance($course->id)->id);
tag_assign('course', $course->id, $tag2->id, 0, 2, 'course', context_course::instance($course->id)->id);
tag_assign('course', $course->id, $tag->id, 0, 2, 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
tag_assign('course', $course->id, $tag2->id, 0, 2, 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Trigger and capture the event for deleting all tags in a course.
$sink = $this->redirectEvents();
@@ -473,7 +556,8 @@ class core_tag_events_testcase extends advanced_testcase {
// Trigger and capture the event for creating a tag.
$sink = $this->redirectEvents();
tag_add('A really awesome tag!');
core_tag_tag::create_if_missing(core_tag_area::get_collection('core', 'course'),
array('A really awesome tag!'));
$events = $sink->get_events();
$event = reset($events);
-1
View File
@@ -27,7 +27,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/tag/lib.php');
require_once($CFG->libdir . '/externallib.php');
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+510 -76
View File
@@ -27,8 +27,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/tag/lib.php');
class core_tag_taglib_testcase extends advanced_testcase {
/**
@@ -42,8 +40,9 @@ class core_tag_taglib_testcase extends advanced_testcase {
/**
* Test the tag_set function.
* This function was deprecated in 3.1
*/
public function test_tag_set() {
public function test_tag_set_get() {
global $DB;
// Create a course to tag.
@@ -51,20 +50,45 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Create the tag and tag instance.
tag_set('course', $course->id, array('A random tag'), 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Get the tag instance that should have been created.
$taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course->id), '*', MUST_EXIST);
$this->assertEquals('core', $taginstance->component);
$this->assertEquals(context_course::instance($course->id)->id, $taginstance->contextid);
// Now call the tag_set function without specifying the component or contextid and
// ensure the function debugging is called.
tag_set('course', $course->id, array('Another tag'));
$tagbyname = tag_get('name', 'A random tag');
$this->assertDebuggingCalled();
$this->assertEquals('A random tag', $tagbyname->rawname);
$this->assertEmpty(tag_get('name', 'Non existing tag'));
$this->assertDebuggingCalled();
$tagbyid = tag_get('id', $tagbyname->id);
$this->assertDebuggingCalled();
$this->assertEquals('A random tag', $tagbyid->rawname);
$tagid = $tagbyname->id;
$this->assertEmpty(tag_get('id', $tagid + 1));
$this->assertDebuggingCalled();
tag_set('tag', $tagid, array('Some related tag'));
$this->assertDebuggingCalled();
$relatedtags = tag_get_related_tags($tagid);
$this->assertDebuggingCalled();
$this->assertCount(1, $relatedtags);
$this->assertEquals('Some related tag', $relatedtags[0]->rawname);
$tagids = tag_get_id(array('A random tag', 'Some related tag'));
$this->assertDebuggingCalled();
$this->assertCount(2, $tagids);
$this->assertEquals($tagid, $tagids['a random tag']);
$this->assertEquals($relatedtags[0]->id, $tagids['some related tag']);
}
/**
* Test the tag_set_add function.
* This function was deprecated in 3.1
*/
public function test_tag_set_add() {
global $DB;
@@ -74,24 +98,25 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Create the tag and tag instance.
tag_set_add('course', $course->id, 'A random tag', 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Get the tag instance that should have been created.
$taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course->id), '*', MUST_EXIST);
$this->assertEquals('core', $taginstance->component);
$this->assertEquals(context_course::instance($course->id)->id, $taginstance->contextid);
$tagid = $taginstance->tagid;
// Remove the tag we just created.
$tag = $DB->get_record('tag', array('rawname' => 'A random tag'));
tag_delete($tag->id);
// Now call the tag_set_add function without specifying the component or
// contextid and ensure the function debugging is called.
tag_set_add('course', $course->id, 'Another tag');
tag_set_add('tag', $tagid, 'Some related tag');
$this->assertDebuggingCalled();
$relatedtags = tag_get_related_tags($tagid);
$this->assertDebuggingCalled();
$this->assertCount(1, $relatedtags);
$this->assertEquals('Some related tag', $relatedtags[0]->rawname);
}
/**
* Test the tag_set_delete function.
* This function was deprecated in 3.1
*/
public function test_tag_set_delete() {
global $DB;
@@ -101,25 +126,56 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Create the tag and tag instance we are going to delete.
tag_set_add('course', $course->id, 'A random tag', 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Call the tag_set_delete function.
tag_set_delete('course', $course->id, 'a random tag', 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Now check that there are no tags or tag instances.
$this->assertEquals(0, $DB->count_records('tag'));
$this->assertEquals(0, $DB->count_records('tag_instance'));
// Recreate the tag and tag instance.
// Add tag again, add and remove related tag.
tag_set_add('course', $course->id, 'A random tag', 'core', context_course::instance($course->id)->id);
// Now call the tag_set_delete function without specifying the component or
// contextid and ensure the function debugging is called.
tag_set_delete('course', $course->id, 'A random tag');
$this->assertDebuggingCalled();
$taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course->id), '*', MUST_EXIST);
$tagid = $taginstance->tagid;
tag_set_add('tag', $tagid, 'Some related tag');
$this->assertDebuggingCalled();
tag_set_delete('tag', $tagid, 'Some related tag');
$this->assertDebuggingCalled();
$relatedtags = tag_get_related_tags($tagid);
$this->assertDebuggingCalled();
$this->assertCount(0, $relatedtags);
}
/**
* Test the core_tag_tag::add_item_tag() and core_tag_tag::remove_item_tag() functions.
*/
public function test_add_remove_item_tag() {
global $DB;
// Create a course to tag.
$course = $this->getDataGenerator()->create_course();
// Create the tag and tag instance we are going to delete.
core_tag_tag::add_item_tag('core', 'course', $course->id, context_course::instance($course->id), 'A random tag');
$this->assertEquals(1, $DB->count_records('tag'));
$this->assertEquals(1, $DB->count_records('tag_instance'));
// Call the tag_set_delete function.
core_tag_tag::remove_item_tag('core', 'course', $course->id, 'A random tag');
// Now check that there are no tags or tag instances.
$this->assertEquals(0, $DB->count_records('tag'));
$this->assertEquals(0, $DB->count_records('tag_instance'));
}
/**
* Test the tag_assign function.
* This function was deprecated in 3.1
*/
public function test_tag_assign() {
global $DB;
@@ -129,9 +185,11 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Create the tag.
$tag = $this->getDataGenerator()->create_tag();
$tag2 = $this->getDataGenerator()->create_tag();
// Tag the course with the tag we created.
tag_assign('course', $course->id, $tag->id, 0, 0, 'core', context_course::instance($course->id)->id);
$this->assertDebuggingCalled();
// Get the tag instance that should have been created.
$taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course->id), '*', MUST_EXIST);
@@ -139,8 +197,8 @@ class core_tag_taglib_testcase extends advanced_testcase {
$this->assertEquals(context_course::instance($course->id)->id, $taginstance->contextid);
// Now call the tag_assign function without specifying the component or
// contextid and ensure the function debugging is called.
tag_assign('course', $course->id, $tag->id, 0, 0);
// contextid and ensure the function debugging is called twice.
tag_assign('course', $course->id, $tag2->id, 0, 0);
$this->assertDebuggingCalled();
}
@@ -150,6 +208,8 @@ class core_tag_taglib_testcase extends advanced_testcase {
public function test_tag_cleanup() {
global $DB;
$task = new \core\task\tag_cron_task();
// Create some users.
$users = array();
for ($i = 0; $i < 10; $i++) {
@@ -162,24 +222,26 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Test clean up instances with tags that no longer exist.
$tags = array();
$tagnames = array();
for ($i = 0; $i < 10; $i++) {
$tags[] = $this->getDataGenerator()->create_tag(array('userid' => $users[0]->id));
$tags[] = $tag = $this->getDataGenerator()->create_tag(array('userid' => $users[0]->id));
$tagnames[] = $tag->rawname;
}
// Create instances with the tags.
foreach ($tags as $tag) {
tag_assign('course', $course->id, $tag->id, 0, 0, 'core', $context->id);
}
core_tag_tag::set_item_tags('core', 'course', $course->id, $context, $tagnames);
// We should now have ten tag instances.
$coursetaginstances = $DB->count_records('tag_instance', array('itemtype' => 'course'));
$this->assertEquals(10, $coursetaginstances);
// Delete four tags
// Manual delete of tags is done as the function will remove the instances as well.
$DB->delete_records('tag', array('id' => $tags[6]->id));
$DB->delete_records('tag', array('id' => $tags[7]->id));
$DB->delete_records('tag', array('id' => $tags[8]->id));
$DB->delete_records('tag', array('id' => $tags[9]->id));
// Clean up the tags.
tag_cleanup();
$task->cleanup();
// Check that we now only have six tag_instance records left.
$coursetaginstances = $DB->count_records('tag_instance', array('itemtype' => 'course'));
$this->assertEquals(6, $coursetaginstances);
@@ -187,7 +249,8 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Test clean up with users that have been deleted.
// Create a tag for this course.
foreach ($users as $user) {
tag_assign('user', $user->id, $tags[0]->id, 0, 0, 'core', $context->id);
$context = context_user::instance($user->id);
core_tag_tag::set_item_tags('core', 'user', $user->id, $context, array($tags[0]->rawname));
}
$usertags = $DB->count_records('tag_instance', array('itemtype' => 'user'));
$this->assertCount($usertags, $users);
@@ -196,8 +259,9 @@ class core_tag_taglib_testcase extends advanced_testcase {
$DB->update_record('user', array('id' => $users[4]->id, 'deleted' => 1));
$DB->update_record('user', array('id' => $users[5]->id, 'deleted' => 1));
$DB->update_record('user', array('id' => $users[6]->id, 'deleted' => 1));
// Clean up the tags.
tag_cleanup();
$task->cleanup();
$usertags = $DB->count_records('tag_instance', array('itemtype' => 'user'));
$usercount = $DB->count_records('user', array('deleted' => 0));
// Remove admin and guest from the count.
@@ -206,7 +270,7 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Test clean up where a course has been removed.
// Delete the course. This also needs to be this way otherwise the tags are removed by using the proper function.
$DB->delete_records('course', array('id' => $course->id));
tag_cleanup();
$task->cleanup();
$coursetags = $DB->count_records('tag_instance', array('itemtype' => 'course'));
$this->assertEquals(0, $coursetags);
@@ -216,14 +280,16 @@ class core_tag_taglib_testcase extends advanced_testcase {
$post->userid = $users[1]->id;
$post->content = 'test post content text';
$post->id = $DB->insert_record('post', $post);
tag_assign('post', $post->id, $tags[0]->id, 0, 0, 'core', $context->id);
$context = context_system::instance();
core_tag_tag::set_item_tags('core', 'post', $post->id, $context, array($tags[0]->rawname));
// Add another one with a fake post id to be removed.
tag_assign('post', 15, $tags[0]->id, 0, 0, 'core', $context->id);
core_tag_tag::set_item_tags('core', 'post', 15, $context, array($tags[0]->rawname));
// Check that there are two tag instances.
$posttags = $DB->count_records('tag_instance', array('itemtype' => 'post'));
$this->assertEquals(2, $posttags);
// Clean up the tags.
tag_cleanup();
$task->cleanup();
// We should only have one entry left now.
$posttags = $DB->count_records('tag_instance', array('itemtype' => 'post'));
$this->assertEquals(1, $posttags);
@@ -234,6 +300,8 @@ class core_tag_taglib_testcase extends advanced_testcase {
*/
public function test_tag_bulk_delete_instances() {
global $DB;
$task = new \core\task\tag_cron_task();
// Setup.
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
@@ -242,7 +310,7 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Create some tag instances.
for ($i = 0; $i < 10; $i++) {
$tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id));
tag_assign('course', $course->id, $tag->id, 0, 0, 'core', $context->id);
core_tag_tag::add_item_tag('core', 'course', $course->id, $context, $tag->rawname);
}
// Get tag instances. tag name and rawname are required for the event fired in this function.
$sql = "SELECT ti.*, t.name, t.rawname
@@ -251,7 +319,7 @@ class core_tag_taglib_testcase extends advanced_testcase {
$taginstances = $DB->get_records_sql($sql);
$this->assertCount(10, $taginstances);
// Run the function.
tag_bulk_delete_instances($taginstances);
$task->bulk_delete_instances($taginstances);
// Make sure they are gone.
$instancecount = $DB->count_records('tag_instance');
$this->assertEquals(0, $instancecount);
@@ -262,6 +330,8 @@ class core_tag_taglib_testcase extends advanced_testcase {
*/
public function test_correlations() {
global $DB;
$task = new \core\task\tag_cron_task();
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
@@ -275,80 +345,118 @@ class core_tag_taglib_testcase extends advanced_testcase {
// Several records have both 'cat' and 'cats' tags attached to them.
// This will make those tags automatically correlated.
// Same with 'dog', 'dogs' and 'puppy.
tag_set('user', $user1->id, array('cat', 'cats'),
'core', context_user::instance($user1->id)->id);
tag_set('user', $user2->id, array('cat', 'cats', 'kitten'),
'core', context_user::instance($user2->id)->id);
tag_set('user', $user3->id, array('cat', 'cats'),
'core', context_user::instance($user3->id)->id);
tag_set('user', $user4->id, array('dog', 'dogs', 'puppy'),
'core', context_user::instance($user4->id)->id);
tag_set('user', $user5->id, array('dog', 'dogs', 'puppy'),
'core', context_user::instance($user5->id)->id);
tag_set('user', $user6->id, array('dog', 'dogs', 'puppy'),
'core', context_user::instance($user6->id)->id);
core_tag_tag::set_item_tags('core', 'user', $user1->id, context_user::instance($user1->id), array('cat', 'cats'));
core_tag_tag::set_item_tags('core', 'user', $user2->id, context_user::instance($user2->id), array('cat', 'cats', 'kitten'));
core_tag_tag::set_item_tags('core', 'user', $user3->id, context_user::instance($user3->id), array('cat', 'cats'));
core_tag_tag::set_item_tags('core', 'user', $user4->id, context_user::instance($user4->id), array('dog', 'dogs', 'puppy'));
core_tag_tag::set_item_tags('core', 'user', $user5->id, context_user::instance($user5->id), array('dog', 'dogs', 'puppy'));
core_tag_tag::set_item_tags('core', 'user', $user6->id, context_user::instance($user6->id), array('dog', 'dogs', 'puppy'));
$tags = tag_get_id(array('cat', 'cats', 'dog', 'dogs', 'kitten', 'puppy'));
$tags = core_tag_tag::get_by_name_bulk(core_tag_collection::get_default(),
array('cat', 'cats', 'dog', 'dogs', 'kitten', 'puppy'));
$tags = array_map(function ($t) {
return $t->id;
}, $tags);
// Add manual relation between tags 'cat' and 'kitten'.
tag_set('tag', $tags['cat'], array('kitten'), 'core', context_system::instance()->id);
core_tag_tag::get($tags['cat'])->set_related_tags(array('kitten'));
tag_compute_correlations();
$task->compute_correlations();
$this->assertEquals($tags['cats'],
$DB->get_field_select('tag_correlation', 'correlatedtags',
$DB->get_field_select('tag_correlation', 'correlatedtags',
'tagid = ?', array($tags['cat'])));
$this->assertEquals($tags['cat'],
$DB->get_field_select('tag_correlation', 'correlatedtags',
$DB->get_field_select('tag_correlation', 'correlatedtags',
'tagid = ?', array($tags['cats'])));
$this->assertEquals($tags['dogs'] . ',' . $tags['puppy'],
$DB->get_field_select('tag_correlation', 'correlatedtags',
$DB->get_field_select('tag_correlation', 'correlatedtags',
'tagid = ?', array($tags['dog'])));
$this->assertEquals($tags['dog'] . ',' . $tags['puppy'],
$DB->get_field_select('tag_correlation', 'correlatedtags',
$DB->get_field_select('tag_correlation', 'correlatedtags',
'tagid = ?', array($tags['dogs'])));
$this->assertEquals($tags['dog'] . ',' . $tags['dogs'],
$DB->get_field_select('tag_correlation', 'correlatedtags',
$DB->get_field_select('tag_correlation', 'correlatedtags',
'tagid = ?', array($tags['puppy'])));
// Make sure tag_get_correlated() returns 'cats' as the only correlated tag to the 'cat'.
// Make sure get_correlated_tags() returns 'cats' as the only correlated tag to the 'cat'.
$correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags(true));
$this->assertCount(3, $correlatedtags); // This will return all existing instances but they all point to the same tag.
$this->assertEquals('cats', $correlatedtags[0]->rawname);
$this->assertEquals('cats', $correlatedtags[1]->rawname);
$this->assertEquals('cats', $correlatedtags[2]->rawname);
$correlatedtags = array_values(core_tag_tag::get($tags['cat'])->get_correlated_tags());
$this->assertCount(1, $correlatedtags); // Duplicates are filtered out here.
$this->assertEquals('cats', $correlatedtags[0]->rawname);
// Make sure tag_get_correlated() returns 'dogs' and 'puppy' as the correlated tags to the 'dog'.
$correlatedtags = core_tag_tag::get($tags['dog'])->get_correlated_tags(true);
$this->assertCount(6, $correlatedtags); // 2 tags times 3 instances.
$correlatedtags = array_values(core_tag_tag::get($tags['dog'])->get_correlated_tags());
$this->assertCount(2, $correlatedtags);
$this->assertEquals('dogs', $correlatedtags[0]->rawname);
$this->assertEquals('puppy', $correlatedtags[1]->rawname);
// Function get_related_tags() will return both related and correlated tags.
$relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());
$this->assertCount(2, $relatedtags);
$this->assertEquals('kitten', $relatedtags[0]->rawname);
$this->assertEquals('cats', $relatedtags[1]->rawname);
// Also test deprecated method tag_get_related_tags() and tag_get_correlated().
$correlatedtags = array_values(tag_get_correlated($tags['cat']));
$this->assertDebuggingCalled();
$this->assertCount(3, $correlatedtags); // This will return all existing instances but they all point to the same tag.
$this->assertEquals('cats', $correlatedtags[0]->rawname);
$this->assertEquals('cats', $correlatedtags[1]->rawname);
$this->assertEquals('cats', $correlatedtags[2]->rawname);
$correlatedtags = array_values(tag_get_related_tags($tags['cat'], TAG_RELATED_CORRELATED));
$this->assertDebuggingCalled();
$this->assertCount(1, $correlatedtags); // Duplicates are filtered out here.
$this->assertEquals('cats', $correlatedtags[0]->rawname);
// Make sure tag_get_correlated() returns 'dogs' and 'puppy' as the correlated tags to the 'dog'.
$correlatedtags = array_values(tag_get_correlated($tags['dog']));
$this->assertDebuggingCalled();
$this->assertCount(6, $correlatedtags); // 2 tags times 3 instances.
$correlatedtags = array_values(tag_get_related_tags($tags['dog'], TAG_RELATED_CORRELATED));
$this->assertDebuggingCalled();
$this->assertCount(2, $correlatedtags);
$this->assertEquals('dogs', $correlatedtags[0]->rawname);
$this->assertEquals('puppy', $correlatedtags[1]->rawname);
// Function tag_get_related_tags() with default argument will return both related and correlated tags.
$relatedtags = array_values(tag_get_related_tags($tags['cat']));
$this->assertDebuggingCalled();
$this->assertCount(2, $relatedtags);
$this->assertEquals('kitten', $relatedtags[0]->rawname);
$this->assertEquals('cats', $relatedtags[1]->rawname);
// End of testing deprecated methods.
// If we then manually set 'cat' and 'cats' as related, get_related_tags() will filter out duplicates.
core_tag_tag::get($tags['cat'])->set_related_tags(array('kitten', 'cats'));
$relatedtags = array_values(core_tag_tag::get($tags['cat'])->get_related_tags());
$this->assertCount(2, $relatedtags);
$this->assertEquals('kitten', $relatedtags[0]->rawname);
$this->assertEquals('cats', $relatedtags[1]->rawname);
// If we then manually set 'cat' and 'cats' as related, tag_get_related_tags() will filter out duplicates.
tag_set('tag', $tags['cat'], array('kitten', 'cats'), 'core', context_system::instance()->id);
$relatedtags = array_values(tag_get_related_tags($tags['cat']));
$this->assertCount(2, $relatedtags);
$this->assertEquals('kitten', $relatedtags[0]->rawname);
$this->assertEquals('cats', $relatedtags[1]->rawname);
// Make sure core_tag_tag::get_item_tags(), core_tag_tag::get_correlated_tags() return the same set of fields.
$relatedtags = core_tag_tag::get_item_tags('core', 'tag', $tags['cat']);
$relatedtag = reset($relatedtags);
$correlatedtags = core_tag_tag::get($tags['cat'])->get_correlated_tags();
$correlatedtag = reset($correlatedtags);
$this->assertEquals(array_keys((array)$relatedtag->to_object()), array_keys((array)$correlatedtag->to_object()));
// Make sure tag_get_correlated() and tag_get_tags() return the same set of fields.
// Both functions were deprecated in 3.1.
$relatedtags = tag_get_tags('tag', $tags['cat']);
$this->assertDebuggingCalled();
$relatedtag = reset($relatedtags);
$correlatedtags = tag_get_correlated($tags['cat']);
$this->assertDebuggingCalled();
$correlatedtag = reset($correlatedtags);
$this->assertEquals(array_keys((array)$relatedtag), array_keys((array)$correlatedtag));
}
@@ -358,26 +466,28 @@ class core_tag_taglib_testcase extends advanced_testcase {
*/
public function test_cleanup() {
global $DB;
$task = new \core\task\tag_cron_task();
$user = $this->getDataGenerator()->create_user();
$defaultcoll = core_tag_collection::get_default();
// Setting tags will create non-official tags 'cat', 'dog' and 'fish'.
tag_set('user', $user->id, array('cat', 'dog', 'fish'),
'core', context_user::instance($user->id)->id);
core_tag_tag::set_item_tags('core', 'user', $user->id, context_user::instance($user->id), array('cat', 'dog', 'fish'));
$this->assertTrue($DB->record_exists('tag', array('name' => 'cat')));
$this->assertTrue($DB->record_exists('tag', array('name' => 'dog')));
$this->assertTrue($DB->record_exists('tag', array('name' => 'fish')));
// Make tag 'dog' official.
$dogtag = tag_get('name', 'dog');
$fishtag = tag_get('name', 'fish');
tag_type_set($dogtag->id, 'official');
$dogtag = core_tag_tag::get_by_name($defaultcoll, 'dog', '*');
$fishtag = core_tag_tag::get_by_name($defaultcoll, 'fish');
$dogtag->update(array('tagtype' => 'official'));
// Manually remove the instances pointing on tags 'dog' and 'fish'.
$DB->execute('DELETE FROM {tag_instance} WHERE tagid in (?,?)', array($dogtag->id, $fishtag->id));
// Call tag_cleanup().
tag_cleanup();
$task->cleanup();
// Tag 'cat' is still present because it's used. Tag 'dog' is present because it's official.
// Tag 'fish' was removed because it is not official and it is no longer used by anybody.
@@ -389,26 +499,25 @@ class core_tag_taglib_testcase extends advanced_testcase {
$DB->update_record('user', array('id' => $user->id, 'deleted' => 1));
// Call tag_cleanup().
tag_cleanup();
$task->cleanup();
// Tag 'cat' was now deleted too.
$this->assertFalse($DB->record_exists('tag', array('name' => 'cat')));
// Assign tag to non-existing record. Make sure tag was created in the DB.
tag_set('course', 1231231, array('bird'), 'core', context_system::instance()->id);
core_tag_tag::set_item_tags('core', 'course', 1231231, context_system::instance(), array('bird'));
$this->assertTrue($DB->record_exists('tag', array('name' => 'bird')));
// Call tag_cleanup().
tag_cleanup();
$task->cleanup();
// Tag 'bird' was now deleted because the related record does not exist in the DB.
$this->assertFalse($DB->record_exists('tag', array('name' => 'bird')));
// Now we have a tag instance pointing on 'sometag' tag.
$user = $this->getDataGenerator()->create_user();
tag_set('user', $user->id, array('sometag'),
'core', context_user::instance($user->id)->id);
$sometag = tag_get('name', 'sometag');
core_tag_tag::set_item_tags('core', 'user', $user->id, context_user::instance($user->id), array('sometag'));
$sometag = core_tag_tag::get_by_name($defaultcoll, 'sometag');
$this->assertTrue($DB->record_exists('tag_instance', array('tagid' => $sometag->id)));
@@ -416,9 +525,334 @@ class core_tag_taglib_testcase extends advanced_testcase {
$DB->delete_records('tag', array('id' => $sometag->id));
// Call tag_cleanup().
tag_cleanup();
$task->cleanup();
// The tag instances were also removed.
$this->assertFalse($DB->record_exists('tag_instance', array('tagid' => $sometag->id)));
}
public function test_guess_tag() {
global $DB;
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$tag1 = $this->getDataGenerator()->create_tag(array('name' => 'Cat'));
$tc = core_tag_collection::create((object)array('name' => 'tagcoll'));
$tag2 = $this->getDataGenerator()->create_tag(array('name' => 'Cat', 'tagcollid' => $tc->id));
$this->assertEquals(2, count($DB->get_records('tag')));
$this->assertEquals(2, count(core_tag_tag::guess_by_name('Cat')));
$this->assertEquals(core_tag_collection::get_default(), core_tag_tag::get_by_name(0, 'Cat')->tagcollid);
}
public function test_instances() {
global $DB;
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// Create a course to tag.
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
$initialtagscount = $DB->count_records('tag');
core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 1', 'Tag 2'));
$tags = core_tag_tag::get_item_tags('core', 'course', $course->id);
$tagssimple = array_values($tags);
$this->assertEquals(2, count($tags));
$this->assertEquals('Tag 1', $tagssimple[0]->rawname);
$this->assertEquals('Tag 2', $tagssimple[1]->rawname);
$this->assertEquals($initialtagscount + 2, $DB->count_records('tag'));
core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 3', 'Tag 2', 'Tag 1'));
$tags = core_tag_tag::get_item_tags('core', 'course', $course->id);
$tagssimple = array_values($tags);
$this->assertEquals(3, count($tags));
$this->assertEquals('Tag 3', $tagssimple[0]->rawname);
$this->assertEquals('Tag 2', $tagssimple[1]->rawname);
$this->assertEquals('Tag 1', $tagssimple[2]->rawname);
$this->assertEquals($initialtagscount + 3, $DB->count_records('tag'));
core_tag_tag::set_item_tags('core', 'course', $course->id, $context, array('Tag 3'));
$tags = core_tag_tag::get_item_tags('core', 'course', $course->id);
$tagssimple = array_values($tags);
$this->assertEquals(1, count($tags));
$this->assertEquals('Tag 3', $tagssimple[0]->rawname);
// Make sure the unused tags were removed from tag table.
$this->assertEquals($initialtagscount + 1, $DB->count_records('tag'));
}
public function test_related_tags() {
global $DB;
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$tagcollid = core_tag_collection::get_default();
$tag = $this->getDataGenerator()->create_tag(array('$tagcollid' => $tagcollid, 'rawname' => 'My tag'));
$tag = core_tag_tag::get($tag->id, '*');
$tag->set_related_tags(array('Synonym 1', 'Synonym 2'));
$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));
$this->assertEquals(2, count($relatedtags));
$this->assertEquals('Synonym 1', $relatedtags[0]->rawname);
$this->assertEquals('Synonym 2', $relatedtags[1]->rawname);
$t1 = core_tag_tag::get_by_name($tagcollid, 'Synonym 1', '*');
$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t1->id));
$this->assertEquals(1, count($relatedtags));
$this->assertEquals('My tag', $relatedtags[0]->rawname);
$t2 = core_tag_tag::get_by_name($tagcollid, 'Synonym 2', '*');
$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t2->id));
$this->assertEquals(1, count($relatedtags));
$this->assertEquals('My tag', $relatedtags[0]->rawname);
$tag->set_related_tags(array('Synonym 3', 'Synonym 2', 'Synonym 1'));
$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));
$this->assertEquals(3, count($relatedtags));
$this->assertEquals('Synonym 1', $relatedtags[0]->rawname);
$this->assertEquals('Synonym 2', $relatedtags[1]->rawname);
$this->assertEquals('Synonym 3', $relatedtags[2]->rawname);
$t3 = core_tag_tag::get_by_name($tagcollid, 'Synonym 3', '*');
$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t3->id));
$this->assertEquals(1, count($relatedtags));
$this->assertEquals('My tag', $relatedtags[0]->rawname);
$tag->set_related_tags(array('Synonym 3', 'Synonym 2'));
$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $tag->id));
$this->assertEquals(2, count($relatedtags));
$this->assertEquals('Synonym 2', $relatedtags[0]->rawname);
$this->assertEquals('Synonym 3', $relatedtags[1]->rawname);
// Assert "Synonym 1" no longer links but is still present (will be removed by cron).
$relatedtags = array_values(core_tag_tag::get_item_tags('core', 'tag', $t1->id));
$this->assertEquals(0, count($relatedtags));
}
/**
* Very basic test for create/move/update/delete actions, without any itemtype movements.
*/
public function test_tag_coll_basic() {
global $DB;
// Make sure there is one and only one tag coll that is marked as default.
$tagcolls = core_tag_collection::get_collections();
$this->assertEquals(1, count($DB->get_records('tag_coll', array('isdefault' => 1))));
$defaulttagcoll = core_tag_collection::get_default();
// Create a new tag coll to store user tags and something else.
$data = (object)array('name' => 'new tag coll');
$tagcollid1 = core_tag_collection::create($data)->id;
$tagcolls = core_tag_collection::get_collections();
$this->assertEquals('new tag coll', $tagcolls[$tagcollid1]->name);
// Create a new tag coll to store post tags.
$data = (object)array('name' => 'posts');
$tagcollid2 = core_tag_collection::create($data)->id;
$tagcolls = core_tag_collection::get_collections();
$this->assertEquals('posts', $tagcolls[$tagcollid2]->name);
$this->assertEquals($tagcolls[$tagcollid1]->sortorder + 1,
$tagcolls[$tagcollid2]->sortorder);
// Illegal tag colls sortorder changing.
$this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$defaulttagcoll], 1));
$this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$defaulttagcoll], -1));
$this->assertFalse(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], 1));
// Move the very last tag coll one position up.
$this->assertTrue(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], -1));
$tagcolls = core_tag_collection::get_collections();
$this->assertEquals($tagcolls[$tagcollid2]->sortorder + 1,
$tagcolls[$tagcollid1]->sortorder);
// Move the second last tag coll one position down.
$this->assertTrue(core_tag_collection::change_sortorder($tagcolls[$tagcollid2], 1));
$tagcolls = core_tag_collection::get_collections();
$this->assertEquals($tagcolls[$tagcollid1]->sortorder + 1,
$tagcolls[$tagcollid2]->sortorder);
// Edit tag coll.
$this->assertTrue(core_tag_collection::update($tagcolls[$tagcollid2],
(object)array('name' => 'posts2')));
$tagcolls = core_tag_collection::get_collections();
$this->assertEquals('posts2', $tagcolls[$tagcollid2]->name);
// Delete tag coll.
$count = $DB->count_records('tag_coll');
$this->assertFalse(core_tag_collection::delete($tagcolls[$defaulttagcoll]));
$this->assertTrue(core_tag_collection::delete($tagcolls[$tagcollid1]));
$this->assertEquals($count - 1, $DB->count_records('tag_coll'));
}
/**
* Prepares environment for test_move_tags_* tests
*/
protected function prepare_move_tags() {
global $CFG;
require_once($CFG->dirroot.'/blog/locallib.php');
$this->setUser($this->getDataGenerator()->create_user());
$collid1 = core_tag_collection::get_default();
$collid2 = core_tag_collection::create(array('name' => 'newcoll'))->id;
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$blogpost = new blog_entry(null, array('subject' => 'test'), null);
$states = blog_entry::get_applicable_publish_states();
$blogpost->publishstate = reset($states);
$blogpost->add();
core_tag_tag::set_item_tags('core', 'user', $user1->id, context_user::instance($user1->id),
array('Tag1', 'Tag2'));
core_tag_tag::set_item_tags('core', 'user', $user2->id, context_user::instance($user2->id),
array('Tag2', 'Tag3'));
$this->getDataGenerator()->create_tag(array('rawname' => 'Tag4',
'tagcollid' => $collid1, 'tagtype' => 'official'));
$this->getDataGenerator()->create_tag(array('rawname' => 'Tag5',
'tagcollid' => $collid2, 'tagtype' => 'official'));
return array($collid1, $collid2, $user1, $user2, $blogpost);
}
public function test_move_tags_simple() {
global $DB;
list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
// Move 'user' area from collection 1 to collection 2, make sure tags were moved completely.
$tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
core_tag_area::update($tagarea, array('tagcollid' => $collid2));
$this->assertEquals(array('Tag4'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
$this->assertEquals(array('Tag1', 'Tag2', 'Tag3', 'Tag5'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
$this->assertEquals(array('Tag1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
}
public function test_move_tags_split_tag() {
global $DB;
list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
core_tag_tag::set_item_tags('core', 'post', $blogpost->id, context_system::instance(),
array('Tag1', 'Tag3'));
// Move 'user' area from collection 1 to collection 2, make sure tag Tag2 was moved and tags Tag1 and Tag3 were duplicated.
$tagareauser = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
core_tag_area::update($tagareauser, array('tagcollid' => $collid2));
$this->assertEquals(array('Tag1', 'Tag3', 'Tag4'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
$this->assertEquals(array('Tag1', 'Tag2', 'Tag3', 'Tag5'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
$this->assertEquals(array('Tag1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
$this->assertEquals(array('Tag1', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'post', $blogpost->id)));
}
public function test_move_tags_merge_tag() {
global $DB;
list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
// Set collection for 'post' tag area to be collection 2 and add some tags there.
$tagareablog = $DB->get_record('tag_area', array('itemtype' => 'post', 'component' => 'core'));
core_tag_area::update($tagareablog, array('tagcollid' => $collid2));
core_tag_tag::set_item_tags('core', 'post', $blogpost->id, context_system::instance(),
array('TAG1', 'Tag3'));
// Move 'user' area from collection 1 to collection 2,
// make sure tag Tag2 was moved and tags Tag1 and Tag3 were merged into existing.
$tagareauser = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
core_tag_area::update($tagareauser, array('tagcollid' => $collid2));
$this->assertEquals(array('Tag4'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
$this->assertEquals(array('TAG1', 'Tag2', 'Tag3', 'Tag5'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
$this->assertEquals(array('TAG1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
$this->assertEquals(array('TAG1', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'post', $blogpost->id)));
}
public function test_move_tags_with_related() {
global $DB;
list($collid1, $collid2, $user1, $user2, $blogpost) = $this->prepare_move_tags();
// Set Tag1 to be related to Tag2 and Tag4 (in collection 1).
core_tag_tag::get_by_name($collid1, 'Tag1')->set_related_tags(array('Tag2', 'Tag4'));
// Set collection for 'post' tag area to be collection 2 and add some tags there.
$tagareablog = $DB->get_record('tag_area', array('itemtype' => 'post', 'component' => 'core'));
core_tag_area::update($tagareablog, array('tagcollid' => $collid2));
core_tag_tag::set_item_tags('core', 'post', $blogpost->id, context_system::instance(),
array('TAG1', 'Tag3'));
// Move 'user' area from collection 1 to collection 2, make sure tags were moved completely.
$tagarea = $DB->get_record('tag_area', array('itemtype' => 'user', 'component' => 'core'));
core_tag_area::update($tagarea, array('tagcollid' => $collid2));
$this->assertEquals(array('Tag1', 'Tag2', 'Tag4'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid1)));
$this->assertEquals(array('TAG1', 'Tag2', 'Tag3', 'Tag4', 'Tag5'),
$DB->get_fieldset_select('tag', 'rawname', 'tagcollid = ? ORDER BY name', array($collid2)));
$this->assertEquals(array('TAG1', 'Tag2'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user1->id)));
$this->assertEquals(array('Tag2', 'Tag3'), array_values(core_tag_tag::get_item_tags_array('core', 'user', $user2->id)));
$tag11 = core_tag_tag::get_by_name($collid1, 'Tag1');
$related11 = tag_get_related_tags($tag11->id, TAG_RELATED_MANUAL);
$this->assertDebuggingCalled();
$related11 = array_map('core_tag_tag::make_display_name', $related11);
sort($related11); // Order of related tags may be random.
$this->assertEquals('Tag2, Tag4', join(', ', $related11));
$tag21 = core_tag_tag::get_by_name($collid2, 'TAG1');
$related21 = tag_get_related_tags($tag21->id, TAG_RELATED_MANUAL);
$this->assertDebuggingCalled();
$related21 = array_map('core_tag_tag::make_display_name', $related21);
sort($related21); // Order of related tags may be random.
$this->assertEquals('Tag2, Tag4', join(', ', $related21));
}
public function test_normalize() {
$tagset = array('Cat', ' Dog ', '<Mouse', '<>', 'mouse', 'Dog');
// Test function tag_normalize() that was deprecated in 3.1.
$this->assertEquals(array('Cat' => 'Cat', 'Dog' => 'Dog', '<Mouse' => 'Mouse', '<>' => '', 'mouse' => 'mouse'),
tag_normalize($tagset, TAG_CASE_ORIGINAL));
$this->assertDebuggingCalled();
$this->assertEquals(array('Cat' => 'cat', 'Dog' => 'dog', '<Mouse' => 'mouse', '<>' => '', 'mouse' => 'mouse'),
tag_normalize($tagset, TAG_CASE_LOWER));
$this->assertDebuggingCalled();
// Test replacement function core_tag_tag::normalize().
$this->assertEquals(array('Cat' => 'Cat', 'Dog' => 'Dog', '<Mouse' => 'Mouse', '<>' => '', 'mouse' => 'mouse'),
core_tag_tag::normalize($tagset, false));
$this->assertEquals(array('Cat' => 'cat', 'Dog' => 'dog', '<Mouse' => 'mouse', '<>' => '', 'mouse' => 'mouse'),
core_tag_tag::normalize($tagset, true));
}
/**
* Test functions core_tag_tag::create_if_missing() and core_tag_tag::get_by_name_bulk().
*/
public function test_create_get() {
$tagset = array('Cat', ' Dog ', '<Mouse', '<>', 'mouse', 'Dog');
$collid = core_tag_collection::get_default();
$tags = core_tag_tag::create_if_missing($collid, $tagset);
$this->assertEquals(array('cat', 'dog', 'mouse'), array_keys($tags));
$this->assertEquals('Dog', $tags['dog']->rawname);
$this->assertEquals('mouse', $tags['mouse']->rawname); // Case of the last tag wins.
$tags2 = core_tag_tag::create_if_missing($collid, array('CAT', 'Elephant'));
$this->assertEquals(array('cat', 'elephant'), array_keys($tags2));
$this->assertEquals('Cat', $tags2['cat']->rawname);
$this->assertEquals('Elephant', $tags2['elephant']->rawname);
$this->assertEquals($tags['cat']->id, $tags2['cat']->id); // Tag 'cat' already existed and was not created again.
$tags3 = core_tag_tag::get_by_name_bulk($collid, $tagset);
$this->assertEquals(array('cat', 'dog', 'mouse'), array_keys($tags3));
$this->assertEquals('Dog', $tags3['dog']->rawname);
$this->assertEquals('mouse', $tags3['mouse']->rawname);
}
}
+60
View File
@@ -1,6 +1,66 @@
This files describes API changes in tagging, information provided
here is intended especially for developers.
=== 3.1 ===
* Each plugin must define the tag areas it uses in plugindir/db/tag.php
See https://docs.moodle.org/dev/Tag_API for more details.
* When adding a 'tags' form element the component and itemtype must be specified
in the element attributes.
* Tag name is no longer a unique field in 'tag' table, tags may be separated in
tag collections
* Specifying component is also required when retrieving item tags. This will allow
different components to add tags to the same table (for example blog and forum
posts) and even suggest alternative tagging for the same items.
* All tag_* functions were deprecated because they now require either tag
collection id or component, some were moved to the new class core_tag_tag and
some - to the renderers or templates. As usual, the deprecated function code
and debugging message will provide you with the suggestions of the alternative.
Please note that all deprecated functions will be removed after several major releases.
- tag_type_set
- tag_description_set
- tag_get_tags
- tag_get_tags_array
- tag_get_tags_csv
- tag_get_tags_ids
- tag_get_id
- tag_rename
- tag_delete_instance
- tag_find_records
- tag_add
- tag_assign
- tag_record_count
- tag_record_tagged_with
- tag_set_flag
- tag_unset_flag
- tag_print_cloud
- tag_print_description_box
- tag_print_management_box
- tag_print_search_box
- tag_print_search_results
- tag_print_tagged_users_table
- tag_print_user_box
- tag_print_user_list
- tag_display_name
- tag_normalize
- tag_get_related_tags_csv
- tag_set
- tag_set_add
- tag_set_delete
- tag_get
- tag_get_related_tags
- tag_delete
- tag_delete_instances
- tag_cleanup
- tag_bulk_delete_instances
- tag_compute_correlations
- tag_process_computed_correlation
- tag_cron
- tag_find_tags
- tag_get_name
- tag_get_correlated
- tag_cloud_sort
=== 3.0 ===
* Tagging courses is now implemented without user id - through course edit form or
+37 -44
View File
@@ -734,28 +734,17 @@ table.mod_index {width:100%;}
/**
* Tags
*/
#page-tag-coursetags_edit .coursetag_edit_centered {position: relative;width: 600px;margin: 20px auto;}
#page-tag-coursetags_edit .coursetag_edit_row {clear:both;}
#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_left {float:left;width:50%;text-align:right;}
#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_right {margin-left:50%;}
#page-tag-coursetags_edit .coursetag_edit_input3 {display: none;}
#page-tag-coursetags_more .coursetag_more_large {font-size: 120%;}
#page-tag-coursetags_more .coursetag_more_small {font-size: 80%;}
#page-tag-coursetags_more .coursetag_more_link {font-size: 80%;}
#tag-description,
#tag-blogs {width:100%;}
#tag-management-box {margin-bottom:10px;line-height:20px;}
#tag-user-table {padding:3px;clear: both;width:100%;}
#tag-user-table:after {content:".";display:block;clear:both;visibility:hidden;height:0;overflow:hidden;}
img.user-image {height:100px;width:100px;}
#small-tag-cloud-box {width:300px;margin:0 auto;}
#big-tag-cloud-box {width:600px;margin:0 auto;float:none;}
ul#tag-cloud-list {list-style:none;padding:5px;margin:0;}
ul#tag-cloud-list li {margin:0;display:inline;list-style-type:none;}
#tag-search-box {text-align:center;margin:10px auto;}
#tag-search-results-container {padding:0;width:100%;}
#tag-search-results {padding:0;margin: 15px 20% 0 20%;float:left;width:60%;display:block;}
#tag-search-results li {width:30%;float:left;padding-left:1%;text-align:left;line-height:20px;padding-right:1%;list-style:none;}
.tagarea {clear:both;overflow:hidden;}
.tagarea .controls {text-align: center;}
.tagarea .controls .gotopage.nextpage {float: right;}
.tagarea .controls .gotopage.prevpage {float: left;}
.tagarea .controls .exclusivemode {display: inline-block;}
.dir-rtl .tagarea .controls .gotopage.nextpage {float: left;}
.dir-rtl .tagarea .controls .gotopage.prevpage {float: right;}
.tagarea .controls.controls-bottom {margin-top: 5px;}
span.flagged-tag,
tr.flagged-tag,
span.flagged-tag a,
@@ -770,31 +759,35 @@ tr.flagged-tag a {color:#FF0000;}
.tag-management-table tr:hover td .tagnameedit img,
.tag-management-table tr td .tagnameedit:focus img {opacity: 1;}
.tag-management-table tr:hover td.tageditingon .tagnameedit img {opacity: 0.2;}
h2.tag-heading,
div#tag-description,
div#tag-blogs,
body.tag .managelink {padding: 5px;}
.tag_cloud .s20 {font-size: 1.5em;font-weight: bold;}
.tag_cloud .s19 {font-size: 1.5em;}
.tag_cloud .s18 {font-size: 1.4em;font-weight: bold;}
.tag_cloud .s17 {font-size: 1.4em;}
.tag_cloud .s16 {font-size: 1.3em;font-weight: bold;}
.tag_cloud .s15 {font-size: 1.3em;}
.tag_cloud .s14 {font-size: 1.2em;font-weight: bold;}
.tag_cloud .s13 {font-size: 1.2em;}
.tag_cloud .s12,
.tag_cloud .s11 {font-size: 1.1em;font-weight: bold;}
.tag_cloud .s10,
.tag_cloud .s9 {font-size: 1.1em;}
.tag_cloud .s8,
.tag_cloud .s7 {font-size: 1em;font-weight: bold;}
.tag_cloud .s6,
.tag_cloud .s5 {font-size: 1em;}
.tag_cloud .s4,
.tag_cloud .s3 {font-size: 0.9em;font-weight: bold;}
.tag_cloud .s2,
.tag_cloud .s1 {font-size: 0.9em;}
.tag_cloud .s0 {font-size: 0.8em;}
.tag_feed .media, .tag_feed .media-body {overflow: hidden;}
.tag_feed.media-list .media .itemimage {float: left;}
.dir-rtl .tag_feed.media-list .media .itemimage {float: right;}
.tag_feed.media-list .media .itemimage img {height: 35px;width: 35px;}
.tag_feed.media-list .media .media-body {padding-right: 10px;padding-left: 10px;}
.tag_cloud {text-align: center;}
.tag_cloud .inline-list li {padding: 0px 0.2em;}
.tag_cloud .tag_overflow {margin-top: 1em;font-style: italic;}
.tag_cloud .s20 {font-size: 2.7em;}
.tag_cloud .s19 {font-size: 2.6em;}
.tag_cloud .s18 {font-size: 2.5em;}
.tag_cloud .s17 {font-size: 2.4em;}
.tag_cloud .s16 {font-size: 2.3em;}
.tag_cloud .s15 {font-size: 2.2em;}
.tag_cloud .s14 {font-size: 2.1em;}
.tag_cloud .s13 {font-size: 2em;}
.tag_cloud .s12 {font-size: 1.9em;}
.tag_cloud .s11 {font-size: 1.8em;}
.tag_cloud .s10 {font-size: 1.7em;}
.tag_cloud .s9 {font-size: 1.6em;}
.tag_cloud .s8 {font-size: 1.5em;}
.tag_cloud .s7 {font-size: 1.4em;}
.tag_cloud .s6 {font-size: 1.3em;}
.tag_cloud .s5 {font-size: 1.2em;}
.tag_cloud .s4 {font-size: 1.1em;}
.tag_cloud .s3 {font-size: 1em;}
.tag_cloud .s2 {font-size: 0.9em;}
.tag_cloud .s1 {font-size: 0.8em;}
.tag_cloud .s0 {font-size: 0.7em;}
/*
* Backup and Restore CSS
+168 -117
View File
@@ -747,97 +747,74 @@ table.mod_index {
/**
* Tags
*/
#page-tag-coursetags_edit .coursetag_edit_centered {
position: relative;
width: 600px;
margin: 20px auto;
}
#page-tag-coursetags_edit .coursetag_edit_row {
clear: both;
}
#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_left {
float: left;
width: 50%;
text-align: right;
}
#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_right {
margin-left: 50%;
}
#page-tag-coursetags_edit .coursetag_edit_input3 {
display: none;
}
#page-tag-coursetags_more .coursetag_more_large {
font-size: 120%;
}
#page-tag-coursetags_more .coursetag_more_small {
font-size: 80%;
}
#page-tag-coursetags_more .coursetag_more_link {
font-size: 80%;
}
#tag-description,
#tag-blogs {
width: 100%;
}
#tag-management-box {
margin-bottom: 10px;
line-height: 20px;
}
#tag-user-table {
padding: 3px;
clear: both;
width: 100%;
}
#tag-user-table {
.clearfix
}
img.user-image {
height: 100px;
width: 100px;
}
#small-tag-cloud-box {
width: 300px;
margin: 0 auto;
}
#big-tag-cloud-box {
width: 600px;
margin: 0 auto;
float: none;
}
ul#tag-cloud-list {
list-style: none;
padding: 5px;
margin: 0;
}
ul#tag-cloud-list li {
margin: 0;
display: inline;
list-style-type: none;
}
#tag-search-box {
text-align: center;
margin: 10px auto;
}
#tag-search-results-container {
padding: 0;
width: 100%;
.path-tag .tag-index-items .tagarea {
border: 1px solid #E3E3E3;
border-radius: 4px;
padding: 10px;
margin-top: 10px;
}
#tag-search-results {
padding: 0;
margin: 15px 20% 0 20%;
float: left;
width: 60%;
.path-tag .tag-index-items .tagarea h3 {
display: block;
}
#tag-search-results li {
width: 30%;
float: left;
padding-left: 1%;
text-align: left;
padding: 3px 0 10px 0;
margin: 0px;
font-size: 1.1em;
font-weight: bold;
line-height: 20px;
padding-right: 1%;
list-style: none;
color: #999;
text-shadow: 0px 1px 0px rgba(255, 255, 255, 0.5);
text-transform: uppercase;
word-wrap: break-word;
border-bottom: solid 1px #E3E3E3;
margin-bottom: 10px;
}
.path-tag .tagarea .controls,
.path-tag .tagarea .taggeditems {
.clearfix();
}
.path-tag .tagarea .controls,
.path-tag .tag-backtoallitems {
text-align: center;
}
.path-tag .tagarea .controls .gotopage.nextpage {
float: right;
}
.path-tag .tagarea .controls .gotopage.prevpage {
float: left;
}
.path-tag .tagarea .controls .exclusivemode {
display: inline-block;
}
.dir-rtl.path-tag .tagarea .controls .gotopage.nextpage {
float: left;
}
.dir-rtl.path-tag .tagarea .controls .gotopage.prevpage {
float: right;
}
.path-tag .tagarea .controls.controls-bottom {
margin-top: 5px;
}
.path-tag .tagarea .controls .gotopage.nextpage::after {
padding-right: 5px;
padding-left: 5px;
content: "»";
}
.path-tag .tagarea .controls .gotopage.prevpage::before {
padding-right: 5px;
padding-left: 5px;
content: "«";
}
span.flagged-tag,
tr.flagged-tag,
span.flagged-tag a,
@@ -871,70 +848,144 @@ tr.flagged-tag a {
.tag-management-table tr:hover td.tageditingon .tagnameedit img {
opacity: 0.2;
}
h2.tag-heading,
div#tag-description,
div#tag-blogs,
body.tag .managelink {
padding: 5px;
.path-tag .tag-relatedtags {
padding-top: 10px;
}
.path-tag .tag-management-box {
text-align: right;
}
.path-tag .tag-index-toc {
padding: 10px;
text-align: center;
}
.path-tag .tag-index-toc li,
.path-tag .tag-management-box li {
margin-left: 5px;
margin-right: 5px;
}
.path-tag .tag-management-box li a.edittag {
background-image: url([[pix:moodle|i/settings]]);
}
.path-tag .tag-management-box li a.flagasinappropriate {
background-image: url([[pix:moodle|i/flagged]]);
}
.path-tag .tag-management-box li a.removefrommyinterests {
background-image: url([[pix:moodle|t/delete]]);
}
.path-tag .tag-management-box li a.addtomyinterests {
background-image: url([[pix:moodle|t/add]]);
}
.path-tag .tag-management-box li a {
background-repeat: no-repeat;
background-position: left;
padding-left: 17px;
}
.tag_feed.media-list .media .itemimage {
float: left;
}
.dir-rtl .tag_feed.media-list .media .itemimage {
float: right;
}
.tag_feed.media-list .media .itemimage img {
height: 35px;
width: 35px;
}
.tag_feed.media-list .media .media-body {
padding-right: 10px;
padding-left: 10px;
}
.tag_feed .media .muted a {
.muted();
}
.tag_cloud {
text-align: center;
}
.tag_cloud .inline-list li {
padding: 0px 0.2em;
}
.tag_cloud .tag_overflow {
margin-top: 1em;
font-style: italic;
}
.tag_cloud .s20 {
font-size: 1.5em;
font-weight: bold;
font-size: 2.7em;
}
.tag_cloud .s19 {
font-size: 1.5em;
font-size: 2.6em;
}
.tag_cloud .s18 {
font-size: 1.4em;
font-weight: bold;
font-size: 2.5em;
}
.tag_cloud .s17 {
font-size: 1.4em;
font-size: 2.4em;
}
.tag_cloud .s16 {
font-size: 1.3em;
font-weight: bold;
font-size: 2.3em;
}
.tag_cloud .s15 {
font-size: 1.3em;
font-size: 2.2em;
}
.tag_cloud .s14 {
font-size: 1.2em;
font-weight: bold;
font-size: 2.1em;
}
.tag_cloud .s13 {
font-size: 2em;
}
.tag_cloud .s12 {
font-size: 1.9em;
}
.tag_cloud .s11 {
font-size: 1.8em;
}
.tag_cloud .s10 {
font-size: 1.7em;
}
.tag_cloud .s9 {
font-size: 1.6em;
}
.tag_cloud .s8 {
font-size: 1.5em;
}
.tag_cloud .s7 {
font-size: 1.4em;
}
.tag_cloud .s6 {
font-size: 1.3em;
}
.tag_cloud .s5 {
font-size: 1.2em;
}
.tag_cloud .s12,
.tag_cloud .s11 {
font-size: 1.1em;
font-weight: bold;
}
.tag_cloud .s10,
.tag_cloud .s9 {
.tag_cloud .s4 {
font-size: 1.1em;
}
.tag_cloud .s8,
.tag_cloud .s7 {
font-size: 1em;
font-weight: bold;
}
.tag_cloud .s6,
.tag_cloud .s5 {
font-size: 1em;
}
.tag_cloud .s4,
.tag_cloud .s3 {
font-size: 1em;
}
.tag_cloud .s2 {
font-size: 0.9em;
font-weight: bold;
}
.tag_cloud .s2,
.tag_cloud .s1 {
font-size: 0.9em;
}
.tag_cloud .s0 {
font-size: 0.8em;
}
.tag_cloud .s0 {
font-size: 0.7em;
}
.tag_list ul {
display: inline;
}
.tag_list.hideoverlimit .overlimit {
display:none;
}
.tag_list .tagmorelink {
display:none;
}
.tag_list.hideoverlimit .tagmorelink {
display:inline;
}
.tag_list.hideoverlimit .taglesslink {
display:none;
}
/**
* Web Service
*/
File diff suppressed because one or more lines are too long
-51
View File
@@ -394,14 +394,6 @@ body {
/* Tags
------------------------*/
#big-tag-cloud-box {
padding: 10px;
}
#tag-management-box {
text-align: center;
}
.tag-management-form {
border: none;
padding: 0 0 1em;
@@ -411,12 +403,6 @@ body {
margin: 1em 0
}
#page-tag-index #tag-blogs {
border-width: 0 0 1px;
width: 65%;
margin: 1em auto;
}
#tagblogentries {
margin: 0 0 1em;
}
@@ -427,43 +413,6 @@ body {
margin: 0
}
#big-tag-cloud-box {
border: none;
}
#tag-user-table {
width: 95%;
margin: 0 auto;
border-width: 0 0 1px;
}
.managelink {
text-align: right;
}
#page-tag-index h2 {
text-align: center;
margin-bottom: 0;
}
#page-tag-index #tag-management-box {
border-bottom: 1px solid #ddd;
padding: 4px 0;
}
#page-tag-index .relatedpages {
text-align: center;
}
#page-tag-manage .lastinitial {
margin-bottom: 0.5em;
}
#page-tag-manage .green {
text-align: center;
padding: 0 0 1em;
}
/* Embedded Pages
------------------------*/
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2016010700.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2016011100.15; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.