From f3f6c2f98fcf5787702b0101ac42f28915a51e37 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Mon, 6 Jun 2016 14:18:15 -0400 Subject: [PATCH] FEATURE: tag groups --- .../components/tags-admin-dropdown.js.es6 | 29 +++++++++ .../controllers/tag-groups-show.js.es6 | 7 +++ .../discourse/controllers/tag-groups.js.es6 | 18 ++++++ .../discourse/models/tag-group.js.es6 | 34 ++++++++++ .../discourse/routes/app-route-map.js.es6 | 4 ++ .../discourse/routes/tag-groups-show.js.es6 | 5 ++ .../discourse/routes/tag-groups.js.es6 | 9 +++ .../discourse/routes/tags-index.js.es6 | 5 ++ .../discourse/templates/tag-groups-index.hbs | 3 + .../discourse/templates/tag-groups-show.hbs | 12 ++++ .../discourse/templates/tag-groups.hbs | 16 +++++ .../discourse/templates/tags/index.hbs | 10 ++- .../stylesheets/common/admin/admin_base.scss | 38 ------------ .../stylesheets/common/base/discourse.scss | 38 ++++++++++++ .../stylesheets/common/base/tagging.scss | 32 ++++++++++ .../stylesheets/desktop/topic-list.scss | 12 ++-- app/assets/stylesheets/mobile/topic-list.scss | 2 +- app/controllers/tag_groups_controller.rb | 62 +++++++++++++++++++ app/models/category.rb | 11 +--- app/models/tag.rb | 3 + app/models/tag_group.rb | 8 +++ app/models/tag_group_membership.rb | 4 ++ app/serializers/tag_group_serializer.rb | 7 +++ config/locales/client.en.yml | 10 +++ config/routes.rb | 1 + .../20160602164008_create_tag_groups.rb | 17 +++++ lib/discourse_tagging.rb | 15 +++++ lib/guardian.rb | 13 +--- lib/guardian/tag_guardian.rb | 18 ++++++ spec/components/guardian_spec.rb | 58 +++++++++++++++++ 30 files changed, 434 insertions(+), 67 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 create mode 100644 app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 create mode 100644 app/assets/javascripts/discourse/controllers/tag-groups.js.es6 create mode 100644 app/assets/javascripts/discourse/models/tag-group.js.es6 create mode 100644 app/assets/javascripts/discourse/routes/tag-groups-show.js.es6 create mode 100644 app/assets/javascripts/discourse/routes/tag-groups.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/tag-groups-index.hbs create mode 100644 app/assets/javascripts/discourse/templates/tag-groups-show.hbs create mode 100644 app/assets/javascripts/discourse/templates/tag-groups.hbs create mode 100644 app/controllers/tag_groups_controller.rb create mode 100644 app/models/tag_group.rb create mode 100644 app/models/tag_group_membership.rb create mode 100644 app/serializers/tag_group_serializer.rb create mode 100644 db/migrate/20160602164008_create_tag_groups.rb create mode 100644 lib/guardian/tag_guardian.rb diff --git a/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 new file mode 100644 index 00000000000..c90efb35f9a --- /dev/null +++ b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 @@ -0,0 +1,29 @@ +import { iconHTML } from 'discourse/helpers/fa-icon'; +import DropdownButton from 'discourse/components/dropdown-button'; +import computed from "ember-addons/ember-computed-decorators"; + +export default DropdownButton.extend({ + buttonExtraClasses: 'no-text', + title: '', + text: iconHTML('bars') + ' ' + iconHTML('caret-down'), + classNames: ['tags-admin-menu'], + + @computed() + dropDownContent() { + const items = [ + { id: 'manageGroups', + title: I18n.t('tagging.manage_groups'), + description: I18n.t('tagging.manage_groups_description'), + styleClasses: 'fa fa-wrench' } + ]; + return items; + }, + + actionNames: { + manageGroups: 'showTagGroups' + }, + + clicked(id) { + this.sendAction('actionNames.' + id); + } +}); diff --git a/app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 b/app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 new file mode 100644 index 00000000000..1def5910f91 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/tag-groups-show.js.es6 @@ -0,0 +1,7 @@ +export default Ember.Controller.extend({ + actions: { + save() { + this.get('model').save(); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/tag-groups.js.es6 b/app/assets/javascripts/discourse/controllers/tag-groups.js.es6 new file mode 100644 index 00000000000..4e52a8ce476 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/tag-groups.js.es6 @@ -0,0 +1,18 @@ +export default Ember.ArrayController.extend({ + actions: { + selectTagGroup: function(tagGroup) { + if (this.get('selectedItem')) { this.get('selectedItem').set('selected', false); } + this.set('selectedItem', tagGroup); + tagGroup.set('selected', true); + tagGroup.set('savingStatus', null); + this.transitionToRoute('tagGroups.show', tagGroup); + }, + + newTagGroup: function() { + const newTagGroup = this.store.createRecord('tag-group'); + newTagGroup.set('name', I18n.t('tagging.groups.new_name')); + this.pushObject(newTagGroup); + this.send('selectTagGroup', newTagGroup); + } + } +}); diff --git a/app/assets/javascripts/discourse/models/tag-group.js.es6 b/app/assets/javascripts/discourse/models/tag-group.js.es6 new file mode 100644 index 00000000000..2bebcc333e9 --- /dev/null +++ b/app/assets/javascripts/discourse/models/tag-group.js.es6 @@ -0,0 +1,34 @@ +import RestModel from 'discourse/models/rest'; +import computed from 'ember-addons/ember-computed-decorators'; + +const TagGroup = RestModel.extend({ + @computed('name', 'tag_names') + disableSave() { + return Ember.isEmpty(this.get('name')) || Ember.isEmpty(this.get('tag_names')) || this.get('saving'); + }, + + save: function() { + var url = "/tag_groups", + self = this; + if (this.get('id')) { + url = "/tag_groups/" + this.get('id'); + } + + this.set('savingStatus', I18n.t('saving')); + this.set('saving', true); + + return Discourse.ajax(url, { + data: { + name: this.get('name'), + tag_names: this.get('tag_names') + }, + type: this.get('id') ? 'PUT' : 'POST' + }).then(function(result) { + if(result.id) { self.set('id', result.id); } + self.set('savingStatus', I18n.t('saved')); + self.set('saving', false); + }); + } +}); + +export default TagGroup; diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 index 2b06baa10ab..e7c2a986545 100644 --- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 +++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 @@ -131,4 +131,8 @@ export default function() { this.route('showParentCategory' + filter.capitalize(), {path: '/c/:parent_category/:category/:tag_id/l/' + filter}); }); }); + + this.resource('tagGroups', {path: '/tag_groups'}, function() { + this.route('show', {path: '/:id'}); + }); } diff --git a/app/assets/javascripts/discourse/routes/tag-groups-show.js.es6 b/app/assets/javascripts/discourse/routes/tag-groups-show.js.es6 new file mode 100644 index 00000000000..0d67542b2f9 --- /dev/null +++ b/app/assets/javascripts/discourse/routes/tag-groups-show.js.es6 @@ -0,0 +1,5 @@ +export default Discourse.Route.extend({ + model(params) { + return this.store.find('tagGroup', params.id); + } +}); diff --git a/app/assets/javascripts/discourse/routes/tag-groups.js.es6 b/app/assets/javascripts/discourse/routes/tag-groups.js.es6 new file mode 100644 index 00000000000..6d6476964af --- /dev/null +++ b/app/assets/javascripts/discourse/routes/tag-groups.js.es6 @@ -0,0 +1,9 @@ +export default Discourse.Route.extend({ + model() { + return this.store.findAll('tagGroup'); + }, + + titleToken() { + return I18n.t("tagging.groups.title"); + }, +}); diff --git a/app/assets/javascripts/discourse/routes/tags-index.js.es6 b/app/assets/javascripts/discourse/routes/tags-index.js.es6 index b6514ef278d..dcbe6f19290 100644 --- a/app/assets/javascripts/discourse/routes/tags-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/tags-index.js.es6 @@ -18,6 +18,11 @@ export default Discourse.Route.extend({ didTransition() { this.controllerFor("application").set("showFooter", true); return true; + }, + + showTagGroups() { + this.transitionTo('tagGroups'); + return true; } } }); diff --git a/app/assets/javascripts/discourse/templates/tag-groups-index.hbs b/app/assets/javascripts/discourse/templates/tag-groups-index.hbs new file mode 100644 index 00000000000..0c933221d97 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/tag-groups-index.hbs @@ -0,0 +1,3 @@ +
+

