FEATURE: ability to restrict some tags to a category while allowing all others

FEATURE: ability to restrict some tags to a category while allowing all others

A new checkbox has been added to the Tags tab of the category settings modal which is used when some tags and/or tag groups are restricted to the category, and all other unrestricted tags should also be allowed. Default is the same as the previous behaviour: only allow the specified set of tags and tag groups in the category.

diff --git a/app/assets/javascripts/discourse/components/edit-category-tags.js.es6 b/app/assets/javascripts/discourse/components/edit-category-tags.js.es6
index 1c583fd..6705f9e 100644
--- a/app/assets/javascripts/discourse/components/edit-category-tags.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-tags.js.es6
@@ -1,3 +1,11 @@
 import { buildCategoryPanel } from "discourse/components/edit-category-panel";
+import computed from "ember-addons/ember-computed-decorators";
 
-export default buildCategoryPanel("tags", {});
+export default buildCategoryPanel("tags", {
+  allowedTagsEmpty: Ember.computed.empty("category.allowed_tags"),
+  allowedTagGroupsEmpty: Ember.computed.empty("category.allowed_tag_groups"),
+  disableAllowGlobalTags: Ember.computed.and(
+    "allowedTagsEmpty",
+    "allowedTagGroupsEmpty"
+  )
+});
diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6
index d31fcb7..9e0c047 100644
--- a/app/assets/javascripts/discourse/models/category.js.es6
+++ b/app/assets/javascripts/discourse/models/category.js.es6
@@ -118,6 +118,7 @@ const Category = RestModel.extend({
         all_topics_wiki: this.get("all_topics_wiki"),
         allowed_tags: this.get("allowed_tags"),
         allowed_tag_groups: this.get("allowed_tag_groups"),
+        allow_global_tags: this.get("allow_global_tags"),
         sort_order: this.get("sort_order"),
         sort_ascending: this.get("sort_ascending"),
         topic_featured_link_allowed: this.get("topic_featured_link_allowed"),
diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs
index dcdc769..a0af5fb 100644
--- a/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs
+++ b/app/assets/javascripts/discourse/templates/components/edit-category-tags.hbs
@@ -11,6 +11,14 @@
 <section class="field">
   <label for="category-allowed-tag-groups">{{i18n 'category.tags_allowed_tag_groups'}}</label>
   {{tag-group-chooser id="category-allowed-tag-groups" tagGroups=category.allowed_tag_groups}}
+  {{#link-to 'tagGroups'}}{{i18n 'category.manage_tag_groups_link'}}{{/link-to}}
+</section>
+
+<section class='field'>
+  <label>
+    {{input type="checkbox" checked=category.allow_global_tags id="allow_global_tags" disabled=disableAllowGlobalTags}}
+    {{i18n 'category.allow_global_tags_label'}}
+  </label>
 </section>
 
 <section class='field'>
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index 3b9195c..ab9fdbe 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -295,6 +295,7 @@ class CategoriesController < ApplicationController
                       :minimum_required_tags,
                       :navigate_to_first_post_after_read,
                       :search_priority,
+                      :allow_global_tags,
                       custom_fields: [params[:custom_fields].try(:keys)],
                       permissions: [*p.try(:keys)],
                       allowed_tags: [],
diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb
index d7973eb..64f81fd 100644
--- a/app/serializers/category_serializer.rb
+++ b/app/serializers/category_serializer.rb
@@ -18,6 +18,7 @@ class CategorySerializer < BasicCategorySerializer
              :custom_fields,
              :allowed_tags,
              :allowed_tag_groups,
+             :allow_global_tags,
              :topic_featured_link_allowed,
              :search_priority
 
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index e8b9c9a..01cdc9a 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2483,11 +2483,13 @@ en:
       settings: "Settings"
       topic_template: "Topic Template"
       tags: "Tags"
-      tags_allowed_tags: "Only allow these tags to be used in this category:"
-      tags_allowed_tag_groups: "Only allow tags from these groups to be used in this category:"
+      tags_allowed_tags: "Restrict these tags to this category:"
+      tags_allowed_tag_groups: "Restrict these tag groups to this category:"
       tags_placeholder: "(Optional) list of allowed tags"
       tags_tab_description: "Tags and tag groups specified here will only be available in this category and other categories that also specify them. They won't be available for use in other categories."
       tag_groups_placeholder: "(Optional) list of allowed tag groups"
+      manage_tag_groups_link: "Manage tag groups here."
+      allow_global_tags_label: "Also allow other tags"
       topic_featured_link_allowed: "Allow featured links in this category"
       delete: "Delete Category"
       create: "New Category"
diff --git a/db/migrate/20190403180142_add_allow_global_tags_to_categories.rb b/db/migrate/20190403180142_add_allow_global_tags_to_categories.rb
new file mode 100644
index 0000000..3ab37e7
--- /dev/null
+++ b/db/migrate/20190403180142_add_allow_global_tags_to_categories.rb
@@ -0,0 +1,5 @@
+class AddAllowGlobalTagsToCategories < ActiveRecord::Migration[5.2]
+  def change
+    add_column :categories, :allow_global_tags, :boolean, default: false, null: false
+  end
+end
diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb
index 5ba2563..1b26986 100644
--- a/lib/discourse_tagging.rb
+++ b/lib/discourse_tagging.rb
@@ -100,21 +100,35 @@ module DiscourseTagging
     category = opts[:category]
 
     if category && (category.tags.count > 0 || category.tag_groups.count > 0)
-      if category.tags.count > 0 && category.tag_groups.count > 0
-        tag_group_ids = category.tag_groups.pluck(:id)
-
-        query = query.where(
-          "tags.id IN (SELECT tag_id FROM category_tags WHERE category_id = ?
+      if category.allow_global_tags
+        # Select tags that:
+        #   * are restricted to the given category
+        #   * belong to no tag groups and aren't restricted to other categories
+        #   * belong to tag groups that are not restricted to any categories
+        query = query.where(<<~SQL, category.tag_groups.pluck(:id), category.id)
+          tags.id IN (
+            SELECT t.id FROM tags t
+            LEFT JOIN category_tags ct ON t.id = ct.tag_id
+            LEFT JOIN (
+              SELECT xtgm.tag_id, xtgm.tag_group_id
+              FROM tag_group_memberships xtgm
+              INNER JOIN category_tag_groups ctg
+              ON xtgm.tag_group_id = ctg.tag_group_id
+            ) AS tgm ON t.id = tgm.tag_id
+            WHERE (tgm.tag_group_id IS NULL AND ct.category_id IS NULL)
+               OR tgm.tag_group_id IN (?)
+               OR ct.category_id = ?
+          )
+        SQL
+      else
+        # Select only tags that are restricted to the given category
+        query = query.where(<<~SQL, category.id, category.tag_groups.pluck(:id))
+          tags.id IN (
+            SELECT tag_id FROM category_tags WHERE category_id = ?
             UNION
-            SELECT tag_id FROM tag_group_memberships WHERE tag_group_id IN (?))",
-          category.id, tag_group_ids
-        )
-      elsif category.tags.count > 0
-        query = query.where("tags.id IN (SELECT tag_id FROM category_tags WHERE category_id = ?)", category.id)
-      else # category.tag_groups.count > 0
-        tag_group_ids = category.tag_groups.pluck(:id)
-
-        query = query.where("tags.id IN (SELECT tag_id FROM tag_group_memberships WHERE tag_group_id IN (?))", tag_group_ids)
+            SELECT tag_id FROM tag_group_memberships WHERE tag_group_id IN (?)
+          )
+        SQL
       end
     elsif opts[:for_input] || opts[:for_topic] || category
       # exclude tags that are restricted to other categories
diff --git a/spec/integration/category_tag_spec.rb b/spec/integration/category_tag_spec.rb
index 86ff857..11b003e 100644
--- a/spec/integration/category_tag_spec.rb
+++ b/spec/integration/category_tag_spec.rb

[... diff too long, it was truncated ...]

GitHub sha: 83996fc8

1 Like

This commit has been mentioned on Discourse Meta. There might be relevant details there:

Fancy, I had to read this a few times to fully understand the usage, but yeah makes sense.