discourse-solved/plugin.rb

603 lines
18 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
# name: discourse-solved
2015-05-19 01:45:19 -04:00
# about: Add a solved button to answers on Discourse
# version: 0.1
# authors: Sam Saffron
2017-04-26 00:40:03 -04:00
# url: https://github.com/discourse/discourse-solved
2015-05-19 01:45:19 -04:00
2017-02-02 12:20:01 -05:00
enabled_site_setting :solved_enabled
2018-11-07 21:08:46 -05:00
if respond_to?(:register_svg_icon)
register_svg_icon "far fa-check-square"
register_svg_icon "check-square"
register_svg_icon "far fa-square"
end
PLUGIN_NAME = "discourse_solved".freeze
2015-05-19 01:45:19 -04:00
register_asset 'stylesheets/solutions.scss'
2019-05-10 07:07:00 -04:00
register_asset 'stylesheets/mobile/solutions.scss', :mobile
2015-05-19 01:45:19 -04:00
after_initialize do
FEATURE: Publish WebHook event when solving/unsolving (#85) * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 * UX: Add "solved" status filter in advanced search page. And rename `in:solved` to `status:solved`. * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
SeedFu.fixture_paths << Rails.root.join("plugins", "discourse-solved", "db", "fixtures").to_s
2015-05-19 01:45:19 -04:00
[
'../app/serializers/concerns/topic_answer_mixin.rb'
].each { |path| load File.expand_path(path, __FILE__) }
skip_db = defined?(GlobalSetting.skip_db?) && GlobalSetting.skip_db?
# we got to do a one time upgrade
if !skip_db && defined?(UserAction::SOLVED)
unless Discourse.redis.get('solved_already_upgraded')
unless UserAction.where(action_type: UserAction::SOLVED).exists?
Rails.logger.info("Upgrading storage for solved")
2017-08-02 02:09:07 -04:00
sql = <<SQL
INSERT INTO user_actions(action_type,
user_id,
target_topic_id,
target_post_id,
acting_user_id,
created_at,
updated_at)
SELECT :solved,
p.user_id,
p.topic_id,
p.id,
t.user_id,
pc.created_at,
pc.updated_at
FROM
post_custom_fields pc
JOIN
posts p ON p.id = pc.post_id
JOIN
topics t ON t.id = p.topic_id
WHERE
pc.name = 'is_accepted_answer' AND
pc.value = 'true' AND
p.user_id IS NOT NULL
SQL
DB.exec(sql, solved: UserAction::SOLVED)
end
Discourse.redis.set("solved_already_upgraded", "true")
end
end
module ::DiscourseSolved
2015-05-19 01:45:19 -04:00
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace DiscourseSolved
2015-05-19 01:45:19 -04:00
end
AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD = "solved_auto_close_topic_timer_id".freeze
def self.accept_answer!(post, acting_user, topic: nil)
topic ||= post.topic
accepted_id = topic.custom_fields["accepted_answer_post_id"].to_i
if accepted_id > 0
2015-05-19 01:45:19 -04:00
if p2 = Post.find_by(id: accepted_id)
p2.custom_fields["is_accepted_answer"] = nil
p2.save!
if defined?(UserAction::SOLVED)
UserAction.where(
action_type: UserAction::SOLVED,
target_post_id: p2.id
).destroy_all
end
2015-05-19 01:45:19 -04:00
end
end
post.custom_fields["is_accepted_answer"] = "true"
topic.custom_fields["accepted_answer_post_id"] = post.id
2015-05-19 01:45:19 -04:00
if defined?(UserAction::SOLVED)
2017-08-02 02:09:07 -04:00
UserAction.log_action!(
action_type: UserAction::SOLVED,
user_id: post.user_id,
acting_user_id: acting_user.id,
2017-08-02 02:09:07 -04:00
target_post_id: post.id,
target_topic_id: post.topic_id
)
end
unless acting_user.id == post.user_id
2017-08-02 02:09:07 -04:00
Notification.create!(
notification_type: Notification.types[:custom],
user_id: post.user_id,
topic_id: post.topic_id,
post_number: post.post_number,
data: {
message: 'solved.accepted_notification',
display_username: acting_user.username,
2017-08-02 02:09:07 -04:00
topic_title: topic.title
}.to_json
)
end
auto_close_hours = SiteSetting.solved_topics_auto_close_hours
if (auto_close_hours > 0) && !topic.closed
begin
topic_timer = topic.set_or_create_timer(
TopicTimer.types[:close],
nil,
based_on_last_post: true,
duration: auto_close_hours
)
rescue ArgumentError
# https://github.com/discourse/discourse/commit/aad12822b7d7c9c6ecd976e23d3a83626c052dce#diff-4d0afa19fa7752955f36089bca420ab4L1135
# this rescue block can be deleted after discourse stable version > 2.4
topic_timer = topic.set_or_create_timer(
TopicTimer.types[:close],
auto_close_hours,
based_on_last_post: true
)
end
topic.custom_fields[
AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD
] = topic_timer.id
MessageBus.publish("/topic/#{topic.id}", reload_topic: true)
end
topic.save!
post.save!
FEATURE: Publish WebHook event when solving/unsolving (#85) * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 * UX: Add "solved" status filter in advanced search page. And rename `in:solved` to `status:solved`. * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
if WebHook.active_web_hooks(:solved).exists?
payload = WebHook.generate_payload(:post, post)
WebHook.enqueue_solved_hooks(:accepted_solution, post, payload)
end
DiscourseEvent.trigger(:accepted_solution, post)
2015-05-19 01:45:19 -04:00
end
def self.unaccept_answer!(post, topic: nil)
topic ||= post.topic
2015-05-19 01:45:19 -04:00
post.custom_fields["is_accepted_answer"] = nil
topic.custom_fields["accepted_answer_post_id"] = nil
if timer_id = topic.custom_fields[AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD]
topic_timer = TopicTimer.find_by(id: timer_id)
topic_timer.destroy! if topic_timer
topic.custom_fields[AUTO_CLOSE_TOPIC_TIMER_CUSTOM_FIELD] = nil
end
topic.save!
2015-05-19 01:45:19 -04:00
post.save!
# TODO remove_action! does not allow for this type of interface
if defined? UserAction::SOLVED
UserAction.where(
action_type: UserAction::SOLVED,
target_post_id: post.id
).destroy_all
end
# yank notification
notification = Notification.find_by(
notification_type: Notification.types[:custom],
user_id: post.user_id,
topic_id: post.topic_id,
post_number: post.post_number
)
notification.destroy! if notification
FEATURE: Publish WebHook event when solving/unsolving (#85) * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 * UX: Add "solved" status filter in advanced search page. And rename `in:solved` to `status:solved`. * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
if WebHook.active_web_hooks(:solved).exists?
payload = WebHook.generate_payload(:post, post)
WebHook.enqueue_solved_hooks(:unaccepted_solution, post, payload)
end
DiscourseEvent.trigger(:unaccepted_solution, post)
end
end
require_dependency "application_controller"
class DiscourseSolved::AnswerController < ::ApplicationController
def accept
limit_accepts
post = Post.find(params[:id].to_i)
topic = post.topic
topic ||= Topic.with_deleted.find(post.topic_id) if guardian.is_staff?
guardian.ensure_can_accept_answer!(topic, post)
DiscourseSolved.accept_answer!(post, current_user, topic: topic)
render json: success_json
end
def unaccept
limit_accepts
post = Post.find(params[:id].to_i)
topic = post.topic
topic ||= Topic.with_deleted.find(post.topic_id) if guardian.is_staff?
guardian.ensure_can_accept_answer!(topic, post)
DiscourseSolved.unaccept_answer!(post, topic: topic)
2015-05-19 01:45:19 -04:00
render json: success_json
end
def limit_accepts
unless current_user.staff?
RateLimiter.new(nil, "accept-hr-#{current_user.id}", 20, 1.hour).performed!
RateLimiter.new(nil, "accept-min-#{current_user.id}", 4, 30.seconds).performed!
end
end
2015-05-19 01:45:19 -04:00
end
DiscourseSolved::Engine.routes.draw do
2015-05-19 01:45:19 -04:00
post "/accept" => "answer#accept"
post "/unaccept" => "answer#unaccept"
end
Discourse::Application.routes.append do
mount ::DiscourseSolved::Engine, at: "solution"
2015-05-19 01:45:19 -04:00
end
# TODO Drop after Discourse 2.6.0 release
if TopicView.respond_to?(:add_post_custom_fields_allowlister)
TopicView.add_post_custom_fields_allowlister do |user|
["is_accepted_answer"]
end
else
TopicView.add_post_custom_fields_whitelister do |user|
["is_accepted_answer"]
end
2015-05-19 01:45:19 -04:00
end
def get_schema_text(post)
post.excerpt(nil, keep_onebox_body: true).presence || post.excerpt(nil, keep_onebox_body: true, keep_quotes: true)
end
def before_head_close_meta(controller)
return "" if !controller.instance_of? TopicsController
topic_view = controller.instance_variable_get(:@topic_view)
topic = topic_view&.topic
return "" if !topic
# note, we have canonicals so we only do this for page 1 at the moment
# it can get confusing to have this on every page and it should make page 1
# a bit more prominent + cut down on pointless work
return "" if !controller.guardian.allow_accepted_answers_on_category?(topic.category_id)
first_post = topic_view.posts&.first
return "" if first_post&.post_number != 1
question_json = {
'@type' => 'Question',
'name' => topic.title,
'text' => get_schema_text(first_post),
'upvoteCount' => first_post.like_count,
'answerCount' => 0,
'dateCreated' => topic.created_at,
'author' => {
'@type' => 'Person',
'name' => topic.user&.name
}
}
if accepted_answer = Post.find_by(id: topic.custom_fields["accepted_answer_post_id"])
question_json['answerCount'] = 1
question_json[:acceptedAnswer] = {
'@type' => 'Answer',
'text' => get_schema_text(accepted_answer),
'upvoteCount' => accepted_answer.like_count,
'dateCreated' => accepted_answer.created_at,
'url' => accepted_answer.full_url,
'author' => {
'@type' => 'Person',
'name' => accepted_answer.user&.username
}
}
end
['<script type="application/ld+json">', MultiJson.dump(
'@context' => 'http://schema.org',
'@type' => 'QAPage',
'name' => topic&.title,
'mainEntity' => question_json
).gsub("</", "<\\/").html_safe, '</script>'].join("")
end
register_html_builder('server:before-head-close-crawler') do |controller|
before_head_close_meta(controller)
end
register_html_builder('server:before-head-close') do |controller|
before_head_close_meta(controller)
end
if Report.respond_to?(:add_report)
Report.add_report("accepted_solutions") do |report|
report.data = []
accepted_solutions = TopicCustomField.where(name: "accepted_answer_post_id")
category_id, include_subcategories = report.add_category_filter
if category_id
if include_subcategories
accepted_solutions = accepted_solutions.joins(:topic).where('topics.category_id IN (?)', Category.subcategory_ids(category_id))
else
accepted_solutions = accepted_solutions.joins(:topic).where('topics.category_id = ?', category_id)
end
end
accepted_solutions.where("topic_custom_fields.created_at >= ?", report.start_date)
2017-08-02 02:09:07 -04:00
.where("topic_custom_fields.created_at <= ?", report.end_date)
.group("DATE(topic_custom_fields.created_at)")
.order("DATE(topic_custom_fields.created_at)")
.count
.each do |date, count|
report.data << { x: date, y: count }
end
report.total = accepted_solutions.count
report.prev30Days = accepted_solutions.where("topic_custom_fields.created_at >= ?", report.start_date - 30.days)
2017-08-02 02:09:07 -04:00
.where("topic_custom_fields.created_at <= ?", report.start_date)
.count
end
end
if defined?(UserAction::SOLVED)
require_dependency 'user_summary'
class ::UserSummary
def solved_count
UserAction
.where(user: @user)
.where(action_type: UserAction::SOLVED)
.count
end
end
require_dependency 'user_summary_serializer'
class ::UserSummarySerializer
attributes :solved_count
def solved_count
object.solved_count
end
end
end
FEATURE: Publish WebHook event when solving/unsolving (#85) * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 * UX: Add "solved" status filter in advanced search page. And rename `in:solved` to `status:solved`. * FEATURE: Publish WebHook event when solving/unsolving This feature will publish a post edit webhook event whenever a solution is accepted or unaccepted. I went ahead and used the existing post-edit webhook because all the post custom fields for the solved plugin are already included in the post-edit serializer. * Create Solved Event Webhook This commit adds a solved event webhook that will only trigger when an answer has been marked as accepted or unaccepted. It uses 100 as the webhook ID. This way any new webhooks in core can keep using lower numbers like 11, 12, 13, but plugins can use 101, 102, etc. * Removed functionality that was added to core This [PR][1] to discourse core adds what what removed in this commit. It is better to have this logic in core so that it is discoverable and future webhooks won't end up accidentally using the same ID. [1]: https://github.com/discourse/discourse/pull/9110 Co-authored-by: Vinoth Kannan <vinothkannan@vinkas.com>
2020-03-06 13:28:29 -05:00
class ::WebHook
def self.enqueue_solved_hooks(event, post, payload = nil)
if active_web_hooks('solved').exists? && post.present?
payload ||= WebHook.generate_payload(:post, post)
WebHook.enqueue_hooks(:solved, event,
id: post.id,
category_id: post.topic&.category_id,
tag_ids: post.topic&.tags&.pluck(:id),
payload: payload
)
end
end
end
2015-05-19 01:45:19 -04:00
require_dependency 'topic_view_serializer'
class ::TopicViewSerializer
attributes :accepted_answer
def include_accepted_answer?
accepted_answer_post_id
end
def accepted_answer
if info = accepted_answer_post_info
{
post_number: info[0],
username: info[1],
excerpt: info[2]
2015-05-19 01:45:19 -04:00
}
end
end
def accepted_answer_post_info
# TODO: we may already have it in the stream ... so bypass query here
postInfo = Post.where(id: accepted_answer_post_id, topic_id: object.topic.id)
2017-08-02 02:09:07 -04:00
.joins(:user)
.pluck('post_number', 'username', 'cooked')
.first
2017-03-08 10:47:18 -05:00
if postInfo
postInfo[2] = if SiteSetting.solved_quote_length > 0
PrettyText.excerpt(postInfo[2], SiteSetting.solved_quote_length, keep_emoji_images: true)
2017-08-11 15:36:38 -04:00
else
nil
end
postInfo
2017-03-08 10:47:18 -05:00
end
2015-05-19 01:45:19 -04:00
end
def accepted_answer_post_id
id = object.topic.custom_fields["accepted_answer_post_id"]
# a bit messy but race conditions can give us an array here, avoid
id && id.to_i rescue nil
2015-05-19 01:45:19 -04:00
end
end
class ::Category
after_save :reset_accepted_cache
protected
def reset_accepted_cache
::Guardian.reset_accepted_answer_cache
end
end
class ::Guardian
@@allowed_accepted_cache = DistributedCache.new("allowed_accepted")
def self.reset_accepted_answer_cache
@@allowed_accepted_cache["allowed"] =
begin
Set.new(
CategoryCustomField
.where(name: "enable_accepted_answers", value: "true")
.pluck(:category_id)
)
end
end
def allow_accepted_answers_on_category?(category_id)
return true if SiteSetting.allow_solved_on_all_topics
self.class.reset_accepted_answer_cache unless @@allowed_accepted_cache["allowed"]
@@allowed_accepted_cache["allowed"].include?(category_id)
end
def can_accept_answer?(topic, post)
return false if !authenticated?
return false if !topic || !post || post.whisper?
return false if !allow_accepted_answers_on_category?(topic.category_id)
return true if is_staff?
return true if current_user.trust_level >= SiteSetting.accept_all_solutions_trust_level
if respond_to? :can_perform_action_available_to_group_moderators?
return true if can_perform_action_available_to_group_moderators?(topic)
end
topic.user_id == current_user.id && !topic.closed
end
end
2015-05-19 01:45:19 -04:00
require_dependency 'post_serializer'
class ::PostSerializer
attributes :can_accept_answer, :can_unaccept_answer, :accepted_answer
def can_accept_answer
topic = (topic_view && topic_view.topic) || object.topic
2015-05-19 01:45:19 -04:00
if topic
return scope.can_accept_answer?(topic, object) && object.post_number > 1 && !accepted_answer
2015-05-19 01:45:19 -04:00
end
false
2015-05-19 01:45:19 -04:00
end
def can_unaccept_answer
topic = (topic_view && topic_view.topic) || object.topic
if topic
scope.can_accept_answer?(topic, object) && (post_custom_fields["is_accepted_answer"] == 'true')
end
2015-05-19 01:45:19 -04:00
end
def accepted_answer
post_custom_fields["is_accepted_answer"] == 'true'
2015-05-19 01:45:19 -04:00
end
2015-06-15 02:26:40 -04:00
end
require_dependency 'search'
#TODO Remove when plugin is 1.0
if Search.respond_to? :advanced_filter
Search.advanced_filter(/status:solved/) do |posts|
posts.where("topics.id IN (
SELECT tc.topic_id
FROM topic_custom_fields tc
WHERE tc.name = 'accepted_answer_post_id' AND
tc.value IS NOT NULL
)")
end
2015-06-22 22:56:22 -04:00
Search.advanced_filter(/status:unsolved/) do |posts|
2015-06-22 22:56:22 -04:00
posts.where("topics.id NOT IN (
SELECT tc.topic_id
FROM topic_custom_fields tc
WHERE tc.name = 'accepted_answer_post_id' AND
tc.value IS NOT NULL
)")
end
end
2015-06-15 02:26:40 -04:00
if Discourse.has_needed_version?(Discourse::VERSION::STRING, '1.8.0.beta6')
require_dependency 'topic_query'
TopicQuery.add_custom_filter(:solved) do |results, topic_query|
2017-02-28 10:22:07 -05:00
if topic_query.options[:solved] == 'yes'
results = results.where("topics.id IN (
SELECT tc.topic_id
FROM topic_custom_fields tc
WHERE tc.name = 'accepted_answer_post_id' AND
tc.value IS NOT NULL
)")
2017-02-28 10:22:07 -05:00
elsif topic_query.options[:solved] == 'no'
results = results.where("topics.id NOT IN (
SELECT tc.topic_id
FROM topic_custom_fields tc
WHERE tc.name = 'accepted_answer_post_id' AND
tc.value IS NOT NULL
)")
end
results
2017-02-27 13:19:21 -05:00
end
end
2015-06-15 02:26:40 -04:00
require_dependency 'topic_list_item_serializer'
require_dependency 'search_topic_list_item_serializer'
require_dependency 'suggested_topic_serializer'
require_dependency 'user_summary_serializer'
2015-06-15 02:26:40 -04:00
class ::TopicListItemSerializer
include TopicAnswerMixin
end
class ::SearchTopicListItemSerializer
include TopicAnswerMixin
end
class ::SuggestedTopicSerializer
include TopicAnswerMixin
end
class ::UserSummarySerializer::TopicSerializer
include TopicAnswerMixin
end
class ::ListableTopicSerializer
include TopicAnswerMixin
end
TopicList.preloaded_custom_fields << "accepted_answer_post_id" if TopicList.respond_to? :preloaded_custom_fields
Site.preloaded_category_custom_fields << "enable_accepted_answers" if Site.respond_to? :preloaded_category_custom_fields
Search.preloaded_topic_custom_fields << "accepted_answer_post_id" if Search.respond_to? :preloaded_topic_custom_fields
2015-06-15 02:26:40 -04:00
if CategoryList.respond_to?(:preloaded_topic_custom_fields)
CategoryList.preloaded_topic_custom_fields << "accepted_answer_post_id"
end
on(:filter_auto_bump_topics) do |_category, filters|
filters.push(->(r) { r.where(<<~SQL)
NOT EXISTS(
SELECT 1 FROM topic_custom_fields
WHERE topic_id = topics.id
AND name = 'accepted_answer_post_id'
AND value IS NOT NULL
)
SQL
})
end
2015-05-19 01:45:19 -04:00
end