{{i18n 'tagging.groups.about'}}

+
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/templates/tag-groups-show.hbs b/app/assets/javascripts/discourse/templates/tag-groups-show.hbs new file mode 100644 index 00000000000..e2555d40e16 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/tag-groups-show.hbs @@ -0,0 +1,12 @@ +
+

{{text-field value=model.name}}

+
+
+ +
+ {{tag-chooser tags=model.tag_names everyTag="false" unlimitedTagCount="true"}} +
+
+ + {{model.savingStatus}} +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/templates/tag-groups.hbs b/app/assets/javascripts/discourse/templates/tag-groups.hbs new file mode 100644 index 00000000000..139874e75d0 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/tag-groups.hbs @@ -0,0 +1,16 @@ +
+

{{i18n "tagging.groups.title"}}

+ +
+ + +
+ + {{outlet}} + +
+
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/templates/tags/index.hbs b/app/assets/javascripts/discourse/templates/tags/index.hbs index 239b2b6c1af..6a31bfdf4f9 100644 --- a/app/assets/javascripts/discourse/templates/tags/index.hbs +++ b/app/assets/javascripts/discourse/templates/tags/index.hbs @@ -2,14 +2,20 @@ {{discourse-banner user=currentUser banner=site.banner}}
-

{{i18n "tagging.tags"}}

