mirror of
https://github.com/discourse/discourse-solved.git
synced 2025-05-07 08:57:52 +00:00
FIX: don't allow or count solutions in PMs
Pretty straightforward, this ensures we don't allow users to mark a post as a solution in a PM. This also ensures we don't count solutions in topics that were converted to a PM. Internal ref t/146766
This commit is contained in:
parent
3efcd3722a
commit
f24ab50fa4
@ -21,7 +21,7 @@ module DiscourseSolved
|
|||||||
|
|
||||||
def can_accept_answer?(topic, post)
|
def can_accept_answer?(topic, post)
|
||||||
return false if !authenticated?
|
return false if !authenticated?
|
||||||
return false if !topic || !post || post.whisper?
|
return false if !topic || topic.private_message? || !post || post.whisper?
|
||||||
return false if !allow_accepted_answers?(topic.category_id, topic.tags.map(&:name))
|
return false if !allow_accepted_answers?(topic.category_id, topic.tags.map(&:name))
|
||||||
|
|
||||||
return true if is_staff?
|
return true if is_staff?
|
||||||
|
69
plugin.rb
69
plugin.rb
@ -272,19 +272,20 @@ after_initialize do
|
|||||||
report.data = []
|
report.data = []
|
||||||
|
|
||||||
accepted_solutions =
|
accepted_solutions =
|
||||||
TopicCustomField.where(name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD)
|
TopicCustomField
|
||||||
|
.joins(:topic)
|
||||||
|
.where("topics.archetype <> ?", Archetype.private_message)
|
||||||
|
.where(name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD)
|
||||||
|
|
||||||
category_id, include_subcategories = report.add_category_filter
|
category_id, include_subcategories = report.add_category_filter
|
||||||
if category_id
|
if category_id
|
||||||
if include_subcategories
|
if include_subcategories
|
||||||
accepted_solutions =
|
accepted_solutions =
|
||||||
accepted_solutions.joins(:topic).where(
|
accepted_solutions
|
||||||
"topics.category_id IN (?)",
|
.where("topics.category_id IN (?)", Category.subcategory_ids(category_id))
|
||||||
Category.subcategory_ids(category_id),
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
accepted_solutions =
|
accepted_solutions =
|
||||||
accepted_solutions.joins(:topic).where("topics.category_id = ?", category_id)
|
accepted_solutions.where("topics.category_id = ?", category_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -345,7 +346,7 @@ after_initialize do
|
|||||||
)
|
)
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
scope.where(sql)
|
scope.where(sql).where("topics.archetype <> ?", Archetype.private_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
unsolved_callback = ->(scope) do
|
unsolved_callback = ->(scope) do
|
||||||
@ -379,7 +380,7 @@ after_initialize do
|
|||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
scope
|
scope.where("topics.archetype <> ?", Archetype.private_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
register_custom_filter_by_status("solved", &solved_callback)
|
register_custom_filter_by_status("solved", &solved_callback)
|
||||||
@ -398,6 +399,12 @@ after_initialize do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
register_modifier(:user_action_stream_builder) do |builder|
|
||||||
|
builder
|
||||||
|
.where("t.deleted_at IS NULL")
|
||||||
|
.where("t.archetype <> ?", Archetype.private_message)
|
||||||
|
end
|
||||||
|
|
||||||
TopicList.preloaded_custom_fields << ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD
|
TopicList.preloaded_custom_fields << ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD
|
||||||
Site.preloaded_category_custom_fields << ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD
|
Site.preloaded_category_custom_fields << ::DiscourseSolved::ENABLE_ACCEPTED_ANSWERS_CUSTOM_FIELD
|
||||||
Search.preloaded_topic_custom_fields << ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD
|
Search.preloaded_topic_custom_fields << ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD
|
||||||
@ -477,26 +484,36 @@ after_initialize do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
query =
|
query = <<~SQL
|
||||||
"
|
WITH x AS (
|
||||||
WITH x AS (SELECT
|
SELECT u.id user_id, COUNT(DISTINCT ua.id) AS solutions
|
||||||
u.id user_id,
|
|
||||||
COUNT(DISTINCT ua.id) AS solutions
|
|
||||||
FROM users AS u
|
FROM users AS u
|
||||||
LEFT OUTER JOIN user_actions AS ua ON ua.user_id = u.id AND ua.action_type = #{UserAction::SOLVED} AND COALESCE(ua.created_at, :since) > :since
|
LEFT JOIN user_actions AS ua
|
||||||
WHERE u.active
|
ON ua.user_id = u.id
|
||||||
|
AND ua.action_type = #{UserAction::SOLVED}
|
||||||
|
AND COALESCE(ua.created_at, :since) > :since
|
||||||
|
JOIN topics AS t
|
||||||
|
ON t.id = ua.target_topic_id
|
||||||
|
AND t.archetype <> 'private_message'
|
||||||
|
AND t.deleted_at IS NULL
|
||||||
|
JOIN posts AS p
|
||||||
|
ON p.id = ua.target_post_id
|
||||||
|
AND p.deleted_at IS NULL
|
||||||
|
WHERE u.id > 0
|
||||||
|
AND u.active
|
||||||
AND u.silenced_till IS NULL
|
AND u.silenced_till IS NULL
|
||||||
AND u.id > 0
|
AND u.suspended_till IS NULL
|
||||||
GROUP BY u.id
|
GROUP BY u.id
|
||||||
)
|
)
|
||||||
UPDATE directory_items di SET
|
UPDATE directory_items di
|
||||||
solutions = x.solutions
|
SET solutions = x.solutions
|
||||||
FROM x
|
FROM x
|
||||||
WHERE x.user_id = di.user_id
|
WHERE x.user_id = di.user_id
|
||||||
AND di.period_type = :period_type
|
AND di.period_type = :period_type
|
||||||
AND di.solutions <> x.solutions
|
AND di.solutions <> x.solutions
|
||||||
"
|
SQL
|
||||||
add_directory_column("solutions", query: query)
|
|
||||||
|
add_directory_column("solutions", query:)
|
||||||
|
|
||||||
add_to_class(:composer_messages_finder, :check_topic_is_solved) do
|
add_to_class(:composer_messages_finder, :check_topic_is_solved) do
|
||||||
return if !SiteSetting.solved_enabled || SiteSetting.disable_solved_education_message
|
return if !SiteSetting.solved_enabled || SiteSetting.disable_solved_education_message
|
||||||
@ -514,7 +531,13 @@ after_initialize do
|
|||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:user_card, :accepted_answers) do
|
add_to_serializer(:user_card, :accepted_answers) do
|
||||||
UserAction.where(user_id: object.id).where(action_type: UserAction::SOLVED).count
|
UserAction
|
||||||
|
.where(user_id: object.id)
|
||||||
|
.where(action_type: UserAction::SOLVED)
|
||||||
|
.joins("JOIN topics ON topics.id = user_actions.target_topic_id")
|
||||||
|
.where("topics.archetype <> ?", Archetype.private_message)
|
||||||
|
.where("topics.deleted_at IS NULL")
|
||||||
|
.count
|
||||||
end
|
end
|
||||||
|
|
||||||
register_topic_list_preload_user_ids do |topics, user_ids, topic_list|
|
register_topic_list_preload_user_ids do |topics, user_ids, topic_list|
|
||||||
|
@ -47,6 +47,16 @@ RSpec.describe "Managing Posts solved status" do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fab!(:solved_pm) do
|
||||||
|
Fabricate(
|
||||||
|
:custom_topic,
|
||||||
|
category_id: nil,
|
||||||
|
archetype: Archetype.private_message,
|
||||||
|
custom_topic_name: ::DiscourseSolved::ACCEPTED_ANSWER_POST_ID_CUSTOM_FIELD,
|
||||||
|
value: "42",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
fab!(:unsolved_in_category) { Fabricate(:topic, category: solvable_category) }
|
fab!(:unsolved_in_category) { Fabricate(:topic, category: solvable_category) }
|
||||||
fab!(:unsolved_in_tag) { Fabricate(:topic, tags: [solvable_tag]) }
|
fab!(:unsolved_in_tag) { Fabricate(:topic, tags: [solvable_tag]) }
|
||||||
|
|
||||||
@ -572,4 +582,33 @@ RSpec.describe "Managing Posts solved status" do
|
|||||||
expect { DiscourseSolved.unaccept_answer!(reply) }.not_to raise_error
|
expect { DiscourseSolved.unaccept_answer!(reply) }.not_to raise_error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "user actions stream modifier" do
|
||||||
|
it "correctly list solutions" do
|
||||||
|
t1 = Fabricate(:topic)
|
||||||
|
t2 = Fabricate(:topic)
|
||||||
|
t3 = Fabricate(:topic)
|
||||||
|
|
||||||
|
p1 = Fabricate(:post, topic: t1, user:)
|
||||||
|
p2 = Fabricate(:post, topic: t2, user:)
|
||||||
|
p3 = Fabricate(:post, topic: t3, user:)
|
||||||
|
|
||||||
|
DiscourseSolved.accept_answer!(p1, Discourse.system_user)
|
||||||
|
DiscourseSolved.accept_answer!(p2, Discourse.system_user)
|
||||||
|
DiscourseSolved.accept_answer!(p3, Discourse.system_user)
|
||||||
|
|
||||||
|
t1.trash!(Discourse.system_user)
|
||||||
|
t2.convert_to_private_message(Discourse.system_user)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
UserAction
|
||||||
|
.stream(
|
||||||
|
user_id: user.id,
|
||||||
|
action_types: [::UserAction::SOLVED],
|
||||||
|
guardian: user.guardian
|
||||||
|
)
|
||||||
|
.map(&:post_id)
|
||||||
|
).to contain_exactly p3.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -25,6 +25,11 @@ describe DiscourseSolved::GuardianExtensions do
|
|||||||
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
|
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns false for private messages" do
|
||||||
|
topic.update!(user:, category_id: nil, archetype: Archetype.private_message)
|
||||||
|
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
it "returns false if accepted answers are not allowed" do
|
it "returns false if accepted answers are not allowed" do
|
||||||
SiteSetting.allow_solved_on_all_topics = false
|
SiteSetting.allow_solved_on_all_topics = false
|
||||||
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
|
expect(guardian.can_accept_answer?(topic, post)).to eq(false)
|
||||||
|
@ -8,6 +8,8 @@ describe UserCardSerializer do
|
|||||||
let(:json) { serializer.as_json }
|
let(:json) { serializer.as_json }
|
||||||
|
|
||||||
it "accepted_answers serializes number of accepted answers" do
|
it "accepted_answers serializes number of accepted answers" do
|
||||||
|
expect(serializer.as_json[:accepted_answers]).to eq(0)
|
||||||
|
|
||||||
post1 = Fabricate(:post, user: user)
|
post1 = Fabricate(:post, user: user)
|
||||||
DiscourseSolved.accept_answer!(post1, Discourse.system_user)
|
DiscourseSolved.accept_answer!(post1, Discourse.system_user)
|
||||||
expect(serializer.as_json[:accepted_answers]).to eq(1)
|
expect(serializer.as_json[:accepted_answers]).to eq(1)
|
||||||
@ -16,7 +18,17 @@ describe UserCardSerializer do
|
|||||||
DiscourseSolved.accept_answer!(post2, Discourse.system_user)
|
DiscourseSolved.accept_answer!(post2, Discourse.system_user)
|
||||||
expect(serializer.as_json[:accepted_answers]).to eq(2)
|
expect(serializer.as_json[:accepted_answers]).to eq(2)
|
||||||
|
|
||||||
|
post3 = Fabricate(:post, user: user)
|
||||||
|
DiscourseSolved.accept_answer!(post3, Discourse.system_user)
|
||||||
|
expect(serializer.as_json[:accepted_answers]).to eq(3)
|
||||||
|
|
||||||
DiscourseSolved.unaccept_answer!(post1)
|
DiscourseSolved.unaccept_answer!(post1)
|
||||||
|
expect(serializer.as_json[:accepted_answers]).to eq(2)
|
||||||
|
|
||||||
|
post2.topic.trash!(Discourse.system_user)
|
||||||
expect(serializer.as_json[:accepted_answers]).to eq(1)
|
expect(serializer.as_json[:accepted_answers]).to eq(1)
|
||||||
|
|
||||||
|
post3.topic.convert_to_private_message(Discourse.system_user)
|
||||||
|
expect(serializer.as_json[:accepted_answers]).to eq(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user