Skip to content

Commit 444dac8

Browse files
DEV: Change accept_all_solutions_trust_level to group setting (#276)
This refactor makes for easier testing and makes things more organised, the guardian extensions had no testing whatsoever and I need some to make the TL -> group change.
1 parent fa3e159 commit 444dac8

7 files changed

+161
-46
lines changed

app/lib/accepted_answer_cache.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseSolved
4+
class AcceptedAnswerCache
5+
@@allowed_accepted_cache = DistributedCache.new("allowed_accepted")
6+
7+
def self.reset_accepted_answer_cache
8+
@@allowed_accepted_cache["allowed"] = begin
9+
Set.new(
10+
CategoryCustomField.where(
11+
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
12+
value: "true",
13+
).pluck(:category_id),
14+
)
15+
end
16+
end
17+
18+
def self.allowed
19+
@@allowed_accepted_cache["allowed"]
20+
end
21+
end
22+
end

app/lib/guardian_extensions.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseSolved
4+
module GuardianExtensions
5+
def allow_accepted_answers?(category_id, tag_names = [])
6+
return true if SiteSetting.allow_solved_on_all_topics
7+
8+
if SiteSetting.enable_solved_tags.present? && tag_names.present?
9+
allowed_tags = SiteSetting.enable_solved_tags.split("|")
10+
is_allowed = (tag_names & allowed_tags).present?
11+
12+
return true if is_allowed
13+
end
14+
15+
return false if category_id.blank?
16+
if !::DiscourseSolved::AcceptedAnswerCache.allowed
17+
::DiscourseSolved::AcceptedAnswerCache.reset_accepted_answer_cache
18+
end
19+
::DiscourseSolved::AcceptedAnswerCache.allowed.include?(category_id)
20+
end
21+
22+
def can_accept_answer?(topic, post)
23+
return false if !authenticated?
24+
return false if !topic || !post || post.whisper?
25+
return false if !allow_accepted_answers?(topic.category_id, topic.tags.map(&:name))
26+
27+
return true if is_staff?
28+
if current_user.in_any_groups?(SiteSetting.accept_all_solutions_allowed_groups_map)
29+
return true
30+
end
31+
return true if can_perform_action_available_to_group_moderators?(topic)
32+
33+
topic.user_id == current_user.id && !topic.closed && SiteSetting.accept_solutions_topic_author
34+
end
35+
end
36+
end

config/locales/server.en.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ en:
55
solved_enabled: "Enable solved plugin, allow users to select solutions for topics"
66
allow_solved_on_all_topics: "Allow users to select solutions on all topics (when unchecked, solutions can be enabled per category or tag)"
77
accept_all_solutions_trust_level: "Minimum trust level required to accept solutions on any topic (even when not OP)"
8+
accept_all_solutions_allowed_groups: "Groups that are allowed to accept solutions on any topic (even when not OP)"
89
empty_box_on_unsolved: "Display an empty box next to unsolved topics"
910
solved_quote_length: "Number of characters to quote when displaying the solution under the first post"
1011
solved_topics_auto_close_hours: "Auto close topic (n) hours after the last reply once the topic has been marked as solved. Set to 0 to disable auto closing."
@@ -16,6 +17,9 @@ en:
1617
enable_solved_tags: "Tags that will allow users to select solutions."
1718
prioritize_solved_topics_in_search: "Prioritize solved topics in search results."
1819

20+
keywords:
21+
accept_all_solutions_allowed_groups: "accept_all_solutions_trust_level"
22+
1923
reports:
2024
accepted_solutions:
2125
title: "Accepted solutions"

config/settings.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ discourse_solved:
88
accept_all_solutions_trust_level:
99
default: 4
1010
client: true
11+
enum: "TrustLevelSetting"
12+
hidden: true
13+
accept_all_solutions_allowed_groups:
14+
default: "14" # auto group trust_level_4
15+
type: group_list
16+
client: false
17+
allow_any: false
18+
refresh: true
19+
validator: "AtLeastOneGroupValidator"
1120
empty_box_on_unsolved:
1221
default: false
1322
client: true
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
class FillAcceptAllSolutionsAllowedGroupsBasedOnDeprecatedSetting < ActiveRecord::Migration[7.0]
4+
def up
5+
old_setting_trust_level =
6+
DB.query_single(
7+
"SELECT value FROM site_settings WHERE name = 'accept_all_solutions_trust_level' LIMIT 1",
8+
).first
9+
10+
if old_setting_trust_level.present?
11+
allowed_groups = "1#{old_setting_trust_level}"
12+
13+
DB.exec(
14+
"INSERT INTO site_settings(name, value, data_type, created_at, updated_at)
15+
VALUES('accept_all_solutions_allowed_groups', :setting, '20', NOW(), NOW())",
16+
setting: allowed_groups,
17+
)
18+
end
19+
end
20+
21+
def down
22+
raise ActiveRecord::IrreversibleMigration
23+
end
24+
end

plugin.rb

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@
2525

2626
%w[
2727
../app/lib/first_accepted_post_solution_validator.rb
28+
../app/lib/accepted_answer_cache.rb
29+
../app/lib/guardian_extensions.rb
2830
../app/serializers/concerns/topic_answer_mixin.rb
2931
].each { |path| load File.expand_path(path, __FILE__) }
3032

3133
skip_db = defined?(GlobalSetting.skip_db?) && GlobalSetting.skip_db?
3234

35+
reloadable_patch { |plugin| Guardian.prepend(DiscourseSolved::GuardianExtensions) }
36+
3337
# we got to do a one time upgrade
3438
if !skip_db && defined?(UserAction::SOLVED)
3539
unless Discourse.redis.get("solved_already_upgraded")
@@ -501,52 +505,7 @@ class ::Category
501505
protected
502506

503507
def reset_accepted_cache
504-
::Guardian.reset_accepted_answer_cache
505-
end
506-
end
507-
508-
class ::Guardian
509-
@@allowed_accepted_cache = DistributedCache.new("allowed_accepted")
510-
511-
def self.reset_accepted_answer_cache
512-
@@allowed_accepted_cache["allowed"] = begin
513-
Set.new(
514-
CategoryCustomField.where(
515-
name: ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD,
516-
value: "true",
517-
).pluck(:category_id),
518-
)
519-
end
520-
end
521-
522-
def allow_accepted_answers?(category_id, tag_names = [])
523-
return true if SiteSetting.allow_solved_on_all_topics
524-
525-
if SiteSetting.enable_solved_tags.present? && tag_names.present?
526-
allowed_tags = SiteSetting.enable_solved_tags.split("|")
527-
is_allowed = (tag_names & allowed_tags).present?
528-
529-
return true if is_allowed
530-
end
531-
532-
return false if category_id.blank?
533-
self.class.reset_accepted_answer_cache unless @@allowed_accepted_cache["allowed"]
534-
@@allowed_accepted_cache["allowed"].include?(category_id)
535-
end
536-
537-
def can_accept_answer?(topic, post)
538-
return false if !authenticated?
539-
return false if !topic || !post || post.whisper?
540-
return false if !allow_accepted_answers?(topic.category_id, topic.tags.map(&:name))
541-
542-
return true if is_staff?
543-
return true if current_user.trust_level >= SiteSetting.accept_all_solutions_trust_level
544-
545-
if respond_to? :can_perform_action_available_to_group_moderators?
546-
return true if can_perform_action_available_to_group_moderators?(topic)
547-
end
548-
549-
topic.user_id == current_user.id && !topic.closed && SiteSetting.accept_solutions_topic_author
508+
::DiscourseSolved::AcceptedAnswerCache.reset_accepted_answer_cache
550509
end
551510
end
552511

spec/lib/guardian_extensions_spec.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
require "rails_helper"
4+
5+
describe DiscourseSolved::GuardianExtensions do
6+
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
7+
fab!(:other_user) { Fabricate(:user, refresh_auto_groups: true) }
8+
fab!(:topic)
9+
fab!(:post) { Fabricate(:post, topic: topic, user: other_user) }
10+
11+
let(:guardian) { user.guardian }
12+
13+
before { SiteSetting.allow_solved_on_all_topics = true }
14+
15+
describe ".can_accept_answer?" do
16+
it "returns false for anon users" do
17+
expect(Guardian.new.can_accept_answer?(topic, post)).to eq(false)
18+
end
19+
20+
it "returns false if the topic is nil, the post is nil, or for whispers" do
21+
expect(guardian.can_accept_answer?(nil, post)).to eq(false)
22+
expect(guardian.can_accept_answer?(topic, nil)).to eq(false)
23+
24+
post.update!(post_type: Post.types[:whisper])
25+
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
26+
end
27+
28+
it "returns false if accepted answers are not allowed" do
29+
SiteSetting.allow_solved_on_all_topics = false
30+
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
31+
end
32+
33+
it "returns true for admins" do
34+
expect(
35+
Guardian.new(Fabricate(:admin, refresh_auto_groups: true)).can_accept_answer?(topic, post),
36+
).to eq(true)
37+
end
38+
39+
it "returns true if the user is in a group allowed to accept solutions" do
40+
SiteSetting.accept_all_solutions_allowed_groups = Group::AUTO_GROUPS[:trust_level_0]
41+
expect(guardian.can_accept_answer?(topic, post)).to eq(true)
42+
SiteSetting.accept_all_solutions_allowed_groups = Group::AUTO_GROUPS[:trust_level_4]
43+
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
44+
end
45+
46+
it "returns true if the user is a category group moderator for the topic" do
47+
group = Fabricate(:group)
48+
group.add(user)
49+
category = Fabricate(:category, reviewable_by_group_id: group.id)
50+
topic.update!(category: category)
51+
SiteSetting.enable_category_group_moderation = true
52+
expect(guardian.can_accept_answer?(topic, post)).to eq(true)
53+
end
54+
55+
it "returns true if the user is the topic author for an open topic" do
56+
SiteSetting.accept_solutions_topic_author = true
57+
topic.update!(user: user)
58+
expect(guardian.can_accept_answer?(topic, post)).to eq(true)
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)