+
+
+ {{tags-admin-dropdown}} +

{{i18n "tagging.tags"}}

+
+
{{i18n "tagging.sort_by"}} {{i18n "tagging.sort_by_count"}} {{i18n "tagging.sort_by_name"}}
-
+ +
{{#each model.extras.categories as |category|}} {{tag-list tags=category.tags sortProperties=sortProperties categoryId=category.id}} diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index e86daf1d3ec..41b62309f08 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -1200,44 +1200,6 @@ table.api-keys { } } -.content-list { - - h3 { - color: dark-light-diff($primary, $secondary, 50%, -20%); - font-size: 1.071em; - padding-left: 5px; - margin-bottom: 10px; - } - - ul { - list-style: none; - margin: 0; - - li:first-of-type { - border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); - } - li { - border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); - } - - li a { - display: block; - padding: 10px; - color: $primary; - - &:hover { - background-color: dark-light-diff($primary, $secondary, 90%, -60%); - color: $primary; - } - - &.active { - font-weight: bold; - color: $primary; - } - } - } -} - .content-editor { min-height: 500px; diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 5fe4eedfd7f..0d68b07b9ac 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -216,6 +216,44 @@ body { } } +.content-list { + + h3 { + color: dark-light-diff($primary, $secondary, 50%, -20%); + font-size: 1.071em; + padding-left: 5px; + margin-bottom: 10px; + } + + ul { + list-style: none; + margin: 0; + + li:first-of-type { + border-top: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + } + li { + border-bottom: 1px solid dark-light-diff($primary, $secondary, 90%, -60%); + } + + li a { + display: block; + padding: 10px; + color: $primary; + + &:hover { + background-color: dark-light-diff($primary, $secondary, 90%, -60%); + color: $primary; + } + + &.active { + font-weight: bold; + color: $primary; + } + } + } +} + // don't wrap relative dates, we want // // Jul 26, '15 diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss index 9fd6e39230b..c1ed04ec934 100644 --- a/app/assets/stylesheets/common/base/tagging.scss +++ b/app/assets/stylesheets/common/base/tagging.scss @@ -201,3 +201,35 @@ header .discourse-tag {color: $tag-color !important; } color: $tag-color; } } + +.tags-admin-menu { + margin-top: 20px; + ul { + width: 320px; + } +} + +.tag-groups-container { + margin-top: 20px; + .content-list { + width: 20%; + float: left; + margin: 20px 0; + ul { + margin-bottom: 10px; + } + } + .tag-group-content { + width: 75%; + float: right; + } + .group-tags-list .tag-chooser { + height: 150px !important; + .select2-choices { + height: 150px !important; // to fight with select2.scss's important + } + } + .saving { + margin-left: 10px; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss index 74c1ab80ee9..63d3e9679aa 100644 --- a/app/assets/stylesheets/desktop/topic-list.scss +++ b/app/assets/stylesheets/desktop/topic-list.scss @@ -272,11 +272,13 @@ button.dismiss-read { margin-left: 10px; } -.category-notification-menu .dropdown-menu { - right: 0; - top: 30px; - bottom: auto; - left: auto; +.category-notification-menu, .tags-admin-menu { + .dropdown-menu { + right: 0; + top: 30px; + bottom: auto; + left: auto; + } } .category-heading { diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss index 01f656b6e51..5ca8cb7b5a1 100644 --- a/app/assets/stylesheets/mobile/topic-list.scss +++ b/app/assets/stylesheets/mobile/topic-list.scss @@ -379,7 +379,7 @@ ol.category-breadcrumb { .btn-default.pull-right { margin-right: 10px; } } -.category-notification-menu { +.category-notification-menu, .tags-admin-menu { display: none; } diff --git a/app/controllers/tag_groups_controller.rb b/app/controllers/tag_groups_controller.rb new file mode 100644 index 00000000000..9ba20c9da9c --- /dev/null +++ b/app/controllers/tag_groups_controller.rb @@ -0,0 +1,62 @@ +class TagGroupsController < ApplicationController + skip_before_filter :check_xhr, only: [:index, :show] + before_filter :ensure_logged_in, except: [:index, :show] + before_filter :fetch_tag_group, only: [:show, :update, :destroy] + + def index + tag_groups = TagGroup.order('name ASC').preload(:tags).all + serializer = ActiveModel::ArraySerializer.new(tag_groups, each_serializer: TagGroupSerializer, root: 'tag_groups') + respond_to do |format| + format.html do + store_preloaded "tagGroups", MultiJson.dump(serializer) + render "default/empty" + end + format.json { render_json_dump(serializer) } + end + end + + def show + serializer = TagGroupSerializer.new(@tag_group) + respond_to do |format| + format.html do + store_preloaded "tagGroup", MultiJson.dump(serializer) + render "default/empty" + end + format.json { render_json_dump(serializer) } + end + end + + def create + guardian.ensure_can_admin_tag_groups! + @tag_group = TagGroup.new(tag_groups_params) + if @tag_group.save + render_serialized(@tag_group, TagGroupSerializer) + else + return render_json_error(@tag_group) + end + end + + def update + guardian.ensure_can_admin_tag_groups! + json_result(@tag_group, serializer: TagGroupSerializer) do |tag_group| + @tag_group.update(tag_groups_params) + end + end + + def destroy + guardian.ensure_can_admin_tag_groups! + @tag_group.destroy + render json: success_json + end + + private + + def fetch_tag_group + @tag_group = TagGroup.find(params[:id]) + end + + def tag_groups_params + params[:tag_names] ||= [] + params.permit(:id, :name, :tag_names => []) + end +end diff --git a/app/models/category.rb b/app/models/category.rb index a6f516ad386..97341b9a48b 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -316,16 +316,7 @@ SQL end def allowed_tags=(tag_names_arg) - tag_names = DiscourseTagging.tags_for_saving(tag_names_arg, Guardian.new(Discourse.system_user)) || [] - if self.tags.pluck(:name).sort != tag_names.sort - self.tags = Tag.where(name: tag_names).all - if self.tags.size < tag_names.size - new_tag_names = tag_names - self.tags.map(&:name) - new_tag_names.each do |name| - self.tags << Tag.create(name: name) - end - end - end + DiscourseTagging.add_or_create_tags_by_name(self, tag_names_arg) end def downcase_email diff --git a/app/models/tag.rb b/app/models/tag.rb index 12d7adf99b4..61cacb74d2d 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -9,6 +9,9 @@ class Tag < ActiveRecord::Base has_many :category_tags, dependent: :destroy has_many :categories, through: :category_tags + has_many :tag_group_memberships + has_many :tag_groups, through: :tag_group_memberships + def self.tags_by_count_query(opts={}) q = TopicTag.joins(:tag, :topic).group("topic_tags.tag_id, tags.name").order('count_all DESC') q = q.limit(opts[:limit]) if opts[:limit] diff --git a/app/models/tag_group.rb b/app/models/tag_group.rb new file mode 100644 index 00000000000..9bd7f11bb2b --- /dev/null +++ b/app/models/tag_group.rb @@ -0,0 +1,8 @@ +class TagGroup < ActiveRecord::Base + has_many :tag_group_memberships, dependent: :destroy + has_many :tags, through: :tag_group_memberships + + def tag_names=(tag_names_arg) + DiscourseTagging.add_or_create_tags_by_name(self, tag_names_arg) + end +end diff --git a/app/models/tag_group_membership.rb b/app/models/tag_group_membership.rb new file mode 100644 index 00000000000..0fee1fdcf60 --- /dev/null +++ b/app/models/tag_group_membership.rb @@ -0,0 +1,4 @@ +class TagGroupMembership < ActiveRecord::Base + belongs_to :tag + belongs_to :tag_group, counter_cache: "tag_count" +end diff --git a/app/serializers/tag_group_serializer.rb b/app/serializers/tag_group_serializer.rb new file mode 100644 index 00000000000..0aa0f412d7d --- /dev/null +++ b/app/serializers/tag_group_serializer.rb @@ -0,0 +1,7 @@ +class TagGroupSerializer < ApplicationSerializer + attributes :id, :name, :tag_names + + def tag_names + object.tags.pluck(:name).sort + end +end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 60bcbe02d8f..b9e43f88312 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2986,6 +2986,8 @@ en: sort_by: "Sort by:" sort_by_count: "count" sort_by_name: "name" + manage_groups: "Manage Tag Groups" + manage_groups_description: "Define groups to organize tags" filters: without_category: "%{filter} %{tag} topics" @@ -3005,6 +3007,14 @@ en: title: "Muted" description: "You will not be notified of anything about new topics in this tag, and they will not appear on your unread tab." + groups: + title: "Tag Groups" + about: "Add tags to groups to manage them more easily." + new: "New Group" + tags_label: "Tags in this group:" + new_name: "New Tag Group" + save: "Save" + topics: none: unread: "You have no unread topics." diff --git a/config/routes.rb b/config/routes.rb index 626d2a0a871..6baaabd8d55 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -636,6 +636,7 @@ Discourse::Application.routes.draw do end end end + resources :tag_groups, except: [:new, :edit] Discourse.filters.each do |filter| root to: "list##{filter}", constraints: HomePageConstraint.new("#{filter}"), :as => "list_#{filter}" diff --git a/db/migrate/20160602164008_create_tag_groups.rb b/db/migrate/20160602164008_create_tag_groups.rb new file mode 100644 index 00000000000..e851ff39b95 --- /dev/null +++ b/db/migrate/20160602164008_create_tag_groups.rb @@ -0,0 +1,17 @@ +class CreateTagGroups < ActiveRecord::Migration + def change + create_table :tag_groups do |t| + t.string :name, null: false + t.integer :tag_count, null: false, default: 0 + t.timestamps + end + + create_table :tag_group_memberships do |t| + t.references :tag, null: false + t.references :tag_group, null: false + t.timestamps + end + + add_index :tag_group_memberships, [:tag_group_id, :tag_id], unique: true + end +end diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb index ca6b9bdc5a2..48ce1c00dc8 100644 --- a/lib/discourse_tagging.rb +++ b/lib/discourse_tagging.rb @@ -120,10 +120,25 @@ module DiscourseTagging return tag_names[0...SiteSetting.max_tags_per_topic] end + def self.add_or_create_tags_by_name(taggable, tag_names_arg) + tag_names = DiscourseTagging.tags_for_saving(tag_names_arg, Guardian.new(Discourse.system_user)) || [] + if taggable.tags.pluck(:name).sort != tag_names.sort + taggable.tags = Tag.where(name: tag_names).all + if taggable.tags.size < tag_names.size + new_tag_names = tag_names - taggable.tags.map(&:name) + new_tag_names.each do |name| + taggable.tags << Tag.create(name: name) + end + end + end + end + + # TODO: this is unused? def self.notification_key(tag_id) "tags_notification:#{tag_id}" end + # TODO: this is unused? def self.muted_tags(user) return [] unless user UserCustomField.where(user_id: user.id, value: TopicUser.notification_levels[:muted]).pluck(:name).map { |x| x[0,17] == "tags_notification" ? x[18..-1] : nil}.compact diff --git a/lib/guardian.rb b/lib/guardian.rb index 2e68f246305..717a90adc9f 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -5,6 +5,7 @@ require_dependency 'guardian/topic_guardian' require_dependency 'guardian/user_guardian' require_dependency 'guardian/post_revision_guardian' require_dependency 'guardian/group_guardian' +require_dependency 'guardian/tag_guardian' # The guardian is responsible for confirming access to various site resources and operations class Guardian @@ -15,6 +16,7 @@ class Guardian include UserGuardian include PostRevisionGuardian include GroupGuardian + include TagGuardian class AnonymousUser def blank?; true; end @@ -277,17 +279,6 @@ class Guardian UserExport.where(user_id: @user.id, created_at: (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day)).count == 0 end - def can_create_tag? - user && user.has_trust_level?(SiteSetting.min_trust_to_create_tag.to_i) - end - - def can_tag_topics? - user && user.has_trust_level?(SiteSetting.min_trust_level_to_tag_topics.to_i) - end - - def can_admin_tags? - is_staff? - end private diff --git a/lib/guardian/tag_guardian.rb b/lib/guardian/tag_guardian.rb new file mode 100644 index 00000000000..b9315078df3 --- /dev/null +++ b/lib/guardian/tag_guardian.rb @@ -0,0 +1,18 @@ +#mixin for all guardian methods dealing with tagging permisions +module TagGuardian + def can_create_tag? + user && SiteSetting.tagging_enabled && user.has_trust_level?(SiteSetting.min_trust_to_create_tag.to_i) + end + + def can_tag_topics? + user && user.has_trust_level?(SiteSetting.min_trust_level_to_tag_topics.to_i) + end + + def can_admin_tags? + is_staff? && SiteSetting.tagging_enabled + end + + def can_admin_tag_groups? + is_staff? && SiteSetting.tagging_enabled + end +end diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index 1f130d4da49..3346b97aa3f 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -2208,4 +2208,62 @@ describe Guardian do end end end + + describe "Tags" do + context "tagging disabled" do + before do + SiteSetting.tagging_enabled = false + end + + it "can_create_tag returns false" do + expect(Guardian.new(admin).can_create_tag?).to be_falsey + end + + it "can_admin_tags returns false" do + expect(Guardian.new(admin).can_admin_tags?).to be_falsey + end + + it "can_admin_tag_groups returns false" do + expect(Guardian.new(admin).can_admin_tag_groups?).to be_falsey + end + end + + context "tagging is enabled" do + before do + SiteSetting.tagging_enabled = true + SiteSetting.min_trust_to_create_tag = 3 + SiteSetting.min_trust_level_to_tag_topics = 1 + end + + describe "can_create_tag" do + it "returns false if trust level is too low" do + expect(Guardian.new(trust_level_2).can_create_tag?).to be_falsey + end + + it "returns true if trust level is high enough" do + expect(Guardian.new(trust_level_3).can_create_tag?).to be_truthy + end + + it "returns true for staff" do + expect(Guardian.new(admin).can_create_tag?).to be_truthy + expect(Guardian.new(moderator).can_create_tag?).to be_truthy + end + end + + describe "can_tag_topics" do + it "returns false if trust level is too low" do + expect(Guardian.new(Fabricate(:user, trust_level: 0)).can_tag_topics?).to be_falsey + end + + it "returns true if trust level is high enough" do + expect(Guardian.new(Fabricate(:user, trust_level: 1)).can_tag_topics?).to be_truthy + end + + it "returns true for staff" do + expect(Guardian.new(admin).can_tag_topics?).to be_truthy + expect(Guardian.new(moderator).can_tag_topics?).to be_truthy + end + end + end + end end