Files
danbooru/app/models/forum_post.rb
evazion 47d95e8d80 forum: fix last page links leading to empty page.
Bug: on the forum index, clicking on 'page 290' of the Danbooru 2 Issues
thread leads to an empty page. The last page is actually page 288.

Cause: 89df0a6ac changed it so that deleted posts are filtered out of
threads, but this made the calculation of the last page incorrect. The
last page is calculated from the topic's response_count, but the
response count includes deleted posts.
2020-01-07 00:18:16 -06:00

222 lines
6.5 KiB
Ruby

class ForumPost < ApplicationRecord
include Mentionable
attr_readonly :topic_id
belongs_to_creator
belongs_to_updater
belongs_to :topic, :class_name => "ForumTopic"
has_many :dtext_links, as: :model, dependent: :destroy
has_many :votes, class_name: "ForumPostVote"
has_one :tag_alias
has_one :tag_implication
has_one :bulk_update_request
before_validation :initialize_is_deleted, :on => :create
before_save :update_dtext_links, if: :dtext_links_changed?
after_create :update_topic_updated_at_on_create
after_update :update_topic_updated_at_on_update_for_original_posts
after_destroy :update_topic_updated_at_on_destroy
validates_presence_of :body
validate :validate_topic_is_unlocked
validate :validate_post_is_not_spam, on: :create
validate :topic_is_not_restricted, :on => :create
before_destroy :validate_topic_is_unlocked
after_save :delete_topic_if_original_post
after_update(:if => ->(rec) {rec.updater_id != rec.creator_id}) do |rec|
ModAction.log("#{CurrentUser.name} updated forum ##{rec.id}", :forum_post_update)
end
after_destroy(:if => ->(rec) {rec.updater_id != rec.creator_id}) do |rec|
ModAction.log("#{CurrentUser.name} deleted forum ##{rec.id}", :forum_post_delete)
end
mentionable(
:message_field => :body,
:title => ->(user_name) {%{#{creator.name} mentioned you in topic ##{topic_id} (#{topic.title})}},
:body => ->(user_name) {%{@#{creator.name} mentioned you in topic ##{topic_id} ("#{topic.title}":[/forum_topics/#{topic_id}?page=#{forum_topic_page}]):\n\n[quote]\n#{DText.excerpt(body, "@" + user_name)}\n[/quote]\n}}
)
module SearchMethods
def topic_title_matches(title)
joins(:topic).merge(ForumTopic.search(title_matches: title))
end
def active
where("forum_posts.is_deleted = false")
end
def permitted
joins(:topic).where("forum_topics.min_level <= ?", CurrentUser.level)
end
def search(params)
q = super
q = q.permitted
q = q.search_attributes(params, :creator, :updater, :topic_id, :is_deleted, :body)
q = q.text_attribute_matches(:body, params[:body_matches], index_column: :text_index)
if params[:linked_to].present?
q = q.where(id: DtextLink.forum_post.wiki_link.where(link_target: params[:linked_to]).select(:model_id))
end
if params[:topic_title_matches].present?
q = q.topic_title_matches(params[:topic_title_matches])
end
if params[:topic_category_id].present?
q = q.joins(:topic).where("forum_topics.category_id = ?", params[:topic_category_id].to_i)
end
q.apply_default_order(params)
end
end
module ApiMethods
def html_data_attributes
super + [[:topic, :is_deleted?]]
end
end
extend SearchMethods
include ApiMethods
def self.new_reply(params)
if params[:topic_id]
new(:topic_id => params[:topic_id])
elsif params[:post_id]
forum_post = ForumPost.find(params[:post_id])
forum_post.build_response
else
new
end
end
def tag_change_request
bulk_update_request || tag_alias || tag_implication
end
def votable?
TagAlias.where(forum_post_id: id).exists? ||
TagImplication.where(forum_post_id: id).exists? ||
BulkUpdateRequest.where(forum_post_id: id).exists?
end
def voted?(user, score)
votes.where(creator_id: user.id, score: score).exists?
end
def validate_post_is_not_spam
errors[:base] << "Failed to create forum post" if SpamDetector.new(self, user_ip: CurrentUser.ip_addr).spam?
end
def validate_topic_is_unlocked
return if CurrentUser.is_moderator?
return if topic.nil?
if topic.is_locked?
errors[:topic] << "is locked"
throw :abort
end
end
def topic_is_not_restricted
if topic && !topic.visible?(creator)
errors[:topic] << "is restricted"
end
end
def editable_by?(user)
(creator_id == user.id || user.is_moderator?) && visible?(user)
end
def visible?(user, show_deleted_posts = false)
user.is_moderator? || (topic.visible?(user) && (show_deleted_posts || !is_deleted?))
end
def update_topic_updated_at_on_create
if topic
# need to do this to bypass the topic's original post from getting touched
ForumTopic.where(:id => topic.id).update_all(["updater_id = ?, response_count = response_count + 1, updated_at = ?", CurrentUser.id, Time.now])
topic.response_count += 1
end
end
def update_topic_updated_at_on_update_for_original_posts
if is_original_post?
topic.touch
end
end
def delete!
update(is_deleted: true)
update_topic_updated_at_on_delete
end
def undelete!
update(is_deleted: false)
update_topic_updated_at_on_undelete
end
def dtext_links_changed?
body_changed? && DText.dtext_links_differ?(body, body_was)
end
def update_dtext_links
self.dtext_links = DtextLink.new_from_dtext(body)
end
def update_topic_updated_at_on_delete
max = ForumPost.where(:topic_id => topic.id, :is_deleted => false).order("updated_at desc").first
if max
ForumTopic.where(:id => topic.id).update_all(["updated_at = ?, updater_id = ?", max.updated_at, max.updater_id])
end
end
def update_topic_updated_at_on_undelete
if topic
ForumTopic.where(:id => topic.id).update_all(["updater_id = ?, updated_at = ?", CurrentUser.id, Time.now])
end
end
def update_topic_updated_at_on_destroy
max = ForumPost.where(:topic_id => topic.id, :is_deleted => false).order("updated_at desc").first
if max
ForumTopic.where(:id => topic.id).update_all(["response_count = response_count - 1, updated_at = ?, updater_id = ?", max.updated_at, max.updater_id])
else
ForumTopic.where(:id => topic.id).update_all("response_count = response_count - 1")
end
topic.response_count -= 1
end
def initialize_is_deleted
self.is_deleted = false if is_deleted.nil?
end
def quoted_response
DText.quote(body, creator.name)
end
def forum_topic_page
(ForumPost.where("topic_id = ? and created_at <= ?", topic_id, created_at).count / Danbooru.config.posts_per_page.to_f).ceil
end
def is_original_post?(original_post_id = nil)
if original_post_id
return id == original_post_id
else
ForumPost.exists?(["id = ? and id = (select _.id from forum_posts _ where _.topic_id = ? order by _.id asc limit 1)", id, topic_id])
end
end
def delete_topic_if_original_post
if is_deleted? && is_original_post?
topic.update_attribute(:is_deleted, true)
end
end
def build_response
dup.tap do |x|
x.body = x.quoted_response
end
end
end