eBooru/app/models/wiki_page.rb

367 lines
9.7 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
class WikiPage < ApplicationRecord
class RevertError < Exception; end
before_validation :normalize_title
before_validation :normalize_other_names
before_validation :normalize_parent
before_save :log_changes
before_save :update_tag, if: :tag_changed?
before_destroy :validate_not_used_as_help_page
before_destroy :log_destroy
2010-02-15 17:32:32 -05:00
after_save :create_version
after_save :update_help_page, if: :saved_change_to_title?
normalizes :body, with: ->(body) { body.gsub("\r\n", "\n") }
validates :title, uniqueness: { case_sensitive: false }
validates :title, presence: true
validates :title, tag_name: true, if: :title_changed?
validates :body, presence: { unless: -> { is_deleted? || other_names.present? || parent.present? } }
2019-09-05 08:59:51 -04:00
validates :title, length: { minimum: 1, maximum: 100 }
validates :body, length: { maximum: Danbooru.config.wiki_page_max_size }
validate :user_not_limited
validate :validate_rename
validate :validate_redirect
2013-09-12 18:20:20 -04:00
validate :validate_not_locked
2019-11-15 23:00:54 -05:00
attr_accessor :skip_secondary_validations, :edit_reason
array_attribute :other_names
belongs_to_creator
belongs_to_updater
has_one :tag, foreign_key: "name", primary_key: "title"
has_one :artist, foreign_key: "name", primary_key: "title"
has_many :versions, -> { order("wiki_page_versions.id ASC") }, class_name: "WikiPageVersion", dependent: :destroy
has_one :help_page, foreign_key: "wiki_page", primary_key: "title"
def log_changes
if title_changed? && !new_record?
ModAction.log(:wiki_page_rename, { new_title: title, old_title: title_was })
end
if is_locked_changed?
ModAction.log(is_locked ? :wiki_page_lock : :wiki_page_unlock, { wiki_page: title })
end
end
def log_destroy
ModAction.log(:wiki_page_delete, { wiki_page: title, wiki_page_id: id })
end
2013-01-10 17:45:52 -05:00
module SearchMethods
def titled(title)
find_by(title: WikiPage.normalize_name(title))
2010-02-15 17:32:32 -05:00
end
2013-03-19 08:10:10 -04:00
def active
where("is_deleted = false")
end
2013-01-10 17:45:52 -05:00
def recent
order("updated_at DESC").limit(25)
end
2013-03-19 08:10:10 -04:00
def other_names_include(name)
name = normalize_other_name(name).downcase
subquery = WikiPage.from("unnest(other_names) AS other_name").where("lower(other_name) = ?", name)
where(id: subquery)
2014-05-22 20:07:15 -04:00
end
def other_names_match(name)
if name =~ /\*/
subquery = WikiPage.from("unnest(other_names) AS other_name").where("other_name ILIKE ?", name.to_escaped_for_sql_like)
where(id: subquery)
else
other_names_include(name)
end
end
def default_order
order(updated_at: :desc)
end
def search(params)
q = super
2013-01-10 17:45:52 -05:00
2013-02-19 12:27:17 -05:00
if params[:title].present?
q = q.where("title LIKE ? ESCAPE E'\\\\'", params[:title].downcase.strip.tr(" ", "_").to_escaped_for_sql_like)
2013-01-10 17:45:52 -05:00
end
q = q.attribute_matches(:body, params[:body_matches])
2013-03-19 08:10:10 -04:00
2014-05-22 20:07:15 -04:00
if params[:other_names_match].present?
q = q.other_names_match(params[:other_names_match])
2014-05-22 20:07:15 -04:00
end
q = q.where_user(:creator_id, :creator, params)
2013-03-19 08:10:10 -04:00
if params[:hide_deleted].to_s.truthy?
q = q.where("is_deleted = false")
end
q = q.attribute_matches(:parent, params[:parent].try(:tr, " ", "_"))
if params[:other_names_present].to_s.truthy?
q = q.where("other_names is not null and other_names != '{}'")
elsif params[:other_names_present].to_s.falsy?
q = q.where("other_names is null or other_names = '{}'")
end
q = q.attribute_matches(:is_locked, params[:is_locked])
q = q.attribute_matches(:is_deleted, params[:is_deleted])
case params[:order]
when "title"
2013-03-21 00:30:24 -04:00
q = q.order("title")
when "post_count"
q = q.includes(:tag).order("tags.post_count desc nulls last").references(:tags)
else
2023-07-07 08:32:57 -04:00
q = q.apply_basic_order(params)
2013-02-04 21:34:25 -05:00
end
2013-01-10 17:45:52 -05:00
q
2010-02-15 17:32:32 -05:00
end
end
2013-03-19 08:10:10 -04:00
2013-03-11 13:14:25 -04:00
module ApiMethods
def method_attributes
super + %i[creator_name category_id]
2013-03-11 13:14:25 -04:00
end
end
2013-03-19 08:10:10 -04:00
module HelpPageMethods
def validate_not_used_as_help_page
if help_page.present?
errors.add(:wiki_page, "is used by a help page")
throw :abort
end
end
def update_help_page
HelpPage.find_by(wiki_page: title_before_last_save)&.update(wiki_page: title)
end
end
module TagMethods
def tag
@tag ||= super
end
def category_id
return @category_id if instance_variable_defined?(:@category_id)
@category_id = tag&.category
end
def category_id=(value)
return if value.blank? || value.to_i == category_id
category_id_will_change!
@category_id = value.to_i
end
def category_is_locked
return @category_is_locked if instance_variable_defined?(:@category_is_locked)
@category_is_locked = tag&.is_locked || false
end
def category_is_locked=(value)
return if value == category_is_locked
category_is_locked_will_change!
@category_is_locked = value
end
def category_id_changed?
attribute_changed?("category_id")
end
def category_id_will_change!
attribute_will_change!("category_id")
end
def category_is_locked_changed?
attribute_changed?("category_is_locked")
end
def category_is_locked_will_change!
attribute_will_change!("category_is_locked")
end
def tag_update_map
{}.tap do |updates|
updates[:category] = @category_id if category_id_changed?
updates[:is_locked] = @category_is_locked if category_is_locked_changed?
end
end
def tag_changed?
tag_update_map.present?
end
def update_tag
updates = tag_update_map
@tag = Tag.find_or_create_by_name(title)
return if updates.empty?
unless @tag.category_editable_by?(CurrentUser.user)
reload_tag_attributes
errors.add(:category_id, "Cannot be changed")
throw(:abort)
end
@tag.update(updates)
@tag.save
if @tag.invalid?
errors.add(:category_id, @tag.errors.full_messages.join(", "))
throw(:abort)
end
reload_tag_attributes
end
def reload_tag_attributes
remove_instance_variable(:@category_id) if instance_variable_defined?(:@category_id)
remove_instance_variable(:@category_is_locked) if instance_variable_defined?(:@category_is_locked)
end
end
2013-01-10 17:45:52 -05:00
extend SearchMethods
2013-03-11 13:14:25 -04:00
include ApiMethods
include HelpPageMethods
include TagMethods
2013-03-19 08:10:10 -04:00
2019-06-27 11:33:02 -04:00
def user_not_limited
allowed = CurrentUser.can_wiki_edit_with_reason
if allowed != true
errors.add(:base, "User #{User.throttle_reason(allowed)}.")
false
end
true
end
2013-09-12 18:20:20 -04:00
def validate_not_locked
if is_locked? && !CurrentUser.is_janitor?
2013-09-12 18:20:20 -04:00
errors.add(:is_locked, "and cannot be updated")
false
2013-09-12 18:20:20 -04:00
end
end
def validate_rename
return unless will_save_change_to_title?
if !CurrentUser.user.is_admin? && HelpPage.find_by(wiki_page: title_was).present?
errors.add(:title, "is used as a help page and cannot be changed")
return
end
return if skip_secondary_validations
tag_was = Tag.find_by_name(Tag.normalize_name(title_was))
if tag_was.present? && tag_was.post_count > 0
errors.add(:title, "cannot be changed: '#{tag_was.name}' still has #{tag_was.post_count} posts. Move the posts and update any wikis linking to this page first.")
end
end
def validate_redirect
return unless will_save_change_to_parent? && parent.present?
if WikiPage.find_by(title: parent).blank?
errors.add(:parent, "does not exist")
return
end
if HelpPage.find_by(wiki_page: title).present?
errors.add(:title, "is used as a help page and cannot be redirected")
end
end
def revert_to(version)
if id != version.wiki_page_id
raise RevertError.new("You cannot revert to a previous version of another wiki page.")
end
2010-02-15 17:32:32 -05:00
self.title = version.title
self.body = version.body
self.parent = version.parent
2014-05-22 20:07:15 -04:00
self.other_names = version.other_names
2010-02-15 17:32:32 -05:00
end
2013-03-19 08:10:10 -04:00
def revert_to!(version)
revert_to(version)
2010-02-15 17:32:32 -05:00
save!
end
def normalize_title
title = self.title.downcase.tr(" ", "_")
if title =~ /\A(#{Tag.categories.regexp}):(.+)\Z/
self.category_id = Tag.categories.value_for($1)
title = $2
end
self.title = title
2010-02-15 17:32:32 -05:00
end
2014-05-24 12:58:14 -04:00
def normalize_other_names
self.other_names = other_names.map { |name| WikiPage.normalize_other_name(name) }.uniq
end
def normalize_parent
self.parent = nil if parent == ""
end
def self.normalize_other_name(name)
name.unicode_normalize(:nfkc).gsub(/[[:space:]]+/, " ").strip.tr(" ", "_")
2014-05-24 12:58:14 -04:00
end
def self.normalize_name(name)
name&.downcase&.tr(" ", "_")
end
def skip_secondary_validations=(value)
@skip_secondary_validations = value.to_s.truthy?
end
2010-02-15 17:32:32 -05:00
def pretty_title
title&.tr("_", " ") || ""
2010-02-15 17:32:32 -05:00
end
2013-03-19 08:10:10 -04:00
def pretty_title_with_category
return pretty_title if category_id.blank? || category_id == 0
"#{Tag.category_for_value(category_id)}: #{pretty_title}"
end
def wiki_page_changed?
saved_change_to_title? || saved_change_to_body? || saved_change_to_is_locked? || saved_change_to_is_deleted? || saved_change_to_other_names? || saved_change_to_parent?
end
2014-03-18 16:06:00 -04:00
def create_new_version
versions.create(
updater_id: CurrentUser.user.id,
updater_ip_addr: CurrentUser.ip_addr,
title: title,
body: body,
is_locked: is_locked,
is_deleted: is_deleted,
other_names: other_names,
parent: parent,
reason: edit_reason,
2014-03-18 16:06:00 -04:00
)
end
2010-02-15 17:32:32 -05:00
def create_version
if wiki_page_changed?
2019-11-15 23:00:54 -05:00
create_new_version
2010-02-24 15:40:55 -05:00
end
2010-02-15 17:32:32 -05:00
end
2013-03-19 08:10:10 -04:00
2011-03-03 18:26:50 -05:00
def post_set
@post_set ||= PostSets::Post.new(title, 1, limit: 4)
2011-03-03 18:26:50 -05:00
end
2013-03-19 08:10:10 -04:00
def tags
body.scan(/\[\[(.+?)\]\]/).flatten.map do |match|
if match =~ /^(.+?)\|(.+)/
$1
else
match
end
end.map { |x| x.downcase.tr(" ", "_").to_s }.uniq
2014-04-17 17:17:11 -04:00
end
2010-02-15 13:59:58 -05:00
end