FEATURE: support to mute all categories by default. (#8295)

FEATURE: support to mute all categories by default. (#8295)

Instead of enabling suppress_from_latest setting on many categories now we can enable mute_all_categories_by_default site setting. Then users should opt-in to categories for them to appear in the latest and categories pages.

diff --git a/app/assets/javascripts/discourse/templates/preferences/categories.hbs b/app/assets/javascripts/discourse/templates/preferences/categories.hbs
index e264ef1137..671242a16a 100644
--- a/app/assets/javascripts/discourse/templates/preferences/categories.hbs
+++ b/app/assets/javascripts/discourse/templates/preferences/categories.hbs
@@ -27,14 +27,16 @@
   </div>
   <div class="instructions">{{i18n 'user.watched_first_post_categories_instructions'}}</div>
 
-  <div class="controls tracking-controls">
-    <label>{{d-icon "d-muted"}} {{i18n 'user.muted_categories'}}</label>
-    {{#if canSee}}
-      <a class="show-tracking" href="{{unbound model.mutedTopicsPath}}">{{i18n 'user.tracked_topics_link'}}</a>
-    {{/if}}
-    {{category-selector categories=model.mutedCategories blacklist=selectedCategories}}
-  </div>
-  <div class="instructions">{{i18n (if hideMutedTags 'user.muted_categories_instructions' 'user.muted_categories_instructions_dont_hide')}}</div>
+  {{#unless siteSettings.mute_all_categories_by_default}}
+    <div class="controls tracking-controls">
+      <label>{{d-icon "d-muted"}} {{i18n 'user.muted_categories'}}</label>
+      {{#if canSee}}
+        <a class="show-tracking" href="{{unbound model.mutedTopicsPath}}">{{i18n 'user.tracked_topics_link'}}</a>
+      {{/if}}
+      {{category-selector categories=model.mutedCategories blacklist=selectedCategories}}
+    </div>
+    <div class="instructions">{{i18n (if hideMutedTags 'user.muted_categories_instructions' 'user.muted_categories_instructions_dont_hide')}}</div>
+  {{/unless}}
 </div>
 
 {{plugin-outlet name="user-preferences-categories" args=(hash model=model save=(action "save"))}}
diff --git a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6
index b11689796a..be146f4285 100644
--- a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6
@@ -173,7 +173,7 @@ export default createWidget("hamburger-menu", {
   listCategories() {
     const maxCategoriesToDisplay = this.siteSettings
       .header_dropdown_category_count;
-    let categories = this.site.get("categoriesByCount");
+    let categories = [];
 
     if (this.currentUser) {
       const allCategories = this.site
@@ -203,6 +203,10 @@ export default createWidget("hamburger-menu", {
           .filter(c => !categories.includes(c))
           .sort((a, b) => b.topic_count - a.topic_count)
       );
+    } else {
+      categories = this.site
+        .get("categoriesByCount")
+        .filter(c => c.notification_level !== NotificationLevels.MUTED);
     }
 
     if (!this.siteSettings.allow_uncategorized_topics) {
diff --git a/app/models/category_list.rb b/app/models/category_list.rb
index f1f7b6a0d9..bc1d6a4980 100644
--- a/app/models/category_list.rb
+++ b/app/models/category_list.rb
@@ -92,16 +92,12 @@ class CategoryList
 
     @categories = @categories.to_a
 
-    category_user = {}
-    default_notification_level = nil
-    unless @guardian.anonymous?
-      category_user = Hash[*CategoryUser.where(user: @guardian.user).pluck(:category_id, :notification_level).flatten]
-      default_notification_level = CategoryUser.notification_levels[:regular]
-    end
+    notification_levels = CategoryUser.notification_levels_for(@guardian)
+    default_notification_level = CategoryUser.default_notification_level
 
     allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id))
     @categories.each do |category|
-      category.notification_level = category_user[category.id] || default_notification_level
+      category.notification_level = notification_levels[category.id] || default_notification_level
       category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id)
       category.has_children = category.subcategories.present?
     end
diff --git a/app/models/category_user.rb b/app/models/category_user.rb
index e25f917d32..d2dd1a4d36 100644
--- a/app/models/category_user.rb
+++ b/app/models/category_user.rb
@@ -197,6 +197,26 @@ class CategoryUser < ActiveRecord::Base
     SQL
   end
 
+  def self.default_notification_level
+    SiteSetting.mute_all_categories_by_default ? notification_levels[:muted] : notification_levels[:regular]
+  end
+
+  def self.notification_levels_for(guardian)
+    if guardian.anonymous?
+      notification_levels = [
+        SiteSetting.default_categories_watching.split("|"),
+        SiteSetting.default_categories_tracking.split("|"),
+        SiteSetting.default_categories_watching_first_post.split("|"),
+      ].flatten.map { |id| [id.to_i, 1] }
+
+      notification_levels += SiteSetting.default_categories_muted.split("|").map { |id| [id.to_i, 0] }
+    else
+      notification_levels = CategoryUser.where(user: guardian.user).pluck(:category_id, :notification_level)
+    end
+
+    Hash[*notification_levels.flatten]
+  end
+
 end
 
 # == Schema Information
diff --git a/app/models/site.rb b/app/models/site.rb
index c7330e1e25..d8bddf0a99 100644
--- a/app/models/site.rb
+++ b/app/models/site.rb
@@ -55,15 +55,11 @@ class Site
 
       by_id = {}
 
-      category_user = {}
-      unless @guardian.anonymous?
-        category_user = Hash[*CategoryUser.where(user: @guardian.user).pluck(:category_id, :notification_level).flatten]
-      end
-
-      regular = CategoryUser.notification_levels[:regular]
+      notification_levels = CategoryUser.notification_levels_for(@guardian)
+      default_notification_level = CategoryUser.default_notification_level
 
       categories.each do |category|
-        category.notification_level = category_user[category.id] || regular
+        category.notification_level = notification_levels[category.id] || default_notification_level
         category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create&.include?(category.id) || @guardian.is_admin?
         category.has_children = with_children.include?(category.id)
         by_id[category.id] = category
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index ffd59c8380..dac88f2d93 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -2067,6 +2067,7 @@ en:
     default_categories_tracking: "List of categories that are tracked by default."
     default_categories_muted: "List of categories that are muted by default."
     default_categories_watching_first_post: "List of categories in which first post in each new topic will be watched by default."
+    mute_all_categories_by_default: "Set the default notification level of all the categories to muted. Require users opt-in to categories for them to appear in 'latest' and 'categories' pages. If you wish to amend the defaults for anonymous users set 'default_categories_' settings."
 
     default_tags_watching: "List of tags that are watched by default."
     default_tags_tracking: "List of tags that are tracked by default."
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 63f1aecf1c..02a4ae3386 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -2022,6 +2022,9 @@ user_preferences:
   default_categories_watching_first_post:
     type: category_list
     default: ""
+  mute_all_categories_by_default:
+    default: false
+    client: true
 
   default_tags_watching:
     type: tag_list
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index 8a2c4637f3..dec1268030 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -869,7 +869,34 @@ class TopicQuery
   def remove_muted_categories(list, user, opts = nil)
     category_id = get_category_id(opts[:exclude]) if opts
 
-    if user
+    if SiteSetting.mute_all_categories_by_default
+      if user
+        list = list.references("cu")
+          .where("
+          NOT EXISTS (
+            SELECT 1
+              FROM categories c
+              LEFT OUTER JOIN category_users cu

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

GitHub sha: ba5b78a3

1 Like

It’s best to avoid using magic numbers and instead use the appropriate constants.

[id.to_i, 1] should be replaced by [id.to_i, notification_levels[:regular]]

1 Like

Ditto,

[id.to_i, 0] should be replaced by [id.to_i, notification_levels[:muted]]

1 Like

FIX: remove magic numbers in notification levels.