2024-02-25 12:15:55 -05:00
# frozen_string_literal: true
2017-06-14 22:27:53 -04:00
class Post < ApplicationRecord
2019-04-09 22:20:19 -04:00
class RevertError < Exception ; end
class DeletionError < Exception ; end
class TimeoutError < Exception ; end
2013-03-19 08:10:10 -04:00
2018-03-31 13:58:56 -04:00
# Tags to copy when copying notes.
2021-06-22 06:24:48 -04:00
NOTE_COPY_TAGS = %w[ translated partially_translated translation_check translation_request ]
2018-03-31 13:58:56 -04:00
2016-10-21 02:11:11 -04:00
before_validation :initialize_uploader , :on = > :create
2016-10-21 05:57:07 -04:00
before_validation :merge_old_changes
2019-09-29 05:22:59 -04:00
before_validation :apply_source_diff
before_validation :apply_tag_diff , if : :should_process_tags?
2019-05-10 19:16:18 -04:00
before_validation :normalize_tags , if : :should_process_tags?
2020-02-26 03:26:13 -05:00
before_validation :tag_count_not_insane , if : :should_process_tags?
2016-10-21 05:57:07 -04:00
before_validation :strip_source
2019-12-31 13:19:11 -05:00
before_validation :fix_bg_color
2016-10-21 02:11:11 -04:00
before_validation :blank_out_nonexistent_parents
before_validation :remove_parent_loops
2024-11-10 23:22:40 -05:00
normalizes :description , with : - > ( desc ) { desc . gsub ( " \r \n " , " \n " ) }
2019-09-05 08:59:51 -04:00
validates :md5 , uniqueness : { :on = > :create , message : - > ( obj , data ) { " duplicate: #{ Post . find_by_md5 ( obj . md5 ) . id } " } }
validates :rating , inclusion : { in : %w( s q e ) , message : " rating must be s, q, or e " }
2019-12-31 13:19:11 -05:00
validates :bg_color , format : { with : / \ A[A-Fa-f0-9]{6} \ z / } , allow_nil : true
2024-10-30 19:52:53 -04:00
validates :title , length : { maximum : Danbooru . config . post_title_max_size } , if : :title_changed?
2021-10-31 23:30:37 -04:00
validates :description , length : { maximum : Danbooru . config . post_descr_max_size } , if : :description_changed?
2024-11-10 14:58:15 -05:00
validates :transcript , length : { maximum : Danbooru . config . post_trasc_max_size } , if : :transcript_changed?
2019-05-10 19:16:18 -04:00
validate :added_tags_are_valid , if : :should_process_tags?
validate :removed_tags_are_valid , if : :should_process_tags?
validate :has_artist_tag , if : :should_process_tags?
validate :has_enough_tags , if : :should_process_tags?
2016-10-21 02:11:11 -04:00
validate :post_is_not_its_own_parent
validate :updater_can_change_rating
2019-05-10 19:16:18 -04:00
before_save :update_tag_post_counts , if : :should_process_tags?
before_save :set_tag_counts , if : :should_process_tags?
2024-06-28 12:26:01 -04:00
after_save :create_post_events
2017-04-14 15:28:58 -04:00
after_save :create_version
2016-10-21 02:11:11 -04:00
after_save :update_parent_on_save
after_save :apply_post_metatags
2017-06-14 21:05:52 -04:00
after_commit :delete_files , :on = > :destroy
after_commit :remove_iqdb_async , :on = > :destroy
2017-02-02 02:26:59 -05:00
after_commit :update_iqdb_async , :on = > :create
2023-05-12 11:55:27 -04:00
after_commit :generate_video_samples , on : :create , if : :is_video?
2016-10-21 02:11:11 -04:00
2018-04-02 13:51:26 -04:00
belongs_to :updater , :class_name = > " User " , optional : true # this is handled in versions
belongs_to :approver , class_name : " User " , optional : true
2019-06-02 10:49:38 -04:00
belongs_to :uploader , :class_name = > " User "
user_status_counter :post_count , foreign_key : :uploader_id
2018-04-02 13:51:26 -04:00
belongs_to :parent , class_name : " Post " , optional : true
2010-02-15 13:59:58 -05:00
has_one :upload , :dependent = > :destroy
2020-09-29 15:11:31 -04:00
has_many :flags , :class_name = > " PostFlag " , :dependent = > :destroy
2010-02-15 17:45:09 -05:00
has_many :votes , :class_name = > " PostVote " , :dependent = > :destroy
2010-02-24 15:40:55 -05:00
has_many :notes , :dependent = > :destroy
2020-06-01 19:03:33 -04:00
has_many :comments , - > { includes ( :creator , :updater ) . order ( " comments.is_sticky DESC, comments.id " ) } , :dependent = > :destroy
2018-05-10 14:18:02 -04:00
has_many :children , - > { order ( " posts.id " ) } , :class_name = > " Post " , :foreign_key = > " parent_id "
2017-04-03 01:10:36 -04:00
has_many :approvals , :class_name = > " PostApproval " , :dependent = > :destroy
2019-04-09 22:20:19 -04:00
has_many :disapprovals , :class_name = > " PostDisapproval " , :dependent = > :destroy
2017-07-18 19:31:25 -04:00
has_many :favorites
2017-07-19 23:58:59 -04:00
has_many :replacements , class_name : " PostReplacement " , :dependent = > :destroy
2017-03-30 14:09:56 -04:00
2022-07-11 14:17:24 -04:00
attr_accessor :old_tag_string , :old_parent_id , :old_source , :old_rating ,
:do_not_version_changes , :tag_string_diff , :source_diff , :edit_reason
2013-03-19 08:10:10 -04:00
2022-08-06 12:58:24 -04:00
has_many :versions , - > { order ( " post_versions.id ASC " ) } , :class_name = > " PostVersion " , :dependent = > :destroy
2018-04-02 13:51:26 -04:00
2020-11-05 15:05:37 -05:00
IMAGE_TYPES = % i [ original large preview crop ]
2022-05-08 12:42:49 -04:00
module PostFileMethods
2017-05-02 20:47:47 -04:00
extend ActiveSupport :: Concern
module ClassMethods
2018-03-18 18:57:09 -04:00
def delete_files ( post_id , md5 , file_ext , force : false )
if Post . where ( md5 : md5 ) . exists? && ! force
raise DeletionError . new ( " Files still in use; skipping deletion. " )
2017-06-13 02:56:58 -04:00
end
2021-02-04 20:47:34 -05:00
Danbooru . config . storage_manager . delete_post_files ( md5 , file_ext )
2017-05-02 20:47:47 -04:00
end
2011-08-28 11:11:58 -04:00
end
2013-03-19 08:10:10 -04:00
2010-02-10 16:12:30 -05:00
def delete_files
2018-03-18 18:57:09 -04:00
Post . delete_files ( id , md5 , file_ext , force : true )
2017-05-02 20:47:47 -04:00
end
2019-02-12 06:00:57 -05:00
def move_files_on_delete
Danbooru . config . storage_manager . move_file_delete ( self )
end
def move_files_on_undelete
Danbooru . config . storage_manager . move_file_undelete ( self )
end
2018-03-20 00:24:32 -04:00
def storage_manager
Danbooru . config . storage_manager
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2018-03-20 00:24:32 -04:00
def file ( type = :original )
storage_manager . open_file ( self , type )
2017-09-12 16:03:59 -04:00
end
2018-04-05 15:03:41 -04:00
def tagged_large_file_url
2019-10-21 04:02:57 -04:00
storage_manager . file_url ( self , :large )
2018-04-05 15:03:41 -04:00
end
2018-03-20 00:24:32 -04:00
def file_url
storage_manager . file_url ( self , :original )
2010-02-10 16:12:30 -05:00
end
2020-09-03 18:39:33 -04:00
def file_url_ext ( ext )
storage_manager . file_url_ext ( self , :original , ext )
end
def scaled_url_ext ( scale , ext )
storage_manager . file_url_ext ( self , :scaled , ext , scale : scale )
end
2018-03-20 00:24:32 -04:00
def large_file_url
2020-10-18 05:04:15 -04:00
return file_url if ! has_large?
2018-03-20 00:24:32 -04:00
storage_manager . file_url ( self , :large )
2016-05-23 14:50:26 -04:00
end
2010-03-12 19:27:54 -05:00
def preview_file_url
2018-03-20 00:24:32 -04:00
storage_manager . file_url ( self , :preview )
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2021-02-21 16:00:02 -05:00
def reverse_image_url
return large_file_url if has_large?
preview_file_url
end
2018-06-14 20:10:07 -04:00
def file_path
2020-10-18 02:01:58 -04:00
storage_manager . file_path ( self , file_ext , :original , is_deleted? )
2018-06-14 20:10:07 -04:00
end
def large_file_path
2020-10-18 02:01:58 -04:00
storage_manager . file_path ( self , file_ext , :large , is_deleted? )
2018-06-14 20:10:07 -04:00
end
def preview_file_path
2020-10-18 02:01:58 -04:00
storage_manager . file_path ( self , file_ext , :preview , is_deleted? )
2018-06-14 20:10:07 -04:00
end
2018-05-16 20:13:36 -04:00
def crop_file_url
storage_manager . file_url ( self , :crop )
end
2020-12-03 18:18:11 -05:00
def open_graph_video_url
if image_height > 720 && has_sample_size? ( '720p' )
return scaled_url_ext ( '720p' , 'mp4' )
end
file_url_ext ( 'mp4' )
end
2017-12-06 10:01:13 -05:00
def open_graph_image_url
2017-12-15 14:30:26 -05:00
if is_image?
if has_large?
2018-03-20 00:24:32 -04:00
large_file_url
2017-12-15 14:30:26 -05:00
else
2018-03-20 00:24:32 -04:00
file_url
2017-12-15 14:30:26 -05:00
end
2017-12-06 10:01:13 -05:00
else
2018-03-18 19:11:44 -04:00
preview_file_url
2017-12-06 10:01:13 -05:00
end
end
2010-02-10 16:12:30 -05:00
def file_url_for ( user )
2018-03-14 18:09:01 -04:00
if user . default_image_size == " large " && image_width > Danbooru . config . large_image_width
2020-09-26 01:28:26 -04:00
large_file_url
2010-02-10 16:12:30 -05:00
else
2020-09-26 01:28:26 -04:00
file_url
end
end
def file_url_ext_for ( user , ext )
if user . default_image_size == " large " && is_video? && has_sample_size? ( '720p' )
scaled_url_ext ( '720p' , ext )
else
file_url_ext ( ext )
2010-02-10 16:12:30 -05:00
end
end
2013-03-19 08:10:10 -04:00
2019-07-02 13:20:13 -04:00
def display_class_for ( user )
if user . default_image_size == " original "
" "
else
" fit-window "
end
end
2014-06-30 13:21:07 -04:00
def has_preview?
2022-03-16 12:08:40 -04:00
is_image? || is_video?
2014-06-30 13:21:07 -04:00
end
def has_dimensions?
2014-08-02 17:07:55 -04:00
image_width . present? && image_height . present?
2014-06-30 13:21:07 -04:00
end
2014-10-28 14:30:02 -04:00
2019-04-30 11:21:45 -04:00
def preview_dimensions ( max_px = Danbooru . config . small_image_width )
2020-03-10 21:33:05 -04:00
return [ max_px , max_px ] unless has_dimensions?
2019-04-30 11:21:45 -04:00
height = width = max_px
dimension_ratio = image_width . to_f / image_height
if dimension_ratio > 1
height = ( width / dimension_ratio ) . to_i
else
width = ( height * dimension_ratio ) . to_i
end
[ height , width ]
end
2020-09-03 18:39:33 -04:00
def has_sample_size? ( scale )
( generated_samples || [ ] ) . include? ( scale )
end
2020-09-26 01:28:26 -04:00
def scaled_sample_dimensions ( box )
ratio = [ box [ 0 ] / image_width . to_f , box [ 1 ] / image_height . to_f ] . min
width = [ ( [ image_width * ratio , 2 ] . max . ceil ) , box [ 0 ] ] . min & ~ 1
height = [ ( [ image_height * ratio , 2 ] . max . ceil ) , box [ 1 ] ] . min & ~ 1
[ width , height ]
end
2020-10-11 20:27:51 -04:00
2021-02-04 20:47:34 -05:00
def generate_video_samples ( later : false )
if later
2023-05-01 12:19:26 -04:00
PostVideoConversionJob . set ( wait : 1 . minute ) . perform_later ( id )
2021-02-04 20:47:34 -05:00
else
2023-05-01 12:19:26 -04:00
PostVideoConversionJob . perform_later ( id )
2021-02-04 20:47:34 -05:00
end
2020-10-11 20:27:51 -04:00
end
2021-03-06 03:37:48 -05:00
def regenerate_video_samples!
# force code to assume no samples exist
update_column ( :generated_samples , nil )
generate_video_samples ( later : true )
end
def regenerate_image_samples!
file = self . file ( )
2024-08-28 12:22:17 -04:00
preview_file , crop_file , sample_file = :: PostThumbnailer . generate_resizes ( file , image_height , image_width , is_video? ? :video : :image , background_color : bg_color . presence || " 000000 " )
2021-03-06 03:37:48 -05:00
storage_manager . store_file ( sample_file , self , :large ) if sample_file . present?
storage_manager . store_file ( preview_file , self , :preview ) if preview_file . present?
storage_manager . store_file ( crop_file , self , :crop ) if crop_file . present?
update ( { has_cropped : crop_file . present? } )
ensure
file . close
end
2010-02-09 01:11:42 -05:00
end
2013-03-19 08:10:10 -04:00
2010-02-10 16:12:30 -05:00
module ImageMethods
2013-06-07 18:51:57 -04:00
def twitter_card_supported?
2015-11-30 14:05:42 -05:00
image_width . to_i > = 280 && image_height . to_i > = 150
2013-06-07 18:51:57 -04:00
end
2010-02-10 16:12:30 -05:00
def has_large?
2019-07-25 14:41:25 -04:00
return true if is_video?
2020-03-10 21:33:05 -04:00
return false if is_gif?
2020-06-26 21:14:01 -04:00
return false if is_flash?
2023-09-03 12:24:40 -04:00
return false if has_tag? ( " animated_gif " , " animated_png " )
2015-02-15 15:57:22 -05:00
is_image? && image_width . present? && image_width > Danbooru . config . large_image_width
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2013-02-28 16:21:01 -05:00
def has_large
2014-10-25 12:08:59 -04:00
! ! has_large?
2013-02-28 16:21:01 -05:00
end
2013-03-19 08:10:10 -04:00
2010-03-26 16:19:12 -04:00
def large_image_width
2015-05-07 16:32:24 -04:00
if has_large?
[ Danbooru . config . large_image_width , image_width ] . min
else
image_width
end
2010-03-26 16:19:12 -04:00
end
2013-03-19 08:10:10 -04:00
2010-03-26 16:19:12 -04:00
def large_image_height
ratio = Danbooru . config . large_image_width . to_f / image_width . to_f
2015-05-07 16:32:24 -04:00
if has_large? && ratio < 1
2010-04-29 17:32:15 -04:00
( image_height * ratio ) . to_i
2010-03-26 16:19:12 -04:00
else
image_height
end
end
2013-03-19 08:10:10 -04:00
2012-03-30 14:28:46 -04:00
def resize_percentage
100 * large_image_width . to_f / image_width . to_f
end
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2010-08-18 18:42:33 -04:00
module ApprovalMethods
2022-09-20 05:04:16 -04:00
def is_approvable?
! is_status_locked? && is_pending? && approver . nil?
2011-02-10 19:08:23 -05:00
end
2013-03-19 08:10:10 -04:00
2019-08-01 19:03:13 -04:00
def unflag!
flags . each ( & :resolve! )
update ( is_flagged : false )
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , :flag_removed )
2019-08-01 19:03:13 -04:00
end
2017-04-03 01:10:36 -04:00
def approved_by? ( user )
approver == user || approvals . where ( user : user ) . exists?
end
2022-01-06 07:44:30 -05:00
def unapprove!
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , :unapproved )
2019-07-19 21:05:43 -04:00
update ( approver : nil , is_pending : true )
end
2021-02-27 18:27:10 -05:00
2022-09-20 05:04:16 -04:00
def is_unapprovable? ( user )
# Allow unapproval only by the approver
return false if approver . present? && approver != user
# Prevent unapproving self approvals by someone else
return false if approver . nil? && uploader != user
# Allow unapproval when the post is not pending anymore and is not at risk of auto deletion
! is_pending? && ! is_deleted? && created_at . after? ( PostPruner :: DELETION_WINDOW . days . ago )
end
2023-08-07 14:23:13 -04:00
def approve! ( approver = CurrentUser . user )
2022-09-20 05:04:16 -04:00
return if self . approver != nil
2021-01-10 19:38:09 -05:00
2022-09-20 05:11:34 -04:00
if uploader == approver
update ( is_pending : false )
else
PostEvent . add ( id , CurrentUser . user , :approved )
approvals . create ( user : approver )
update ( approver : approver , is_pending : false )
end
2011-10-22 12:54:14 -04:00
end
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2019-04-26 06:34:40 -04:00
module SourceMethods
def source_array
2019-04-26 10:27:06 -04:00
return [ ] if source . blank?
source . split ( " \n " )
2019-04-26 06:34:40 -04:00
end
2019-09-29 05:22:59 -04:00
def apply_source_diff
return unless source_diff . present?
diff = source_diff . gsub ( / \ r \ n? / , " \n " ) . gsub ( / %0A /i , " \n " ) . split ( / (?: \ r)? \ n / )
to_remove , to_add = diff . partition { | x | x =~ / \ A- /i }
2024-09-08 17:03:16 -04:00
to_remove = to_remove . map { | x | x [ 1 .. - 1 ] . starts_with? ( '"' ) && x . ends_with? ( '"' ) ? x [ 1 .. - 1 ] . delete_prefix ( '"' ) . delete_suffix ( '"' ) : x [ 1 .. - 1 ] }
to_add = to_add . map { | x | x . starts_with? ( '"' ) && x . ends_with? ( '"' ) ? x . delete_prefix ( '"' ) . delete_suffix ( '"' ) : x }
2019-09-29 05:22:59 -04:00
current_sources = source_array
current_sources += to_add
current_sources -= to_remove
self . source = current_sources . join ( " \n " )
end
2019-04-26 06:34:40 -04:00
def strip_source
self . source = " " if source . blank?
self . source . gsub! ( / \ r \ n? / , " \n " ) # Normalize newlines
self . source . gsub! ( / %0A /i , " \n " ) # Handle accidentally-encoded %0As from api calls (which would normally insert a literal %0A into the source)
sources = self . source . split ( / (?: \ r)? \ n / )
gallery_sources = [ ]
submission_sources = [ ]
direct_sources = [ ]
additional_sources = [ ]
2020-07-04 20:35:45 -04:00
alternate_processors = [ ]
2019-04-26 06:34:40 -04:00
sources . map! do | src |
src . unicode_normalize! ( :nfc )
src = src . try ( :strip )
alternate = Sources :: Alternates . find ( src )
2020-07-04 20:35:45 -04:00
alternate_processors << alternate
2019-04-26 06:34:40 -04:00
gallery_sources << alternate . gallery_url if alternate . gallery_url
submission_sources << alternate . submission_url if alternate . submission_url
direct_sources << alternate . submission_url if alternate . direct_url
additional_sources += alternate . additional_urls if alternate . additional_urls
alternate . original_url
end
2020-07-04 20:35:45 -04:00
sources = ( sources + submission_sources + gallery_sources + direct_sources + additional_sources ) . compact . reject { | e | e . strip . empty? } . uniq
alternate_processors . each do | alt_processor |
sources = alt_processor . remove_duplicates ( sources )
end
self . source = sources . first ( 10 ) . join ( " \n " )
2019-04-26 06:34:40 -04:00
end
2021-03-11 01:39:12 -05:00
def copy_sources_to_parent
return unless parent_id . present?
parent . source += " \n #{ self . source } "
end
2019-04-26 06:34:40 -04:00
end
2010-02-10 16:12:30 -05:00
module PresenterMethods
2011-02-12 17:37:48 -05:00
def presenter
@presenter || = PostPresenter . new ( self )
end
2013-04-19 18:21:43 -04:00
def status_flags
flags = [ ]
flags << " pending " if is_pending?
flags << " flagged " if is_flagged?
flags << " deleted " if is_deleted?
flags . join ( " " )
end
2010-02-10 16:12:30 -05:00
def pretty_rating
case rating
when " q "
" Questionable "
2013-03-19 08:10:10 -04:00
2010-02-10 16:12:30 -05:00
when " e "
" Explicit "
2013-03-19 08:10:10 -04:00
2010-02-10 16:12:30 -05:00
when " s "
" Safe "
end
end
end
2013-03-19 08:10:10 -04:00
2010-02-10 16:12:30 -05:00
module TagMethods
2019-05-10 19:16:18 -04:00
def should_process_tags?
2024-09-01 14:41:56 -04:00
if @removed_tags . nil?
@removed_tags = [ ]
end
tag_string_changed? || locked_tags_changed? || tag_string_diff . present? || @removed_tags . length > 0 || added_tags . length > 0
2019-05-10 19:16:18 -04:00
end
2010-02-11 23:15:31 -05:00
def tag_array
2023-08-24 07:16:44 -04:00
@tag_array || = TagQuery . scan ( tag_string )
2010-02-11 23:15:31 -05:00
end
2013-03-19 08:10:10 -04:00
2010-02-11 23:15:31 -05:00
def tag_array_was
2023-08-24 07:16:44 -04:00
@tag_array_was || = TagQuery . scan ( tag_string_in_database . presence || tag_string_before_last_save || " " )
2010-02-11 23:15:31 -05:00
end
2013-03-19 08:10:10 -04:00
2017-11-08 20:33:02 -05:00
def tags
Tag . where ( name : tag_array )
end
def tags_was
Tag . where ( name : tag_array_was )
end
def added_tags
tags - tags_was
end
2010-08-18 18:42:33 -04:00
def decrement_tag_post_counts
2022-07-10 14:34:00 -04:00
Tag . decrement_post_counts ( tag_array )
end
def increment_tag_post_counts
Tag . increment_post_counts ( tag_array )
2010-08-18 18:42:33 -04:00
end
2013-03-19 08:10:10 -04:00
2010-02-11 23:15:31 -05:00
def update_tag_post_counts
2022-07-10 14:34:00 -04:00
return if is_deleted?
2015-07-21 17:14:08 -04:00
2022-07-10 14:34:00 -04:00
decrement_tags = tag_array_was - tag_array
2010-02-11 23:15:31 -05:00
increment_tags = tag_array - tag_array_was
2022-07-10 14:34:00 -04:00
Tag . increment_post_counts ( increment_tags )
Tag . decrement_post_counts ( decrement_tags )
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2019-03-23 04:17:15 -04:00
def set_tag_count ( category , tagcount )
self . send ( " tag_count_ #{ category } = " , tagcount )
2017-11-06 02:17:32 -05:00
end
def inc_tag_count ( category )
2019-03-23 04:17:15 -04:00
set_tag_count ( category , self . send ( " tag_count_ #{ category } " ) + 1 )
2017-11-06 02:17:32 -05:00
end
2023-05-19 14:43:59 -04:00
def set_tag_counts ( disable_cache : true )
2010-02-10 16:12:30 -05:00
self . tag_count = 0
2023-08-18 13:08:52 -04:00
TagCategory :: CATEGORIES . each { | x | set_tag_count ( x , 0 ) }
2023-05-19 14:43:59 -04:00
categories = Tag . categories_for ( tag_array , disable_cache : disable_cache )
2010-02-10 16:12:30 -05:00
categories . each_value do | category |
self . tag_count += 1
2023-08-18 13:08:52 -04:00
inc_tag_count ( TagCategory :: REVERSE_MAPPING [ category ] )
2010-02-10 16:12:30 -05:00
end
end
2013-03-19 08:10:10 -04:00
2013-08-29 23:33:09 -04:00
def merge_old_changes
2010-02-10 16:12:30 -05:00
if old_tag_string
# If someone else committed changes to this post before we did,
# then try to merge the tag changes together.
2010-02-11 23:26:38 -05:00
current_tags = tag_array_was ( )
2010-02-10 16:12:30 -05:00
new_tags = tag_array ( )
2023-08-24 07:16:44 -04:00
old_tags = TagQuery . scan ( old_tag_string )
2017-11-25 18:02:16 -05:00
kept_tags = current_tags & new_tags
@removed_tags = old_tags - kept_tags
2013-02-28 16:21:01 -05:00
set_tag_string ( ( ( current_tags + new_tags ) - old_tags + ( current_tags & new_tags ) ) . uniq . sort . join ( " " ) )
2010-02-10 16:12:30 -05:00
end
2013-08-29 23:33:09 -04:00
if old_parent_id == " "
old_parent_id = nil
else
old_parent_id = old_parent_id . to_i
end
if old_parent_id == parent_id
2018-04-02 13:51:26 -04:00
self . parent_id = parent_id_before_last_save || parent_id_was
2013-08-29 23:33:09 -04:00
end
if old_source == source . to_s
2018-04-02 13:51:26 -04:00
self . source = source_before_last_save || source_was
2013-08-29 23:33:09 -04:00
end
if old_rating == rating
2018-04-02 13:51:26 -04:00
self . rating = rating_before_last_save || rating_was
2013-08-29 23:33:09 -04:00
end
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2019-09-29 05:22:59 -04:00
def apply_tag_diff
return unless tag_string_diff . present?
current_tags = tag_array
2024-10-22 18:41:05 -04:00
diff = TagQuery . scan ( tag_string_diff )
2019-09-29 05:22:59 -04:00
to_remove , to_add = diff . partition { | x | x =~ / \ A- /i }
to_remove = to_remove . map { | x | x [ 1 .. - 1 ] }
to_remove = TagAlias . to_aliased ( to_remove )
to_add = TagAlias . to_aliased ( to_add )
@removed_tags = to_remove
current_tags += to_add
current_tags -= to_remove
set_tag_string ( current_tags . uniq . sort . join ( " " ) )
end
2010-02-11 23:15:31 -05:00
def reset_tag_array_cache
@tag_array = nil
@tag_array_was = nil
end
2013-03-19 08:10:10 -04:00
2010-02-11 23:15:31 -05:00
def set_tag_string ( string )
self . tag_string = string
reset_tag_array_cache
end
2013-03-19 08:10:10 -04:00
2020-02-26 03:26:13 -05:00
def tag_count_not_insane
2024-04-21 13:09:52 -04:00
return if do_not_version_changes
2020-02-26 03:26:13 -05:00
max_count = Danbooru . config . max_tags_per_post
2023-08-24 07:16:44 -04:00
if TagQuery . scan ( tag_string ) . size > max_count
2020-02-26 03:26:13 -05:00
self . errors . add ( :tag_string , " tag count exceeds maximum of #{ max_count } " )
throw :abort
end
true
end
2010-02-10 16:12:30 -05:00
def normalize_tags
2019-02-02 21:03:47 -05:00
if ! locked_tags . nil? && locked_tags . strip . blank?
2019-06-23 03:47:24 -04:00
self . locked_tags = nil
2019-02-05 18:47:11 -05:00
elsif locked_tags . present?
2023-08-24 07:16:44 -04:00
locked = TagQuery . scan ( locked_tags . downcase )
2019-02-05 18:47:11 -05:00
to_remove , to_add = locked . partition { | x | x =~ / \ A- /i }
to_remove = to_remove . map { | x | x [ 1 .. - 1 ] }
2022-09-24 14:40:59 -04:00
to_remove = TagAlias . to_aliased ( to_remove )
@locked_to_remove = to_remove + to_remove . map { | tag_name | TagImplication . cached_descendants ( tag_name ) } . flatten
2019-02-05 18:47:11 -05:00
@locked_to_add = TagAlias . to_aliased ( to_add )
2019-02-02 21:03:47 -05:00
end
2019-02-05 18:47:11 -05:00
2023-08-24 07:16:44 -04:00
normalized_tags = TagQuery . scan ( tag_string )
2017-10-21 16:34:34 -04:00
normalized_tags = apply_casesensitive_metatags ( normalized_tags )
2017-10-09 16:34:58 -04:00
normalized_tags = normalized_tags . map { | tag | tag . downcase }
2017-10-09 19:02:29 -04:00
normalized_tags = filter_metatags ( normalized_tags )
2013-11-27 00:03:06 -05:00
normalized_tags = remove_negated_tags ( normalized_tags )
2020-01-02 14:30:08 -05:00
normalized_tags = remove_dnp_tags ( normalized_tags )
2014-09-20 15:10:49 -04:00
normalized_tags = TagAlias . to_aliased ( normalized_tags )
2019-02-05 18:47:11 -05:00
normalized_tags = apply_locked_tags ( normalized_tags , @locked_to_add , @locked_to_remove )
2023-04-10 14:08:36 -04:00
normalized_tags = %w[ tagme ] if normalized_tags . empty?
2017-12-19 15:37:12 -05:00
normalized_tags = add_automatic_tags ( normalized_tags )
normalized_tags = TagImplication . with_descendants ( normalized_tags )
2023-04-10 14:08:36 -04:00
add_dnp_tags_to_locked ( normalized_tags )
2019-02-05 18:47:11 -05:00
normalized_tags -= @locked_to_remove if @locked_to_remove # Prevent adding locked tags through implications or aliases.
2019-10-24 10:09:29 -04:00
normalized_tags = normalized_tags . compact . uniq
2020-02-26 04:53:26 -05:00
normalized_tags = Tag . find_or_create_by_name_list ( normalized_tags )
normalized_tags = remove_invalid_tags ( normalized_tags )
set_tag_string ( normalized_tags . map ( & :name ) . uniq . sort . join ( " " ) )
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2023-04-10 14:08:36 -04:00
# Prevent adding these without an implication
2020-01-02 14:30:08 -05:00
def remove_dnp_tags ( tags )
2023-04-10 14:08:36 -04:00
locked = locked_tags || " "
# Don't remove dnp tags here if they would be later added through locked tags
# to prevent the warning message from appearing when they didn't actually get removed
if locked . exclude? ( " avoid_posting " )
tags -= [ " avoid_posting " ]
end
if locked . exclude? ( " conditional_dnp " )
tags -= [ " conditional_dnp " ]
end
tags
2020-01-02 14:30:08 -05:00
end
2023-04-10 14:08:36 -04:00
def add_dnp_tags_to_locked ( tags )
2023-08-24 07:16:44 -04:00
locked = TagQuery . scan ( ( locked_tags || '' ) . downcase )
2019-10-11 22:14:36 -04:00
if tags . include? 'avoid_posting'
locked << 'avoid_posting'
end
if tags . include? 'conditional_dnp'
locked << 'conditional_dnp'
end
self . locked_tags = locked . uniq . join ( ' ' ) if locked . size > 0
end
2019-02-05 18:47:11 -05:00
def apply_locked_tags ( tags , to_add , to_remove )
if to_remove
overlap = tags & to_remove
n = overlap . size
2019-02-15 04:34:59 -05:00
if n > 0
2021-02-07 20:16:52 -05:00
self . warnings . add ( :base , " Forcefully removed #{ n } locked #{ n == 1 ? " tag " : " tags " } : #{ overlap . join ( " , " ) } " )
2019-02-05 18:47:11 -05:00
end
tags -= to_remove
end
if to_add
missing = to_add - tags
n = missing . size
2019-02-15 04:34:59 -05:00
if n > 0
2021-02-07 20:16:52 -05:00
self . warnings . add ( :base , " Forcefully added #{ n } locked #{ n == 1 ? " tag " : " tags " } : #{ missing . join ( " , " ) } " )
2019-02-05 18:47:11 -05:00
end
tags += to_add
end
tags
end
2018-01-15 21:00:47 -05:00
def remove_invalid_tags ( tags )
2020-02-26 04:53:26 -05:00
tags = tags . reject do | tag |
if tag . errors . size > 0
2021-02-07 20:16:52 -05:00
self . warnings . add ( :base , " Can't add tag #{ tag . name } : #{ tag . errors . full_messages . join ( '; ' ) } " )
2020-02-26 04:53:26 -05:00
end
tag . errors . size > 0
end
2019-07-19 22:04:18 -04:00
tags
2018-01-15 21:00:47 -05:00
end
2013-11-27 00:03:06 -05:00
def remove_negated_tags ( tags )
2017-11-25 18:02:16 -05:00
@negated_tags , tags = tags . partition { | x | x =~ / \ A- /i }
@negated_tags = @negated_tags . map { | x | x [ 1 .. - 1 ] }
@negated_tags = TagAlias . to_aliased ( @negated_tags )
return tags - @negated_tags
2013-11-27 00:03:06 -05:00
end
2013-12-15 19:39:10 -05:00
def add_automatic_tags ( tags )
2021-11-14 16:16:36 -05:00
return tags if ! Danbooru . config . enable_dimension_autotagging?
2013-12-15 19:39:10 -05:00
2022-03-16 12:08:40 -04:00
tags -= %w[ thumbnail low_res hi_res absurd_res superabsurd_res huge_filesize flash webm mp4 wide_image long_image ]
2013-12-15 19:39:10 -05:00
2014-08-02 17:07:55 -04:00
if has_dimensions?
2019-07-19 22:04:18 -04:00
tags << " superabsurd_res " if image_width > = 10_000 && image_height > = 10_000
tags << " absurd_res " if image_width > = 3200 || image_height > = 2400
tags << " hi_res " if image_width > = 1600 || image_height > = 1200
2019-07-19 23:24:31 -04:00
tags << " low_res " if image_width < = 500 && image_height < = 500
2019-07-19 22:04:18 -04:00
tags << " thumbnail " if image_width < = 250 && image_height < = 250
2014-08-02 17:07:55 -04:00
if image_width > = 1024 && image_width . to_f / image_height > = 4
2014-09-20 14:50:52 -04:00
tags << " wide_image "
tags << " long_image "
2014-08-02 17:07:55 -04:00
elsif image_height > = 1024 && image_height . to_f / image_width > = 4
2014-09-20 14:50:52 -04:00
tags << " tall_image "
tags << " long_image "
2014-08-02 17:07:55 -04:00
end
2013-12-15 19:39:10 -05:00
end
2019-07-19 22:04:18 -04:00
if file_size > = 30 . megabytes
2013-12-15 19:39:10 -05:00
tags << " huge_filesize "
end
2014-10-24 19:29:11 -04:00
if is_flash?
2014-09-20 14:52:49 -04:00
tags << " flash "
end
2015-08-25 20:02:57 -04:00
if is_webm?
2014-09-20 14:52:49 -04:00
tags << " webm "
end
2019-10-21 07:08:17 -04:00
unless is_gif?
2018-10-11 00:48:45 -04:00
tags -= [ " animated_gif " ]
end
2019-10-21 07:08:17 -04:00
unless is_png?
2018-10-11 00:48:45 -04:00
tags -= [ " animated_png " ]
end
2013-12-15 19:39:10 -05:00
return tags
end
2017-10-21 16:34:34 -04:00
def apply_casesensitive_metatags ( tags )
2017-10-30 19:38:06 -04:00
casesensitive_metatags , tags = tags . partition { | x | x =~ / \ A(?:source): /i }
#Reuse the following metatags after the post has been saved
casesensitive_metatags += tags . select { | x | x =~ / \ A(?:newpool): /i }
2017-10-21 16:34:34 -04:00
if casesensitive_metatags . length > 0
case casesensitive_metatags [ - 1 ]
when / ^source:none$ /i
self . source = " "
when / ^source:"(.*)"$ /i
self . source = $1
when / ^source:(.*)$ /i
self . source = $1
when / ^newpool:(.+)$ /i
pool = Pool . find_by_name ( $1 )
if pool . nil?
2024-10-22 18:41:05 -04:00
pool = Pool . create ( name : $1 , description : " " )
2017-10-21 16:34:34 -04:00
end
end
end
return tags
end
2010-02-11 14:59:58 -05:00
def filter_metatags ( tags )
2019-09-22 18:49:44 -04:00
@bad_type_changes = [ ]
2017-10-21 16:34:34 -04:00
@pre_metatags , tags = tags . partition { | x | x =~ / \ A(?:rating|parent|-parent|-?locked): /i }
2020-01-31 13:01:50 -05:00
tags = apply_categorization_metatags ( tags )
2019-09-15 18:52:53 -04:00
@post_metatags , tags = tags . partition { | x | x =~ / \ A(?:-pool|pool|newpool|-set|set|fav|-fav|child|-child|upvote|downvote): /i }
2013-03-01 09:16:26 -05:00
apply_pre_metatags
2020-01-31 13:01:50 -05:00
if @bad_type_changes . size > 0
bad_tags = @bad_type_changes . map { | x | " [[ #{ x } ]] " }
2021-02-07 20:16:52 -05:00
self . warnings . add ( :base , " Failed to update the tag category for the following tags: #{ bad_tags . join ( ', ' ) } . You can not edit the tag category of existing tags using prefixes. Please review usage of the tags, and if you are sure that the tag categories should be changed, then you can change them using the \" Tags \" :/tags section of the website " )
2020-01-31 13:01:50 -05:00
end
2019-09-22 18:49:44 -04:00
tags
2011-09-13 19:08:39 -04:00
end
2013-03-19 08:10:10 -04:00
2017-10-09 19:02:29 -04:00
def apply_categorization_metatags ( tags )
2020-01-31 13:01:50 -05:00
prefixed , unprefixed = tags . partition { | x | x =~ Tag . categories . regexp }
prefixed = Tag . find_or_create_by_name_list ( prefixed )
prefixed . map! do | tag |
@bad_type_changes << tag . name if tag . errors . include? :category
tag . name
2017-10-09 19:02:29 -04:00
end
2020-01-31 13:01:50 -05:00
prefixed + unprefixed
2017-10-09 19:02:29 -04:00
end
2013-03-01 09:16:26 -05:00
def apply_post_metatags
return unless @post_metatags
2013-03-19 08:10:10 -04:00
2013-03-01 09:16:26 -05:00
@post_metatags . each do | tag |
2011-09-13 19:08:39 -04:00
case tag
2013-04-21 11:44:38 -04:00
when / ^-pool:( \ d+)$ /i
2024-07-25 17:47:59 -04:00
pool = Pool . find_by ( id : $1 . to_i )
if pool
pool . remove! ( self )
if pool . errors . any?
errors . add ( :base , pool . errors . full_messages . join ( " ; " ) )
end
end
2013-03-19 08:10:10 -04:00
2013-04-21 11:44:38 -04:00
when / ^-pool:(.+)$ /i
2024-10-22 18:41:05 -04:00
pool = Pool . find_by_name ( $1 )
2024-07-25 17:47:59 -04:00
if pool
pool . remove! ( self )
if pool . errors . any?
errors . add ( :base , pool . errors . full_messages . join ( " ; " ) )
end
end
2013-03-19 08:10:10 -04:00
2013-04-21 11:44:38 -04:00
when / ^pool:( \ d+)$ /i
2024-07-25 17:47:59 -04:00
pool = Pool . find_by ( id : $1 . to_i )
if pool
pool . add! ( self )
if pool . errors . any?
errors . add ( :base , pool . errors . full_messages . join ( " ; " ) )
end
end
2013-08-08 15:44:46 -04:00
2024-07-25 17:47:59 -04:00
when / ^(?:new)?pool:(.+)$ /i
2024-10-22 18:41:05 -04:00
pool = Pool . find_by_name ( $1 )
2024-07-25 17:47:59 -04:00
if pool
pool . add! ( self )
if pool . errors . any?
errors . add ( :base , pool . errors . full_messages . join ( " ; " ) )
end
end
2017-10-30 19:38:06 -04:00
2019-09-18 16:13:18 -04:00
when / ^set:( \ d+)$ /i
2024-07-25 17:47:59 -04:00
set = PostSet . find_by ( id : $1 . to_i )
if set & . can_edit_posts? ( CurrentUser . user )
set . add! ( self )
if set . errors . any?
errors . add ( :base , set . errors . full_messages . join ( " ; " ) )
end
end
2019-09-18 16:13:18 -04:00
when / ^-set:( \ d+)$ /i
2024-07-25 17:47:59 -04:00
set = PostSet . find_by ( id : $1 . to_i )
if set & . can_edit_posts? ( CurrentUser . user )
set . remove! ( self )
if set . errors . any?
errors . add ( :base , set . errors . full_messages . join ( " ; " ) )
end
end
2019-09-18 16:13:18 -04:00
when / ^set:(.+)$ /i
2024-07-25 17:47:59 -04:00
set = PostSet . find_by ( shortname : $1 )
if set & . can_edit_posts? ( CurrentUser . user )
set . add! ( self )
if set . errors . any?
errors . add ( :base , set . errors . full_messages . join ( " ; " ) )
end
end
2019-09-18 16:13:18 -04:00
when / ^-set:(.+)$ /i
2024-07-25 17:47:59 -04:00
set = PostSet . find_by ( shortname : $1 )
if set & . can_edit_posts? ( CurrentUser . user )
set . remove! ( self )
if set . errors . any?
errors . add ( :base , set . errors . full_messages . join ( " ; " ) )
end
end
2019-09-18 16:13:18 -04:00
2018-09-29 20:05:09 -04:00
when / ^child:none$ /i
children . each do | post |
post . update! ( parent_id : nil )
end
when / ^-child:(.+)$ /i
children . numeric_attribute_matches ( :id , $1 ) . each do | post |
post . update! ( parent_id : nil )
end
2013-11-29 17:48:20 -05:00
when / ^child:(.+)$ /i
2018-09-29 20:05:09 -04:00
Post . numeric_attribute_matches ( :id , $1 ) . where . not ( id : id ) . limit ( 10 ) . each do | post |
post . update! ( parent_id : id )
end
2011-09-13 19:08:39 -04:00
end
end
2019-02-17 14:17:35 -05:00
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2013-03-01 09:16:26 -05:00
def apply_pre_metatags
return unless @pre_metatags
2013-03-19 08:10:10 -04:00
2013-03-01 09:16:26 -05:00
@pre_metatags . each do | tag |
case tag
2013-04-21 11:44:38 -04:00
when / ^parent:none$ /i , / ^parent:0$ /i
2013-03-01 09:16:26 -05:00
self . parent_id = nil
2013-03-19 08:10:10 -04:00
2013-12-06 14:25:24 -05:00
when / ^-parent:( \ d+)$ /i
if parent_id == $1 . to_i
self . parent_id = nil
end
2013-04-21 11:44:38 -04:00
when / ^parent:( \ d+)$ /i
2013-10-20 20:13:03 -04:00
if $1 . to_i != id && Post . exists? ( [ " id = ? " , $1 . to_i ] )
2013-03-26 20:55:58 -04:00
self . parent_id = $1 . to_i
2013-10-20 20:13:03 -04:00
remove_parent_loops
2013-03-26 20:55:58 -04:00
end
2013-03-01 09:16:26 -05:00
when / ^rating:([qse]) /i
2017-10-21 16:34:34 -04:00
self . rating = $1
2016-10-22 02:47:38 -04:00
when / ^(-?)locked:notes?$ /i
2019-06-29 15:58:28 -04:00
self . is_note_locked = ( $1 != " - " ) if CurrentUser . is_janitor?
2016-10-22 02:47:38 -04:00
when / ^(-?)locked:rating$ /i
2019-06-29 15:58:28 -04:00
self . is_rating_locked = ( $1 != " - " ) if CurrentUser . is_janitor?
2016-10-22 02:47:38 -04:00
when / ^(-?)locked:status$ /i
2023-03-23 14:19:19 -04:00
self . is_status_locked = ( $1 != " - " ) if CurrentUser . is_admin?
2017-10-09 19:02:29 -04:00
2013-03-01 09:16:26 -05:00
end
end
end
2013-03-19 08:10:10 -04:00
2023-09-03 12:24:40 -04:00
def has_tag? ( * )
TagQuery . has_tag? ( tag_array , * )
end
def fetch_tags ( * )
TagQuery . fetch_tags ( tag_array , * )
2010-10-08 18:42:26 -04:00
end
2013-03-19 08:10:10 -04:00
2023-09-03 12:44:42 -04:00
def ad_tag_string
TagQuery . ad_tag_string ( tag_array )
end
2016-07-19 19:49:04 -04:00
def add_tag ( tag )
set_tag_string ( " #{ tag_string } #{ tag } " )
end
def remove_tag ( tag )
2016-07-28 18:53:23 -04:00
set_tag_string ( ( tag_array - Array ( tag ) ) . join ( " " ) )
2016-07-19 19:49:04 -04:00
end
2022-04-29 16:28:53 -04:00
def inject_tag_categories ( tag_cats )
@tag_categories = tag_cats
2023-08-29 11:33:30 -04:00
@typed_tags = tag_array . group_by do | tag_name |
@tag_categories [ tag_name ]
2022-04-29 16:28:53 -04:00
end
end
2012-10-30 17:58:52 -04:00
def tag_categories
@tag_categories || = Tag . categories_for ( tag_array )
end
2013-03-19 08:10:10 -04:00
2023-08-29 11:33:30 -04:00
def typed_tags ( category_id )
2012-10-30 17:58:52 -04:00
@typed_tags || = { }
2023-08-29 11:33:30 -04:00
@typed_tags [ category_id ] || = begin
2012-10-30 17:58:52 -04:00
tag_array . select do | tag |
2023-08-29 11:33:30 -04:00
tag_categories [ tag ] == category_id
2012-10-30 17:58:52 -04:00
end
end
end
2013-03-19 08:10:10 -04:00
2021-03-11 01:39:12 -05:00
def copy_tags_to_parent
return unless parent_id . present?
parent . tag_string += " #{ tag_string } "
end
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2017-11-06 02:17:32 -05:00
2010-02-10 16:12:30 -05:00
module FavoriteMethods
2013-04-26 20:54:38 -04:00
def clean_fav_string!
2018-09-20 20:13:31 -04:00
array = fav_string . split . uniq
2013-04-26 20:54:38 -04:00
self . fav_string = array . join ( " " )
self . fav_count = array . size
end
2018-04-27 01:48:53 -04:00
def favorited_by? ( user_id = CurrentUser . id )
2017-05-23 14:12:10 -04:00
! ! ( fav_string =~ / (?: \ A| )fav: #{ user_id } (?: \ Z| ) / )
2011-06-03 19:47:16 -04:00
end
2013-03-19 08:10:10 -04:00
2018-04-27 01:48:53 -04:00
alias_method :is_favorited? , :favorited_by?
2011-06-05 04:05:21 -04:00
def append_user_to_fav_string ( user_id )
2019-03-30 04:59:48 -04:00
self . fav_string = ( fav_string + " fav: #{ user_id } " ) . strip
clean_fav_string!
2011-06-05 04:05:21 -04:00
end
2013-03-19 08:10:10 -04:00
2011-06-05 04:05:21 -04:00
def delete_user_from_fav_string ( user_id )
2019-03-30 04:59:48 -04:00
self . fav_string = fav_string . gsub ( / (?: \ A| )fav: #{ user_id } (?: \ Z| ) / , " " ) . strip
clean_fav_string!
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2017-01-07 19:57:36 -05:00
# users who favorited this post, ordered by users who favorited it first
2013-04-14 12:56:35 -04:00
def favorited_users
2017-01-07 19:57:36 -05:00
favorited_user_ids = fav_string . scan ( / \ d+ / ) . map ( & :to_i )
visible_users = User . find ( favorited_user_ids ) . reject ( & :hide_favorites? )
ordered_users = visible_users . index_by ( & :id ) . slice ( * favorited_user_ids ) . values
ordered_users
2013-04-14 12:56:35 -04:00
end
2015-06-23 15:25:54 -04:00
2017-07-19 16:22:24 -04:00
def remove_from_favorites
2018-04-02 13:51:26 -04:00
Favorite . where ( post_id : id ) . delete_all
2017-09-13 17:47:42 -04:00
user_ids = fav_string . scan ( / \ d+ / )
2019-06-04 09:41:43 -04:00
UserStatus . where ( :user_id = > user_ids ) . update_all ( " favorite_count = favorite_count - 1 " )
2017-07-19 16:22:24 -04:00
end
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2010-02-10 16:12:30 -05:00
module UploaderMethods
2010-10-08 18:42:26 -04:00
def initialize_uploader
2011-07-16 19:20:02 -04:00
if uploader_id . blank?
self . uploader_id = CurrentUser . id
self . uploader_ip_addr = CurrentUser . ip_addr
end
2010-02-11 14:59:58 -05:00
end
2013-03-19 08:10:10 -04:00
2010-02-11 14:59:58 -05:00
def uploader_name
2019-10-12 19:37:16 -04:00
if association ( :uploader ) . loaded?
2020-01-30 07:16:17 -05:00
return uploader & . name || " Anonymous "
2019-10-12 19:37:16 -04:00
end
2017-04-30 02:29:21 -04:00
User . id_to_name ( uploader_id )
2010-02-10 16:12:30 -05:00
end
2010-02-11 14:59:58 -05:00
end
2013-03-19 08:10:10 -04:00
2019-03-20 08:14:08 -04:00
module SetMethods
2019-08-15 10:51:27 -04:00
def set_ids
pool_string . scan ( / set \ :( \ d+) / ) . map { | set | set [ 0 ] . to_i }
end
2019-03-20 08:14:08 -04:00
def post_sets
@post_sets || = begin
return PostSet . none if pool_string . blank?
2019-08-15 10:51:27 -04:00
PostSet . where ( id : set_ids )
2019-03-20 08:14:08 -04:00
end
end
2019-03-23 04:17:15 -04:00
def belongs_to_post_set ( set )
pool_string =~ / (?: \ A| )set: #{ set . id } (?: \ z| ) /
end
2019-03-20 08:14:08 -04:00
def add_set! ( set , force = false )
2019-03-23 04:17:15 -04:00
return if belongs_to_post_set ( set ) && ! force
2019-03-20 08:14:08 -04:00
with_lock do
self . pool_string = " #{ pool_string } set: #{ set . id } " . strip
end
end
def remove_set! ( set )
with_lock do
2019-03-23 04:17:15 -04:00
self . pool_string = ( pool_string . split ( ' ' ) - [ " set: #{ set . id } " ] ) . join ( ' ' ) . strip
2019-03-20 08:14:08 -04:00
end
end
2019-03-23 04:17:15 -04:00
def give_post_sets_to_parent
transaction do
post_sets . find_each do | set |
2020-04-19 06:03:44 -04:00
begin
set . remove ( [ id ] )
set . add ( [ parent . id ] ) if parent_id . present? && set . transfer_on_delete
set . save!
rescue
#Ignore set errors due to things like set post count
end
2019-03-23 04:17:15 -04:00
end
end
end
def remove_from_post_sets
post_sets . find_each do | set |
set . remove! ( self )
end
end
2019-03-20 08:14:08 -04:00
end
2010-02-11 14:59:58 -05:00
module PoolMethods
2019-08-15 10:51:27 -04:00
def pool_ids
pool_string . scan ( / pool \ :( \ d+) / ) . map { | pool | pool [ 0 ] . to_i }
end
2011-03-13 17:49:34 -04:00
def pools
@pools || = begin
2017-06-07 23:41:07 -04:00
return Pool . none if pool_string . blank?
2017-07-20 22:00:13 -04:00
Pool . where ( id : pool_ids ) . series_first
2011-03-13 17:49:34 -04:00
end
end
2013-03-19 08:10:10 -04:00
2017-06-07 23:02:51 -04:00
def has_active_pools?
2022-07-11 16:52:26 -04:00
pools . any?
2017-06-07 23:02:51 -04:00
end
2011-06-07 17:34:09 -04:00
def belongs_to_pool? ( pool )
pool_string =~ / (?: \ A| )pool: #{ pool . id } (?: \ Z| ) /
2010-02-11 14:59:58 -05:00
end
2013-03-19 08:10:10 -04:00
2022-07-11 16:52:26 -04:00
def add_pool! ( pool )
2011-06-07 17:34:09 -04:00
return if belongs_to_pool? ( pool )
2017-05-26 17:22:03 -04:00
with_lock do
self . pool_string = " #{ pool_string } pool: #{ pool . id } " . strip
end
2011-06-07 17:34:09 -04:00
end
2013-03-19 08:10:10 -04:00
2017-07-20 22:06:37 -04:00
def remove_pool! ( pool )
2011-06-07 17:34:09 -04:00
return unless belongs_to_pool? ( pool )
2015-10-22 22:25:02 -04:00
return unless CurrentUser . user . can_remove_from_pools?
2017-05-26 17:22:03 -04:00
with_lock do
self . pool_string = pool_string . gsub ( / (?: \ A| )pool: #{ pool . id } (?: \ Z| ) / , " " ) . strip
end
2010-02-10 16:33:00 -05:00
end
2013-05-21 21:02:35 -04:00
def remove_from_all_pools
pools . find_each do | pool |
pool . remove! ( self )
end
end
2010-02-10 16:12:30 -05:00
end
2013-03-19 08:10:10 -04:00
2010-02-15 17:45:09 -05:00
module VoteMethods
2019-04-10 23:08:36 -04:00
def own_vote ( user = CurrentUser . user )
return nil unless user
votes . where ( 'user_id = ?' , user . id ) . first
end
2010-02-15 17:45:09 -05:00
end
2013-03-19 08:10:10 -04:00
2010-02-24 16:00:52 -05:00
module CountMethods
2023-09-11 12:30:25 -04:00
def fast_count ( tags = " " , enable_safe_mode : CurrentUser . safe_mode? )
2017-11-29 13:26:32 -05:00
tags = tags . to_s
2023-09-11 12:30:25 -04:00
tags += " rating:s " if enable_safe_mode
2023-08-24 07:16:44 -04:00
tags += " -status:deleted " unless TagQuery . has_metatag? ( tags , " status " , " -status " )
tags = TagQuery . normalize ( tags )
2013-02-20 16:05:03 -05:00
2023-05-19 16:14:34 -04:00
cache_key = " pfc: #{ tags } "
2023-05-19 14:43:59 -04:00
count = Cache . fetch ( cache_key )
2017-11-16 16:32:20 -05:00
if count . nil?
2023-05-19 14:43:59 -04:00
count = Post . tag_match ( tags ) . count_only
expiry = count . seconds . clamp ( 3 . minutes , 20 . hours ) . to_i
2023-05-19 16:53:20 -04:00
Cache . write ( cache_key , count , expires_in : expiry )
2010-03-19 17:03:14 -04:00
end
2017-11-16 16:32:20 -05:00
count
2023-08-24 07:43:01 -04:00
rescue TagQuery :: CountExceededError
2012-02-08 12:44:17 -05:00
0
2010-02-24 16:00:52 -05:00
end
end
2010-08-18 18:42:33 -04:00
module ParentMethods
2013-03-19 08:10:10 -04:00
# A parent has many children. A child belongs to a parent.
2010-08-18 18:42:33 -04:00
# A parent cannot have a parent.
#
2013-04-08 13:44:43 -04:00
# After expunging a child:
2010-08-18 18:42:33 -04:00
# - Move favorites to parent.
2014-03-17 20:51:49 -04:00
# - Does the parent have any children?
2010-08-18 18:42:33 -04:00
# - Yes: Done.
# - No: Update parent's has_children flag to false.
#
2013-04-08 13:44:43 -04:00
# After expunging a parent:
2010-08-18 18:42:33 -04:00
# - Move favorites to the first child.
2014-03-17 20:51:49 -04:00
# - Reparent all children to the first child.
2013-03-19 08:10:10 -04:00
2017-07-20 23:11:43 -04:00
def update_has_children_flag
2018-04-02 13:51:26 -04:00
update ( has_children : children . exists? , has_active_children : children . undeleted . exists? )
2010-08-18 18:42:33 -04:00
end
2013-06-05 17:43:45 -04:00
def blank_out_nonexistent_parents
if parent_id . present? && parent . nil?
self . parent_id = nil
end
end
2013-09-09 20:36:43 -04:00
def remove_parent_loops
2013-10-20 20:13:03 -04:00
if parent . present? && parent . parent_id . present? && parent . parent_id == id
2013-09-09 20:36:43 -04:00
parent . parent_id = nil
parent . save
end
end
2010-08-18 18:42:33 -04:00
def update_parent_on_destroy
2017-07-20 23:11:43 -04:00
parent . update_has_children_flag if parent
2010-08-18 18:42:33 -04:00
end
2013-03-19 08:10:10 -04:00
2010-08-18 18:42:33 -04:00
def update_children_on_destroy
2017-07-20 22:53:53 -04:00
return unless children . present?
eldest = children [ 0 ]
siblings = children [ 1 .. - 1 ]
eldest . update ( parent_id : nil )
2019-03-23 04:17:15 -04:00
Post . where ( id : siblings ) . find_each { | p | p . update ( parent_id : eldest . id ) }
2017-07-20 22:53:53 -04:00
# Post.where(id: siblings).update(parent_id: eldest.id) # XXX rails 5
2010-08-18 18:42:33 -04:00
end
def update_parent_on_save
2018-04-02 13:51:26 -04:00
return unless saved_change_to_parent_id? || saved_change_to_is_deleted?
2017-07-20 23:11:43 -04:00
parent . update_has_children_flag if parent . present?
2018-04-02 13:51:26 -04:00
Post . find ( parent_id_before_last_save ) . update_has_children_flag if parent_id_before_last_save . present?
2010-08-18 18:42:33 -04:00
end
2013-03-19 08:10:10 -04:00
2022-01-06 07:44:30 -05:00
def give_favorites_to_parent
TransferFavoritesJob . perform_later ( id , CurrentUser . id )
2019-03-31 16:25:55 -04:00
end
2022-01-06 07:44:30 -05:00
def give_favorites_to_parent!
2010-08-18 18:42:33 -04:00
return if parent . nil?
2019-03-31 16:25:55 -04:00
FavoriteManager . give_to_parent! ( self )
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , :favorites_moved , { parent_id : parent_id } )
PostEvent . add ( parent_id , CurrentUser . user , :favorites_received , { child_id : id } )
2010-08-18 18:42:33 -04:00
end
2013-04-30 20:16:16 -04:00
2013-06-05 18:04:31 -04:00
def parent_exists?
Post . exists? ( parent_id )
end
2014-05-29 22:55:35 -04:00
def has_visible_children?
return true if has_active_children?
2019-02-15 06:43:10 -05:00
return true if has_children? && CurrentUser . is_approver?
2014-05-29 22:55:35 -04:00
return true if has_children? && is_deleted?
return false
end
2014-10-14 12:48:54 -04:00
def has_visible_children
has_visible_children?
end
2017-01-09 05:53:58 -05:00
2022-04-29 16:28:53 -04:00
def inject_children ( ids )
@children_ids = ids . map ( & :id ) . join ( ' ' )
end
2017-01-09 05:53:58 -05:00
def children_ids
if has_children?
2022-04-29 16:28:53 -04:00
@children_ids || = children . map { | p | p . id } . join ( ' ' )
2017-01-09 05:53:58 -05:00
end
end
2010-08-18 18:42:33 -04:00
end
2013-03-19 08:10:10 -04:00
2011-02-12 17:37:48 -05:00
module DeletionMethods
2024-07-13 20:05:50 -04:00
def backup_post_data_destroy ( reason : " " )
2020-11-13 03:35:33 -05:00
post_data = {
id : id ,
2024-10-30 19:52:53 -04:00
title : title ,
2020-11-13 03:35:33 -05:00
description : description ,
2024-11-10 14:58:15 -05:00
transcript : transcript ,
2020-11-13 03:35:33 -05:00
md5 : md5 ,
tags : tag_string ,
height : image_height ,
width : image_width ,
file_size : file_size ,
sources : source ,
approver_id : approver_id ,
locked_tags : locked_tags ,
rating : rating ,
parent_id : parent_id ,
change_seq : change_seq ,
is_deleted : is_deleted ,
is_pending : is_pending ,
duration : duration ,
fav_count : fav_count ,
comment_count : comment_count
}
DestroyedPost . create! ( post_id : id , post_data : post_data , md5 : md5 ,
uploader_ip_addr : uploader_ip_addr , uploader_id : uploader_id ,
destroyer_id : CurrentUser . id , destroyer_ip_addr : CurrentUser . ip_addr ,
2024-07-13 20:05:50 -04:00
upload_date : created_at , reason : reason || " " )
2020-11-13 03:35:33 -05:00
end
2024-07-13 20:05:50 -04:00
def expunge! ( reason : " " )
2011-10-30 02:00:33 -04:00
if is_status_locked?
self . errors . add ( :is_status_locked , " ; cannot delete post " )
return false
end
2013-03-19 08:10:10 -04:00
2020-11-13 03:35:33 -05:00
transaction do
2024-07-13 20:05:50 -04:00
backup_post_data_destroy ( reason : reason )
2020-11-13 03:35:33 -05:00
end
2017-07-21 01:10:07 -04:00
transaction do
Post . without_timeout do
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , :expunged )
2017-07-21 01:10:07 -04:00
update_children_on_destroy
decrement_tag_post_counts
remove_from_all_pools
2019-03-23 03:57:37 -04:00
remove_from_post_sets
2017-07-21 01:10:07 -04:00
remove_from_favorites
destroy
update_parent_on_destroy
end
2015-12-07 17:39:46 -05:00
end
2011-10-15 23:07:50 -04:00
end
2013-03-19 08:10:10 -04:00
2019-02-12 06:00:57 -05:00
def protect_file?
2019-02-14 04:44:54 -05:00
is_deleted?
2013-04-16 23:28:42 -04:00
end
2017-05-17 23:48:01 -04:00
def delete! ( reason , options = { } )
2019-02-23 11:45:10 -05:00
if is_status_locked? && ! options . fetch ( :force , false )
2011-10-30 02:00:33 -04:00
self . errors . add ( :is_status_locked , " ; cannot delete post " )
return false
end
2013-03-19 08:10:10 -04:00
2019-08-01 19:03:13 -04:00
if reason . blank?
2023-01-27 16:53:04 -05:00
if pending_flag . blank?
errors . add ( :base , " Cannot delete with given reason when no active flag exists. " )
return
2019-08-01 19:03:13 -04:00
end
2023-01-27 16:53:04 -05:00
if pending_flag . reason =~ / uploading_guidelines /
errors . add ( :base , " Cannot delete with given reason when the flag is for uploading guidelines. " )
return
end
reason = pending_flag . reason
2019-08-01 19:03:13 -04:00
end
2020-11-05 17:03:26 -05:00
force_flag = options . fetch ( :force , false )
2020-04-19 22:39:10 -04:00
Post . with_timeout ( 30_000 ) do
transaction do
2020-11-05 17:03:26 -05:00
flag = flags . create ( reason : reason , reason_name : 'deletion' , is_resolved : false , is_deletion : true , force_flag : force_flag )
2019-08-02 18:39:28 -04:00
2020-04-19 22:39:10 -04:00
if flag . errors . any?
raise PostFlag :: Error . new ( flag . errors . full_messages . join ( " ; " ) )
end
2013-03-19 08:10:10 -04:00
2020-04-19 22:39:10 -04:00
update (
2022-07-10 14:34:00 -04:00
is_deleted : true ,
is_pending : false ,
is_flagged : false
2020-04-19 22:39:10 -04:00
)
2022-07-10 14:34:00 -04:00
decrement_tag_post_counts
2020-04-19 22:39:10 -04:00
move_files_on_delete
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , :deleted , { reason : reason } )
2013-02-28 12:56:10 -05:00
end
2010-08-18 18:42:33 -04:00
end
2020-04-19 22:39:10 -04:00
# XXX This must happen *after* the `is_deleted` flag is set to true (issue #3419).
# We don't care if these fail per-se so they are outside the transaction.
UserStatus . for_user ( uploader_id ) . update_all ( " post_deleted_count = post_deleted_count + 1 " )
2022-01-06 07:44:30 -05:00
give_favorites_to_parent if options [ :move_favorites ]
2020-04-19 22:39:10 -04:00
give_post_sets_to_parent if options [ :move_favorites ]
2020-06-11 20:55:37 -04:00
reject_pending_replacements
end
def reject_pending_replacements
replacements . where ( status : 'pending' ) . update_all ( status : 'rejected' )
2010-08-18 18:42:33 -04:00
end
2013-03-19 08:10:10 -04:00
2019-02-27 06:47:23 -05:00
def undelete! ( options = { } )
if is_status_locked? && ! options . fetch ( :force , false )
2023-02-26 07:43:09 -05:00
errors . add ( :is_status_locked , " ; cannot undelete post " )
return
2011-10-30 02:00:33 -04:00
end
2013-03-19 08:10:10 -04:00
2023-02-26 07:43:09 -05:00
if ! CurrentUser . is_admin? && uploader_id == CurrentUser . id
raise User :: PrivilegeError , " You cannot undelete a post you uploaded "
end
if ! is_deleted
errors . add ( :base , " Post is not deleted " )
return
2015-07-24 17:19:40 -04:00
end
2020-04-19 22:39:10 -04:00
transaction do
self . is_deleted = false
2023-02-26 07:43:09 -05:00
self . is_pending = false
2020-04-19 22:39:10 -04:00
self . approver_id = CurrentUser . id
2022-07-10 14:34:00 -04:00
flags . each { | x | x . resolve! }
increment_tag_post_counts
2020-04-19 22:39:10 -04:00
save
approvals . create ( user : CurrentUser . user )
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , :undeleted )
2019-02-27 06:47:23 -05:00
end
2020-04-19 22:39:10 -04:00
move_files_on_undelete
UserStatus . for_user ( uploader_id ) . update_all ( " post_deleted_count = post_deleted_count - 1 " )
2010-10-08 18:42:26 -04:00
end
2022-07-10 14:45:52 -04:00
def deletion_flag
flags . order ( id : :desc ) . first
end
2023-01-27 16:53:04 -05:00
def pending_flag
flags . unresolved . order ( id : :desc ) . first
end
2010-08-18 18:42:33 -04:00
end
2013-03-19 08:10:10 -04:00
2011-01-26 18:10:49 -05:00
module VersionMethods
2013-05-31 18:15:24 -04:00
def create_version ( force = false )
2019-08-13 12:40:53 -04:00
return if do_not_version_changes == true
2018-04-02 13:51:26 -04:00
if new_record? || saved_change_to_watched_attributes? || force
2016-06-29 17:47:32 -04:00
create_new_version
2011-01-26 18:10:49 -05:00
end
end
2013-03-19 08:10:10 -04:00
2018-04-02 13:51:26 -04:00
def saved_change_to_watched_attributes?
2024-11-10 14:58:15 -05:00
saved_change_to_rating? || saved_change_to_source? || saved_change_to_parent_id? || saved_change_to_tag_string? || saved_change_to_locked_tags? || saved_change_to_title? || saved_change_to_description? || saved_change_to_transcript?
2018-04-02 13:51:26 -04:00
end
2013-07-25 17:12:35 -04:00
def create_new_version
2019-10-31 16:22:27 -04:00
# This function name is misleading, this directly creates the version.
# Previously there was a queue involved, now there isn't.
2022-08-06 12:58:24 -04:00
PostVersion . queue ( self )
2013-07-25 17:12:35 -04:00
end
2011-01-26 18:26:32 -05:00
def revert_to ( target )
2016-10-10 06:24:49 -04:00
if id != target . post_id
raise RevertError . new ( " You cannot revert to a previous version of another post. " )
end
2011-10-22 21:34:32 -04:00
self . tag_string = target . tags
self . rating = target . rating
self . source = target . source
self . parent_id = target . parent_id
2024-10-30 19:52:53 -04:00
self . title = target . title
2019-07-14 09:24:52 -04:00
self . description = target . description
2024-11-10 14:58:15 -05:00
self . transcript = target . transcript
2023-08-01 11:24:14 -04:00
self . edit_reason = " Revert to version #{ target . version } "
2011-01-26 18:26:32 -05:00
end
2013-03-19 08:10:10 -04:00
2011-01-26 18:26:32 -05:00
def revert_to! ( target )
revert_to ( target )
save!
end
2011-01-26 18:10:49 -05:00
end
2013-03-19 08:10:10 -04:00
2011-07-02 21:04:29 -04:00
module NoteMethods
2013-08-24 13:38:05 -04:00
def has_notes?
last_noted_at . present?
end
2018-03-31 13:58:56 -04:00
def copy_notes_to ( other_post , copy_tags : NOTE_COPY_TAGS )
2018-03-31 12:44:49 -04:00
transaction do
if id == other_post . id
errors . add :base , " Source and destination posts are the same "
return false
end
unless has_notes?
errors . add :post , " has no notes "
return false
end
2013-08-09 18:42:52 -04:00
2018-03-31 12:44:49 -04:00
notes . active . each do | note |
note . copy_to ( other_post )
end
2013-08-09 18:42:52 -04:00
2018-03-31 12:44:49 -04:00
dummy = Note . new
if notes . active . length == 1
dummy . body = " Copied 1 note from post # #{ id } . "
else
dummy . body = " Copied #{ notes . active . length } notes from post # #{ id } . "
end
dummy . is_active = false
dummy . post_id = other_post . id
dummy . x = dummy . y = dummy . width = dummy . height = 0
dummy . save
2018-03-31 13:58:56 -04:00
copy_tags . each do | tag |
other_post . remove_tag ( tag )
other_post . add_tag ( tag ) if has_tag? ( tag )
end
other_post . save
2013-08-09 18:42:52 -04:00
end
2013-05-18 16:25:46 -04:00
end
2011-07-02 21:04:29 -04:00
end
2013-03-19 08:10:10 -04:00
2011-10-22 01:56:36 -04:00
module ApiMethods
def hidden_attributes
2023-02-26 10:41:02 -05:00
list = super + [ :pool_string , :fav_string ]
2014-05-25 14:50:07 -04:00
if ! visible?
2014-02-13 20:08:37 -05:00
list += [ :md5 , :file_ext ]
end
super + list
end
def method_attributes
2023-08-29 10:56:10 -04:00
list = super + [ :has_large , :has_visible_children , :children_ids , :pool_ids , :is_favorited? ]
2014-05-25 14:50:07 -04:00
if visible?
2014-02-13 20:08:37 -05:00
list += [ :file_url , :large_file_url , :preview_file_url ]
end
list
2011-10-22 01:56:36 -04:00
end
2013-03-19 08:10:10 -04:00
2024-07-26 16:12:54 -04:00
def thumbnail_attributes
attributes = {
id : id ,
flags : status_flags ,
tags : tag_string ,
rating : rating ,
file_ext : file_ext ,
width : image_width ,
height : image_height ,
size : file_size ,
created_at : created_at ,
uploader : uploader_name ,
uploader_id : uploader_id ,
score : score ,
fav_count : fav_count ,
is_favorited : favorited_by? ( CurrentUser . user . id ) ,
pools : pool_ids ,
2019-04-30 07:53:31 -04:00
}
if visible?
2024-07-26 16:12:54 -04:00
attributes [ :md5 ] = md5
attributes [ :preview_url ] = preview_file_url
attributes [ :large_url ] = large_file_url
attributes [ :file_url ] = file_url
attributes [ :preview_width ] = preview_dimensions [ 1 ]
attributes [ :preview_height ] = preview_dimensions [ 0 ]
2025-01-23 17:24:39 -05:00
attributes [ :large_width ] = large_image_width
attributes [ :large_height ] = large_image_height
2019-04-30 07:53:31 -04:00
end
2024-07-26 16:12:54 -04:00
attributes
2019-04-30 07:53:31 -04:00
end
2011-12-22 12:39:27 -05:00
def status
if is_pending?
" pending "
elsif is_deleted?
" deleted "
elsif is_flagged?
" flagged "
else
" active "
end
end
2011-10-22 01:56:36 -04:00
end
2013-03-19 08:10:10 -04:00
2013-01-10 17:45:52 -05:00
module SearchMethods
2017-01-18 17:12:12 -05:00
# returns one single post
def random
key = Digest :: MD5 . hexdigest ( Time . now . to_f . to_s )
random_up ( key ) || random_down ( key )
end
def random_up ( key )
where ( " md5 < ? " , key ) . reorder ( " md5 desc " ) . first
end
def random_down ( key )
where ( " md5 >= ? " , key ) . reorder ( " md5 asc " ) . first
end
2017-05-26 13:25:44 -04:00
def sample ( query , sample_size )
2023-09-16 09:30:28 -04:00
tag_match_system ( " #{ query } order:random " , free_tags_count : 1 ) . limit ( sample_size ) . relation
2017-05-26 13:25:44 -04:00
end
# unflattens the tag_string into one tag per row.
def with_unflattened_tags
joins ( " CROSS JOIN unnest(string_to_array(tag_string, ' ')) AS tag " )
end
2013-01-10 17:45:52 -05:00
def pending
2018-08-25 12:26:00 -04:00
where ( is_pending : true )
2013-01-10 17:45:52 -05:00
end
2013-03-19 08:10:10 -04:00
2013-02-22 21:13:11 -05:00
def flagged
2018-08-25 12:26:00 -04:00
where ( is_flagged : true )
2013-02-22 21:13:11 -05:00
end
2013-03-19 08:10:10 -04:00
2013-01-10 17:45:52 -05:00
def pending_or_flagged
2018-08-25 12:26:00 -04:00
pending . or ( flagged )
2013-01-10 17:45:52 -05:00
end
2013-03-19 08:10:10 -04:00
2013-01-10 17:45:52 -05:00
def undeleted
where ( " is_deleted = ? " , false )
end
2013-03-19 08:10:10 -04:00
2013-01-10 17:45:52 -05:00
def deleted
where ( " is_deleted = ? " , true )
end
2013-03-19 08:10:10 -04:00
2013-01-10 17:45:52 -05:00
def has_notes
where ( " last_noted_at is not null " )
end
2013-03-19 08:10:10 -04:00
2013-01-10 17:45:52 -05:00
def for_user ( user_id )
where ( " uploader_id = ? " , user_id )
end
2013-03-19 08:10:10 -04:00
2019-03-28 17:43:02 -04:00
def sql_raw_tag_match ( tag )
2023-02-21 14:36:06 -05:00
where ( " string_to_array(posts.tag_string, ' ') @> ARRAY[?] " , tag )
2019-03-28 17:43:02 -04:00
end
2023-09-11 12:30:25 -04:00
def tag_match_system ( query , free_tags_count : 0 )
tag_match ( query , free_tags_count : free_tags_count , enable_safe_mode : false , always_show_deleted : true )
end
def tag_match ( query , resolve_aliases : true , free_tags_count : 0 , enable_safe_mode : CurrentUser . safe_mode? , always_show_deleted : false )
ElasticPostQueryBuilder . new (
query ,
resolve_aliases : resolve_aliases ,
free_tags_count : free_tags_count ,
enable_safe_mode : enable_safe_mode ,
always_show_deleted : always_show_deleted ,
) . search
2013-01-10 17:45:52 -05:00
end
2023-02-21 16:50:17 -05:00
def tag_match_sql ( query )
2023-09-05 10:03:13 -04:00
PostQueryBuilder . new ( query ) . search
2023-02-21 16:50:17 -05:00
end
2013-01-10 17:45:52 -05:00
end
2019-02-27 06:47:23 -05:00
2014-03-21 19:43:02 -04:00
module IqdbMethods
2014-03-24 17:38:06 -04:00
extend ActiveSupport :: Concern
module ClassMethods
def remove_iqdb ( post_id )
2024-04-26 09:57:56 -04:00
if IqdbProxy . enabled?
2023-05-01 12:19:26 -04:00
IqdbRemoveJob . perform_later ( post_id )
2016-11-28 20:14:25 -05:00
end
2014-03-24 17:38:06 -04:00
end
end
def update_iqdb_async
2024-04-26 09:57:56 -04:00
if IqdbProxy . enabled? && has_preview?
2023-05-01 12:19:26 -04:00
IqdbUpdateJob . perform_later ( id )
2014-03-21 19:43:02 -04:00
end
end
2014-03-24 17:38:06 -04:00
def remove_iqdb_async
2017-06-14 21:07:38 -04:00
Post . remove_iqdb ( id )
2014-03-21 19:43:02 -04:00
end
end
2017-02-08 23:46:43 -05:00
2022-01-06 07:54:40 -05:00
module PostEventMethods
2024-06-28 12:26:01 -04:00
def create_post_events
2022-01-06 07:54:40 -05:00
if saved_change_to_is_rating_locked?
action = is_rating_locked? ? :rating_locked : :rating_unlocked
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , action )
2022-01-06 07:54:40 -05:00
end
if saved_change_to_is_status_locked?
action = is_status_locked? ? :status_locked : :status_unlocked
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , action )
2022-01-06 07:54:40 -05:00
end
if saved_change_to_is_note_locked?
action = is_note_locked? ? :note_locked : :note_unlocked
2022-01-08 19:47:06 -05:00
PostEvent . add ( id , CurrentUser . user , action )
2022-01-06 07:54:40 -05:00
end
2023-09-05 11:27:05 -04:00
if saved_change_to_is_comment_locked?
action = is_comment_locked? ? :comment_locked : :comment_unlocked
2022-05-16 08:20:11 -04:00
PostEvent . add ( id , CurrentUser . user , action )
end
2024-10-28 16:56:22 -04:00
if saved_change_to_is_comment_disabled?
action = is_comment_disabled? ? :comment_disabled : :comment_enabled
PostEvent . add ( id , CurrentUser . user , action )
end
2024-06-28 12:26:01 -04:00
if saved_change_to_bg_color?
PostEvent . add ( id , CurrentUser . user , :changed_bg_color , { bg_color : bg_color } )
end
2019-04-17 22:13:26 -04:00
end
end
2017-02-08 23:46:43 -05:00
module ValidationMethods
2019-12-31 13:19:11 -05:00
def fix_bg_color
if bg_color . blank?
self . bg_color = nil
end
end
2017-02-08 23:46:43 -05:00
def post_is_not_its_own_parent
if ! new_record? && id == parent_id
2021-02-07 20:16:52 -05:00
errors . add ( :base , " Post cannot have itself as a parent " )
2017-02-08 23:46:43 -05:00
false
end
end
def updater_can_change_rating
if rating_changed? && is_rating_locked?
# Don't forbid changes if the rating lock was just now set in the same update.
if ! is_rating_locked_changed?
errors . add ( :rating , " is locked and cannot be changed. Unlock the post first. " )
end
end
end
2017-02-08 23:48:48 -05:00
2017-11-08 20:33:02 -05:00
def added_tags_are_valid
2019-10-12 00:41:17 -04:00
# Load this only once since it isn't cached
added = added_tags
2023-04-10 12:24:26 -04:00
added_invalid_tags = added . select { | t | t . category == Tag . categories . invalid }
new_tags = added . select { | t | t . post_count < = 0 }
new_general_tags = new_tags . select { | t | t . category == Tag . categories . general }
new_artist_tags = new_tags . select { | t | t . category == Tag . categories . artist }
# See https://github.com/e621ng/e621ng/issues/494
# If the tag is fresh it's save to assume it was created with a prefix
repopulated_tags = new_tags . select { | t | t . category != Tag . categories . general && t . category != Tag . categories . meta && t . created_at < 10 . seconds . ago }
2019-09-22 19:08:12 -04:00
if added_invalid_tags . present?
n = added_invalid_tags . size
2023-04-10 12:24:26 -04:00
tag_wiki_links = added_invalid_tags . map { | tag | " [[ #{ tag . name } ]] " }
warnings . add ( :base , " Added #{ n } invalid #{ 'tag' . pluralize ( n ) } . See the wiki page for each tag for help on resolving these: #{ tag_wiki_links . join ( ', ' ) } " )
2019-09-22 19:08:12 -04:00
end
2017-11-08 20:33:02 -05:00
if new_general_tags . present?
n = new_general_tags . size
2023-04-10 12:24:26 -04:00
tag_wiki_links = new_general_tags . map { | tag | " [[ #{ tag . name } ]] " }
warnings . add ( :base , " Created #{ n } new #{ 'tag' . pluralize ( n ) } : #{ tag_wiki_links . join ( ', ' ) } " )
2017-11-08 20:33:02 -05:00
end
2017-11-08 20:37:09 -05:00
2017-12-20 11:56:46 -05:00
if repopulated_tags . present?
n = repopulated_tags . size
2023-04-10 12:24:26 -04:00
tag_wiki_links = repopulated_tags . map { | tag | " [[ #{ tag . name } ]] " }
warnings . add ( :base , " Repopulated #{ n } old #{ 'tag' . pluralize ( n ) } : #{ tag_wiki_links . join ( ', ' ) } " )
2017-12-20 11:56:46 -05:00
end
2017-11-08 20:37:09 -05:00
new_artist_tags . each do | tag |
if tag . artist . blank?
2023-04-10 12:24:26 -04:00
warnings . add ( :base , " Artist [[ #{ tag . name } ]] requires an artist entry. \" Create new artist entry \" :[/artists/new?artist%5Bname%5D= #{ CGI . escape ( tag . name ) } ] " )
2017-11-08 20:37:09 -05:00
end
end
2017-11-08 20:33:02 -05:00
end
2017-11-25 14:45:17 -05:00
2017-11-25 18:02:16 -05:00
def removed_tags_are_valid
attempted_removed_tags = @removed_tags + @negated_tags
unremoved_tags = tag_array & attempted_removed_tags
if unremoved_tags . present?
2019-03-23 04:17:15 -04:00
unremoved_tags_list = unremoved_tags . map { | t | " [[ #{ t } ]] " } . to_sentence
2021-02-07 20:16:52 -05:00
self . warnings . add ( :base , " #{ unremoved_tags_list } could not be removed. Check for implications and locked tags and try again " )
2017-11-25 18:02:16 -05:00
end
2024-09-01 14:41:56 -04:00
@removed_tags = [ ]
2017-11-25 18:02:16 -05:00
end
2017-11-25 15:44:22 -05:00
def has_artist_tag
return if ! new_record?
2023-04-10 14:31:00 -04:00
return if tags . any? { | t | t . category == Tag . categories . artist }
2017-11-25 15:44:22 -05:00
2023-04-10 14:31:00 -04:00
self . warnings . add ( :base , 'Artist tag is required. "Click here":/help/tags#catchange if you need help changing the category of an tag. Ask on the forum if you need naming help' )
2017-11-25 15:44:22 -05:00
end
2017-12-27 16:04:00 -05:00
def has_enough_tags
return if ! new_record?
2019-03-23 04:17:15 -04:00
if tags . count { | t | t . category == Tag . categories . general } < 10
2024-11-06 22:45:47 -05:00
self . warnings . add ( :base , " Uploads must have at least 10 general tags. Read [[help:tags]] for guidelines on tagging your uploads " )
2017-12-27 16:04:00 -05:00
end
end
2017-02-08 23:46:43 -05:00
end
2019-02-27 06:47:23 -05:00
2022-05-08 12:42:49 -04:00
include PostFileMethods
2010-02-10 16:12:30 -05:00
include FileMethods
include ImageMethods
2010-08-18 18:42:33 -04:00
include ApprovalMethods
2019-04-26 06:34:40 -04:00
include SourceMethods
2010-02-10 16:12:30 -05:00
include PresenterMethods
include TagMethods
include FavoriteMethods
include UploaderMethods
2010-02-11 14:59:58 -05:00
include PoolMethods
2019-03-20 08:14:08 -04:00
include SetMethods
2010-02-15 17:45:09 -05:00
include VoteMethods
2010-02-24 16:00:52 -05:00
extend CountMethods
2010-08-18 18:42:33 -04:00
include ParentMethods
2011-02-12 17:37:48 -05:00
include DeletionMethods
2011-01-26 18:10:49 -05:00
include VersionMethods
2011-07-02 21:04:29 -04:00
include NoteMethods
2011-10-22 01:56:36 -04:00
include ApiMethods
2013-01-10 17:45:52 -05:00
extend SearchMethods
2014-03-24 17:38:06 -04:00
include IqdbMethods
2017-02-08 23:46:43 -05:00
include ValidationMethods
2022-01-06 07:54:40 -05:00
include PostEventMethods
2015-07-31 19:20:36 -04:00
include Danbooru :: HasBitFlags
2023-09-16 09:52:05 -04:00
include DocumentStore :: Model
2019-02-17 10:14:04 -05:00
include PostIndex
2013-03-19 08:10:10 -04:00
2015-07-31 19:20:36 -04:00
BOOLEAN_ATTRIBUTES = %w(
2023-04-08 14:18:00 -04:00
_has_embedded_notes
2017-09-08 18:01:12 -04:00
has_cropped
2019-04-17 22:44:12 -04:00
hide_from_anonymous
hide_from_search_engines
2015-07-31 19:20:36 -04:00
)
has_bit_flags BOOLEAN_ATTRIBUTES
2015-01-27 20:11:51 -05:00
2017-11-23 16:07:01 -05:00
def safeblocked?
2021-11-14 16:16:36 -05:00
return true if Danbooru . config . safe_mode? && rating != " s "
2023-09-03 12:24:40 -04:00
CurrentUser . safe_mode? && ( rating != " s " || has_tag? ( * Danbooru . config . safeblocked_tags ) )
2017-11-23 16:07:01 -05:00
end
2019-02-15 06:14:35 -05:00
def deleteblocked?
2017-11-23 16:07:01 -05:00
! Danbooru . config . can_user_see_post? ( CurrentUser . user , self )
end
2019-04-17 22:44:12 -04:00
def loginblocked?
2019-07-17 15:30:28 -04:00
CurrentUser . is_anonymous? && ( hide_from_anonymous? || Danbooru . config . user_needs_login_for_post? ( self ) )
2019-04-17 22:44:12 -04:00
end
2013-07-18 20:26:52 -04:00
def visible?
2019-04-17 22:44:12 -04:00
return false if loginblocked?
2017-11-23 16:07:01 -05:00
return false if safeblocked?
2019-02-15 06:14:35 -05:00
return false if deleteblocked?
2013-07-18 20:26:52 -04:00
return true
end
2019-08-07 04:55:51 -04:00
def allow_sample_resize?
2022-03-16 12:08:40 -04:00
! is_flash?
2019-08-07 04:55:51 -04:00
end
2022-03-16 12:08:40 -04:00
def force_original_size?
is_flash?
2019-08-07 04:55:51 -04:00
end
2010-02-11 23:15:31 -05:00
def reload ( options = nil )
super
reset_tag_array_cache
2019-02-02 21:03:47 -05:00
@locked_to_add = nil
@locked_to_remove = nil
2015-05-21 14:57:08 -04:00
@pools = nil
2019-03-23 03:57:37 -04:00
@post_sets = nil
2012-10-30 17:58:52 -04:00
@tag_categories = nil
@typed_tags = nil
self
2010-02-11 23:15:31 -05:00
end
2013-06-06 09:03:31 -04:00
2015-05-13 18:25:01 -04:00
def mark_as_translated ( params )
2021-06-22 06:24:48 -04:00
add_tag ( " translation_check " ) if params [ " translation_check " ] . to_s . truthy?
remove_tag ( " translation_check " ) if params [ " translation_check " ] . to_s . falsy?
2015-05-13 18:25:01 -04:00
2018-05-03 19:53:35 -04:00
add_tag ( " partially_translated " ) if params [ " partially_translated " ] . to_s . truthy?
remove_tag ( " partially_translated " ) if params [ " partially_translated " ] . to_s . falsy?
2015-05-13 18:25:01 -04:00
2023-09-03 12:24:40 -04:00
if has_tag? ( " translation_check " , " partially_translated " )
2018-05-03 19:53:35 -04:00
add_tag ( " translation_request " )
remove_tag ( " translated " )
2015-05-13 18:25:01 -04:00
else
2018-05-03 19:53:35 -04:00
add_tag ( " translated " )
remove_tag ( " translation_request " )
2015-05-13 18:25:01 -04:00
end
save
end
2022-09-17 08:04:11 -04:00
2024-08-03 17:15:26 -04:00
def artist_tags
tags . select { | t | t . category == Tag . categories . artist }
end
2022-09-17 08:04:11 -04:00
def uploader_linked_artists
2024-08-03 17:15:26 -04:00
artist_tags . filter_map ( & :artist ) . select { | artist | artist . linked_user_id == uploader_id }
2022-09-17 08:04:11 -04:00
end
2023-01-27 16:25:48 -05:00
def flaggable_for_guidelines?
2024-09-29 16:08:45 -04:00
! has_tag? ( " grandfathered_content " ) && created_at . after? ( " 2015-01-01 " )
2023-05-10 12:39:38 -04:00
end
2024-10-28 16:56:22 -04:00
def visible_comment_count ( user )
if user . is_moderator? || ! is_comment_disabled?
comment_count
else
comments . visible ( user ) . count
end
2023-01-27 16:25:48 -05:00
end
2024-08-03 17:15:26 -04:00
def avoid_posting_artists
AvoidPosting . active . joins ( :artist ) . where ( " artists.name " : artist_tags . map ( & :name ) )
end
2010-02-06 16:48:40 -05:00
end