1
|
class Post < ActiveRecord::Base
|
1
|
2
|
class ApprovalError < Exception ; end
|
1
|
3
|
|
|
4
|
attr_accessor :old_tag_string, :old_parent_id
|
1
|
5
|
after_destroy :delete_files
|
1
|
6
|
after_save :update_history
|
1
|
7
|
after_save :update_parent_on_save
|
1
|
8
|
before_save :merge_old_tags
|
1
|
9
|
before_save :normalize_tags
|
1
|
10
|
before_save :create_tags
|
1
|
11
|
before_save :update_tag_post_counts
|
1
|
12
|
before_save :set_tag_counts
|
1
|
13
|
before_validation :initialize_uploader, :on => :create
|
1
|
14
|
belongs_to :updater, :class_name => "User"
|
1
|
15
|
belongs_to :approver, :class_name => "User"
|
1
|
16
|
belongs_to :parent, :class_name => "Post"
|
1
|
17
|
has_one :unapproval, :dependent => :destroy
|
1
|
18
|
has_one :upload, :dependent => :destroy
|
1
|
19
|
has_one :moderation_detail, :class_name => "PostModerationDetail", :dependent => :destroy
|
1
|
20
|
has_one :history, :class_name => "PostHistory"
|
1
|
21
|
has_many :votes, :class_name => "PostVote", :dependent => :destroy
|
1
|
22
|
has_many :notes, :dependent => :destroy
|
1
|
23
|
has_many :comments
|
1
|
24
|
has_many :children, :class_name => "Post", :foreign_key => "parent_id", :order => "posts.id"
|
1
|
25
|
validates_uniqueness_of :md5
|
1
|
26
|
validates_presence_of :parent, :if => lambda {|rec| !rec.parent_id.nil?}
|
7
|
27
|
validate :validate_parent_does_not_have_a_parent
|
1
|
28
|
attr_accessible :source, :rating, :tag_string, :old_tag_string, :last_noted_at
|
1
|
29
|
scope :visible, lambda {|user| Danbooru.config.can_user_see_post_conditions(user)}
|
1
|
30
|
scope :commented_before, lambda {|date| where("last_commented_at < ?", date).order("last_commented_at DESC")}
|
1
|
31
|
|
|
32
|
module FileMethods
|
1
|
33
|
def delete_files
|
1
|
34
|
FileUtils.rm_f(file_path)
|
0
|
35
|
FileUtils.rm_f(medium_file_path)
|
0
|
36
|
FileUtils.rm_f(large_file_path)
|
0
|
37
|
FileUtils.rm_f(preview_file_path)
|
0
|
38
|
end
|
|
39
|
|
|
40
|
def file_path_prefix
|
1
|
41
|
Rails.env == "test" ? "test." : ""
|
0
|
42
|
end
|
|
43
|
|
|
44
|
def file_path
|
1
|
45
|
"#{Rails.root}/public/data/original/#{file_path_prefix}#{md5}.#{file_ext}"
|
0
|
46
|
end
|
|
47
|
|
|
48
|
def medium_file_path
|
1
|
49
|
if has_medium?
|
0
|
50
|
"#{Rails.root}/public/data/medium/#{file_path_prefix}#{md5}.jpg"
|
0
|
51
|
else
|
|
52
|
file_path
|
0
|
53
|
end
|
|
54
|
end
|
|
55
|
|
|
56
|
def large_file_path
|
1
|
57
|
if has_large?
|
0
|
58
|
"#{Rails.root}/public/data/large/#{file_path_prefix}#{md5}.jpg"
|
0
|
59
|
else
|
|
60
|
file_path
|
0
|
61
|
end
|
|
62
|
end
|
|
63
|
|
|
64
|
def preview_file_path
|
1
|
65
|
"#{Rails.root}/public/data/preview/#{file_path_prefix}#{md5}.jpg"
|
0
|
66
|
end
|
|
67
|
|
|
68
|
def file_url
|
1
|
69
|
"/data/original/#{file_path_prefix}#{md5}.#{file_ext}"
|
0
|
70
|
end
|
|
71
|
|
|
72
|
def medium_file_url
|
1
|
73
|
"/data/medium/#{file_path_prefix}#{md5}.jpg"
|
0
|
74
|
end
|
|
75
|
|
|
76
|
def large_file_url
|
1
|
77
|
"/data/large/#{file_path_prefix}#{md5}.jpg"
|
0
|
78
|
end
|
|
79
|
|
|
80
|
def preview_file_url
|
1
|
81
|
"/data/preview/#{file_path_prefix}#{md5}.jpg"
|
0
|
82
|
end
|
|
83
|
|
|
84
|
def file_url_for(user)
|
1
|
85
|
case user.default_image_size
|
0
|
86
|
when "medium"
|
|
87
|
if image_width > Danbooru.config.medium_image_width
|
0
|
88
|
medium_file_url
|
0
|
89
|
else
|
|
90
|
file_url
|
0
|
91
|
end
|
|
92
|
|
|
93
|
when "large"
|
|
94
|
if image_width > Danbooru.config.large_image_width
|
0
|
95
|
large_file_url
|
0
|
96
|
else
|
|
97
|
file_url
|
0
|
98
|
end
|
|
99
|
|
|
100
|
else
|
|
101
|
file_url
|
0
|
102
|
end
|
|
103
|
end
|
|
104
|
|
|
105
|
def file_path_for(user)
|
1
|
106
|
case user.default_image_size
|
0
|
107
|
when "medium"
|
|
108
|
if image_width > Danbooru.config.medium_image_width
|
0
|
109
|
medium_file_path
|
0
|
110
|
else
|
|
111
|
file_path
|
0
|
112
|
end
|
|
113
|
|
|
114
|
when "large"
|
|
115
|
if image_width > Danbooru.config.large_image_width
|
0
|
116
|
large_file_path
|
0
|
117
|
else
|
|
118
|
file_path
|
0
|
119
|
end
|
|
120
|
|
|
121
|
else
|
|
122
|
file_path
|
0
|
123
|
end
|
|
124
|
end
|
|
125
|
|
|
126
|
def is_image?
|
1
|
127
|
file_ext =~ /jpg|gif|png/
|
0
|
128
|
end
|
|
129
|
|
|
130
|
def is_flash?
|
1
|
131
|
file_ext =~ /swf/
|
0
|
132
|
end
|
|
133
|
end
|
|
134
|
|
|
135
|
module ImageMethods
|
1
|
136
|
def has_medium?
|
1
|
137
|
image_width > Danbooru.config.medium_image_width
|
0
|
138
|
end
|
|
139
|
|
|
140
|
def has_large?
|
1
|
141
|
image_width > Danbooru.config.large_image_width
|
0
|
142
|
end
|
|
143
|
|
|
144
|
def medium_image_width
|
1
|
145
|
[Danbooru.config.medium_image_width, image_width].min
|
0
|
146
|
end
|
|
147
|
|
|
148
|
def large_image_width
|
1
|
149
|
[Danbooru.config.large_image_width, image_width].min
|
0
|
150
|
end
|
|
151
|
|
|
152
|
def medium_image_height
|
1
|
153
|
ratio = Danbooru.config.medium_image_width.to_f / image_width.to_f
|
0
|
154
|
if ratio < 1
|
0
|
155
|
(image_height * ratio).to_i
|
0
|
156
|
else
|
|
157
|
image_height
|
0
|
158
|
end
|
|
159
|
end
|
|
160
|
|
|
161
|
def large_image_height
|
1
|
162
|
ratio = Danbooru.config.large_image_width.to_f / image_width.to_f
|
0
|
163
|
if ratio < 1
|
0
|
164
|
(image_height * ratio).to_i
|
0
|
165
|
else
|
|
166
|
image_height
|
0
|
167
|
end
|
|
168
|
end
|
|
169
|
|
|
170
|
def image_width_for(user)
|
1
|
171
|
case user.default_image_size
|
0
|
172
|
when "medium"
|
|
173
|
medium_image_width
|
0
|
174
|
|
|
175
|
when "large"
|
|
176
|
large_image_width
|
0
|
177
|
|
|
178
|
else
|
|
179
|
image_width
|
0
|
180
|
end
|
|
181
|
end
|
|
182
|
|
|
183
|
def image_height_for(user)
|
1
|
184
|
case user.default_image_size
|
0
|
185
|
when "medium"
|
|
186
|
medium_image_height
|
0
|
187
|
|
|
188
|
when "large"
|
|
189
|
large_image_height
|
0
|
190
|
|
|
191
|
else
|
|
192
|
image_height
|
0
|
193
|
end
|
|
194
|
end
|
|
195
|
end
|
|
196
|
|
|
197
|
module ApprovalMethods
|
1
|
198
|
def unapprove!(reason)
|
1
|
199
|
raise Unapproval::Error.new("This post has already been flagged") if is_flagged?
|
0
|
200
|
raise Unapproval::Error.new("This post has already been unapproved once") unless unapproval.nil?
|
0
|
201
|
|
|
202
|
unapproval = create_unapproval(
|
0
|
203
|
:unapprover_id => CurrentUser.user.id,
|
|
204
|
:unapprover_ip_addr => CurrentUser.ip_addr,
|
|
205
|
:reason => reason
|
|
206
|
)
|
|
207
|
|
|
208
|
if unapproval.errors.any?
|
0
|
209
|
raise Unapproval::Error.new(unapproval.errors.full_messages.join("; "))
|
0
|
210
|
end
|
|
211
|
|
|
212
|
update_attribute(:is_flagged, true)
|
0
|
213
|
end
|
|
214
|
|
|
215
|
def approve!
|
1
|
216
|
raise ApprovalError.new("You have already approved this post previously") if approver_string == "approver:#{CurrentUser.name}"
|
0
|
217
|
|
|
218
|
self.is_flagged = false
|
0
|
219
|
self.is_pending = false
|
0
|
220
|
self.approver_string = "approver:#{CurrentUser.name}"
|
0
|
221
|
save!
|
0
|
222
|
end
|
|
223
|
end
|
|
224
|
|
|
225
|
module PresenterMethods
|
1
|
226
|
def pretty_rating
|
1
|
227
|
case rating
|
0
|
228
|
when "q"
|
|
229
|
"Questionable"
|
0
|
230
|
|
|
231
|
when "e"
|
|
232
|
"Explicit"
|
0
|
233
|
|
|
234
|
when "s"
|
|
235
|
"Safe"
|
0
|
236
|
end
|
|
237
|
end
|
|
238
|
end
|
|
239
|
|
|
240
|
module HistoryMethods
|
1
|
241
|
def revisions
|
1
|
242
|
if history.nil?
|
0
|
243
|
update_history
|
0
|
244
|
end
|
|
245
|
|
|
246
|
history.revisions
|
0
|
247
|
end
|
|
248
|
|
|
249
|
def update_history
|
1
|
250
|
if history.nil?
|
6
|
251
|
create_history
|
6
|
252
|
end
|
|
253
|
|
|
254
|
history << self
|
6
|
255
|
end
|
|
256
|
end
|
|
257
|
|
|
258
|
module TagMethods
|
1
|
259
|
def tag_array
|
1
|
260
|
@tag_array ||= Tag.scan_tags(tag_string)
|
24
|
261
|
end
|
|
262
|
|
|
263
|
def tag_array_was
|
1
|
264
|
@tag_array_was ||= Tag.scan_tags(tag_string_was)
|
12
|
265
|
end
|
|
266
|
|
|
267
|
def create_tags
|
1
|
268
|
set_tag_string(tag_array.map {|x| Tag.find_or_create_by_name(x).name}.join(" "))
|
18
|
269
|
end
|
|
270
|
|
|
271
|
def increment_tag_post_counts
|
1
|
272
|
execute_sql("UPDATE tags SET post_count = post_count + 1 WHERE name IN (?)", tag_array) if tag_array.any?
|
0
|
273
|
end
|
|
274
|
|
|
275
|
def decrement_tag_post_counts
|
1
|
276
|
execute_sql("UPDATE tags SET post_count = post_count - 1 WHERE name IN (?)", tag_array) if tag_array.any?
|
0
|
277
|
end
|
|
278
|
|
|
279
|
def update_tag_post_counts
|
1
|
280
|
decrement_tags = tag_array_was - tag_array
|
6
|
281
|
increment_tags = tag_array - tag_array_was
|
6
|
282
|
execute_sql("UPDATE tags SET post_count = post_count - 1 WHERE name IN (?)", decrement_tags) if decrement_tags.any?
|
6
|
283
|
execute_sql("UPDATE tags SET post_count = post_count + 1 WHERE name IN (?)", increment_tags) if increment_tags.any?
|
6
|
284
|
decrement_tags.each do |tag|
|
6
|
285
|
expire_cache(tag)
|
0
|
286
|
end
|
|
287
|
increment_tags.each do |tag|
|
6
|
288
|
expire_cache(tag)
|
12
|
289
|
end
|
|
290
|
expire_cache("")
|
6
|
291
|
end
|
|
292
|
|
|
293
|
def set_tag_counts
|
1
|
294
|
self.tag_count = 0
|
6
|
295
|
self.tag_count_general = 0
|
6
|
296
|
self.tag_count_artist = 0
|
6
|
297
|
self.tag_count_copyright = 0
|
6
|
298
|
self.tag_count_character = 0
|
6
|
299
|
|
|
300
|
categories = Tag.categories_for(tag_array)
|
6
|
301
|
categories.each_value do |category|
|
6
|
302
|
self.tag_count += 1
|
12
|
303
|
|
|
304
|
case category
|
12
|
305
|
when Tag.categories.general
|
|
306
|
self.tag_count_general += 1
|
12
|
307
|
|
|
308
|
when Tag.categories.artist
|
|
309
|
self.tag_count_artist += 1
|
0
|
310
|
|
|
311
|
when Tag.categories.copyright
|
|
312
|
self.tag_count_copyright += 1
|
0
|
313
|
|
|
314
|
when Tag.categories.character
|
|
315
|
self.tag_count_character += 1
|
0
|
316
|
end
|
|
317
|
end
|
|
318
|
end
|
|
319
|
|
|
320
|
def merge_old_tags
|
1
|
321
|
if old_tag_string
|
6
|
322
|
# If someone else committed changes to this post before we did,
|
|
323
|
# then try to merge the tag changes together.
|
|
324
|
current_tags = tag_array_was()
|
0
|
325
|
new_tags = tag_array()
|
0
|
326
|
old_tags = Tag.scan_tags(old_tag_string)
|
0
|
327
|
set_tag_string(((current_tags + new_tags) - old_tags + (current_tags & new_tags)).uniq.join(" "))
|
0
|
328
|
end
|
|
329
|
end
|
|
330
|
|
|
331
|
def reset_tag_array_cache
|
1
|
332
|
@tag_array = nil
|
12
|
333
|
@tag_array_was = nil
|
12
|
334
|
end
|
|
335
|
|
|
336
|
def set_tag_string(string)
|
1
|
337
|
self.tag_string = string
|
12
|
338
|
reset_tag_array_cache
|
12
|
339
|
end
|
|
340
|
|
|
341
|
def normalize_tags
|
1
|
342
|
normalized_tags = Tag.scan_tags(tag_string)
|
6
|
343
|
normalized_tags = TagAlias.to_aliased(normalized_tags)
|
6
|
344
|
normalized_tags = TagImplication.with_descendants(normalized_tags)
|
6
|
345
|
normalized_tags = filter_metatags(normalized_tags)
|
6
|
346
|
set_tag_string(normalized_tags.uniq.join(" "))
|
6
|
347
|
end
|
|
348
|
|
|
349
|
def filter_metatags(tags)
|
1
|
350
|
tags.reject {|tag| tag =~ /\A(?:pool|rating|fav|approver|uploader):/}
|
30
|
351
|
end
|
|
352
|
|
|
353
|
def has_tag?(tag)
|
1
|
354
|
tag_string =~ /(?:^| )#{tag}(?:$| )/
|
0
|
355
|
end
|
|
356
|
end
|
|
357
|
|
|
358
|
module FavoriteMethods
|
1
|
359
|
def delete_favorites
|
1
|
360
|
Favorite.destroy_for_post(self)
|
0
|
361
|
end
|
|
362
|
|
|
363
|
def add_favorite(user)
|
1
|
364
|
if user.is_a?(ActiveRecord::Base)
|
0
|
365
|
user_id = user.id
|
0
|
366
|
else
|
|
367
|
user_id = user
|
0
|
368
|
end
|
|
369
|
|
|
370
|
return false if fav_string =~ /(?:\A| )fav:#{user_id}(?:\Z| )/
|
0
|
371
|
self.fav_string += " fav:#{user_id}"
|
0
|
372
|
self.fav_string.strip!
|
0
|
373
|
|
|
374
|
# in order to avoid rerunning the callbacks, just update through raw sql
|
|
375
|
execute_sql("UPDATE posts SET fav_string = ? WHERE id = ?", fav_string, id)
|
0
|
376
|
|
|
377
|
Favorite.create(:user_id => user_id, :post_id => id)
|
0
|
378
|
end
|
|
379
|
|
|
380
|
def remove_favorite(user)
|
1
|
381
|
if user.is_a?(ActiveRecord::Base)
|
0
|
382
|
user_id = user.id
|
0
|
383
|
else
|
|
384
|
user_id = user
|
0
|
385
|
end
|
|
386
|
|
|
387
|
self.fav_string.gsub!(/(?:\A| )fav:#{user_id}(?:\Z| )/, " ")
|
0
|
388
|
self.fav_string.strip!
|
0
|
389
|
|
|
390
|
# in order to avoid rerunning the callbacks, just update through raw sql
|
|
391
|
execute_sql("UPDATE posts SET fav_string = ? WHERE id = ?", fav_string, id)
|
0
|
392
|
|
|
393
|
Favorite.destroy(:user_id => user_id, :post_id => id)
|
0
|
394
|
end
|
|
395
|
|
|
396
|
def favorited_user_ids
|
1
|
397
|
fav_string.scan(/\d+/)
|
0
|
398
|
end
|
|
399
|
end
|
|
400
|
|
|
401
|
module SearchMethods
|
1
|
402
|
class SearchError < Exception ; end
|
1
|
403
|
|
|
404
|
def add_range_relation(arr, field, relation)
|
1
|
405
|
case arr[0]
|
216
|
406
|
when :eq
|
|
407
|
relation.where(["#{field} = ?", arr[1]])
|
0
|
408
|
|
|
409
|
when :gt
|
|
410
|
relation.where(["#{field} > ?", arr[1]])
|
0
|
411
|
|
|
412
|
when :gte
|
|
413
|
relation.where(["#{field} >= ?", arr[1]])
|
0
|
414
|
|
|
415
|
when :lt
|
|
416
|
relation.where(["#{field} < ?", arr[1]])
|
0
|
417
|
|
|
418
|
when :lte
|
|
419
|
relation.where(["#{field} <= ?", arr[1]])
|
0
|
420
|
|
|
421
|
when :between
|
|
422
|
relation.where(["#{field} BETWEEN ? AND ?", arr[1], arr[2]])
|
0
|
423
|
|
|
424
|
else
|
|
425
|
relation
|
216
|
426
|
end
|
|
427
|
end
|
|
428
|
|
|
429
|
def escape_string_for_tsquery(array)
|
1
|
430
|
array.map do |token|
|
0
|
431
|
escaped_token = token.gsub(/\\|'/, '\0\0\0\0').gsub("?", "\\\\77").gsub("%", "\\\\37")
|
0
|
432
|
"''" + escaped_token + "''"
|
0
|
433
|
end
|
|
434
|
end
|
|
435
|
|
|
436
|
def add_tag_string_search_relation(tags, relation)
|
1
|
437
|
tag_query_sql = []
|
18
|
438
|
|
|
439
|
if tags[:include].any?
|
18
|
440
|
tag_query_sql << "(" + escape_string_for_tsquery(tags[:include]).join(" | ") + ")"
|
0
|
441
|
end
|
|
442
|
|
|
443
|
if tags[:related].any?
|
18
|
444
|
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if tags[:related].size > Danbooru.config.tag_query_limit
|
0
|
445
|
tag_query_sql << "(" + escape_string_for_tsquery(tags[:related]).join(" & ") + ")"
|
0
|
446
|
end
|
|
447
|
|
|
448
|
if tags[:exclude].any?
|
18
|
449
|
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if tags[:exclude].size > Danbooru.config.tag_query_limit
|
0
|
450
|
|
|
451
|
if tags[:related].any? || tags[:include].any?
|
0
|
452
|
tag_query_sql << "!(" + escape_string_for_tsquery(tags[:exclude]).join(" | ") + ")"
|
0
|
453
|
else
|
|
454
|
raise SearchError.new("You cannot search for only excluded tags")
|
0
|
455
|
end
|
|
456
|
end
|
|
457
|
|
|
458
|
if tag_query_sql.any?
|
18
|
459
|
relation = relation.where("posts.tag_index @@ to_tsquery('danbooru', E'" + tag_query_sql.join(" & ") + "')")
|
0
|
460
|
end
|
|
461
|
|
|
462
|
relation
|
18
|
463
|
end
|
|
464
|
|
|
465
|
def add_tag_subscription_relation(subscriptions, relation)
|
1
|
466
|
subscriptions.each do |subscription|
|
0
|
467
|
subscription =~ /^(.+?):(.+)$/
|
0
|
468
|
user_name = $1 || subscription
|
0
|
469
|
subscription_name = $2
|
0
|
470
|
|
|
471
|
user = User.find_by_name(user_name)
|
0
|
472
|
|
|
473
|
if user
|
0
|
474
|
post_ids = TagSubscription.find_post_ids(user.id, subscription_name)
|
0
|
475
|
relation = relation.where(["posts.id IN (?)", post_ids])
|
0
|
476
|
end
|
|
477
|
end
|
|
478
|
|
|
479
|
relation
|
0
|
480
|
end
|
|
481
|
|
|
482
|
def find_by_tags(q, options = {})
|
1
|
483
|
unless q.is_a?(Hash)
|
18
|
484
|
q = Tag.parse_query(q)
|
18
|
485
|
end
|
|
486
|
|
|
487
|
if q[:status] == "deleted"
|
18
|
488
|
relation = RemovedPost.where("TRUE")
|
0
|
489
|
else
|
|
490
|
relation = where("TRUE")
|
18
|
491
|
end
|
|
492
|
|
|
493
|
relation = add_range_relation(q[:post_id], "posts.id", relation)
|
18
|
494
|
relation = add_range_relation(q[:mpixels], "posts.width * posts.height / 1000000.0", relation)
|
18
|
495
|
relation = add_range_relation(q[:width], "posts.image_width", relation)
|
18
|
496
|
relation = add_range_relation(q[:height], "posts.image_height", relation)
|
18
|
497
|
relation = add_range_relation(q[:score], "posts.score", relation)
|
18
|
498
|
relation = add_range_relation(q[:filesize], "posts.file_size", relation)
|
18
|
499
|
relation = add_range_relation(q[:date], "posts.created_at::date", relation)
|
18
|
500
|
relation = add_range_relation(q[:general_tag_count], "posts.tag_count_general", relation)
|
18
|
501
|
relation = add_range_relation(q[:artist_tag_count], "posts.tag_count_artist", relation)
|
18
|
502
|
relation = add_range_relation(q[:copyright_tag_count], "posts.tag_count_copyright", relation)
|
18
|
503
|
relation = add_range_relation(q[:character_tag_count], "posts.tag_count_character", relation)
|
18
|
504
|
relation = add_range_relation(q[:tag_count], "posts.tag_count", relation)
|
18
|
505
|
|
|
506
|
if options[:before_id]
|
18
|
507
|
relation = relation.where(["posts.id < ?", options[:before_id]])
|
0
|
508
|
end
|
|
509
|
|
|
510
|
if q[:md5].any?
|
18
|
511
|
relation = relation.where(["posts.md5 IN (?)", q[:md5]])
|
0
|
512
|
end
|
|
513
|
|
|
514
|
if q[:status] == "pending"
|
18
|
515
|
relation = relation.where("posts.is_pending = TRUE")
|
0
|
516
|
elsif q[:status] == "flagged"
|
|
517
|
relation = relation.where("posts.is_flagged = TRUE")
|
0
|
518
|
end
|
|
519
|
|
|
520
|
if q[:source].is_a?(String)
|
18
|
521
|
relation = relation.where(["posts.source LIKE ? ESCAPE E'\\\\'", q[:source]])
|
0
|
522
|
end
|
|
523
|
|
|
524
|
if q[:subscriptions].any?
|
18
|
525
|
relation = add_tag_subscription_relation(q[:subscriptions], relation)
|
0
|
526
|
end
|
|
527
|
|
|
528
|
relation = add_tag_string_search_relation(q[:tags], relation)
|
18
|
529
|
|
|
530
|
if q[:rating] == "q"
|
18
|
531
|
relation = relation.where("posts.rating = 'q'")
|
0
|
532
|
elsif q[:rating] == "s"
|
|
533
|
relation = relation.where("posts.rating = 's'")
|
0
|
534
|
elsif q[:rating] == "e"
|
|
535
|
relation = relation.where("posts.rating = 'e'")
|
0
|
536
|
end
|
|
537
|
|
|
538
|
if q[:rating_negated] == "q"
|
18
|
539
|
relation = relation.where("posts.rating <> 'q'")
|
0
|
540
|
elsif q[:rating_negated] == "s"
|
|
541
|
relation = relation.where("posts.rating <> 's'")
|
0
|
542
|
elsif q[:rating_negated] == "e"
|
|
543
|
relation = relation.where("posts.rating <> 'e'")
|
0
|
544
|
end
|
|
545
|
|
|
546
|
case q[:order]
|
18
|
547
|
when "id", "id_asc"
|
|
548
|
relation = relation.order("posts.id")
|
0
|
549
|
|
|
550
|
when "id_desc"
|
|
551
|
relation = relation.order("posts.id DESC")
|
0
|
552
|
|
|
553
|
when "score", "score_desc"
|
|
554
|
relation = relation.order("posts.score DESC, posts.id DESC")
|
0
|
555
|
|
|
556
|
when "score_asc"
|
|
557
|
relation = relation.order("posts.score, posts.id DESC")
|
0
|
558
|
|
|
559
|
when "mpixels", "mpixels_desc"
|
|
560
|
# Use "w*h/1000000", even though "w*h" would give the same result, so this can use
|
|
561
|
# the posts_mpixels index.
|
|
562
|
relation = relation.order("posts.image_width * posts.image_height / 1000000.0 DESC, posts.id DESC")
|
0
|
563
|
|
|
564
|
when "mpixels_asc"
|
|
565
|
relation = relation.order("posts.image_width * posts.image_height / 1000000.0, posts.id DESC")
|
0
|
566
|
|
|
567
|
when "portrait"
|
|
568
|
relation = relation.order("1.0 * posts.image_width / GREATEST(1, posts.image_height), posts.id DESC")
|
0
|
569
|
|
|
570
|
when "landscape"
|
|
571
|
relation = relation.order("1.0 * posts.image_width / GREATEST(1, posts.image_height) DESC, posts.id DESC")
|
0
|
572
|
|
|
573
|
when "filesize", "filesize_desc"
|
|
574
|
relation = relation.order("posts.file_size DESC")
|
0
|
575
|
|
|
576
|
when "filesize_asc"
|
|
577
|
relation = relation.order("posts.file_size")
|
0
|
578
|
|
|
579
|
else
|
|
580
|
relation = relation.order("posts.id DESC")
|
18
|
581
|
end
|
|
582
|
|
|
583
|
if options[:limit]
|
18
|
584
|
relation = relation.limit(options[:limit])
|
0
|
585
|
end
|
|
586
|
|
|
587
|
if options[:offset]
|
18
|
588
|
relation = relation.offset(options[:offset])
|
0
|
589
|
end
|
|
590
|
|
|
591
|
if options[:select]
|
18
|
592
|
relation = relation.select(options[:select])
|
0
|
593
|
end
|
|
594
|
|
|
595
|
relation
|
18
|
596
|
end
|
|
597
|
end
|
|
598
|
|
|
599
|
module UploaderMethods
|
1
|
600
|
def initialize_uploader
|
1
|
601
|
self.uploader = CurrentUser.user
|
6
|
602
|
self.uploader_ip_addr = CurrentUser.ip_addr
|
6
|
603
|
end
|
|
604
|
|
|
605
|
def uploader_id=(user_id)
|
1
|
606
|
self.uploader = User.find(user_id)
|
0
|
607
|
end
|
|
608
|
|
|
609
|
def uploader_id
|
1
|
610
|
uploader_string[9..-1].to_i
|
0
|
611
|
end
|
|
612
|
|
|
613
|
def uploader_name
|
1
|
614
|
User.id_to_name(uploader_id)
|
0
|
615
|
end
|
|
616
|
|
|
617
|
def uploader
|
1
|
618
|
User.find(uploader_id)
|
0
|
619
|
end
|
|
620
|
|
|
621
|
def uploader=(user)
|
1
|
622
|
self.uploader_string = "uploader:#{user.id}"
|
12
|
623
|
end
|
|
624
|
end
|
|
625
|
|
|
626
|
module PoolMethods
|
1
|
627
|
def add_pool(pool)
|
1
|
628
|
return if pool_string =~ /(?:\A| )pool:#{pool.id}(?:\Z| )/
|
0
|
629
|
self.pool_string += " pool:#{pool.id}"
|
0
|
630
|
self.pool_string.strip!
|
0
|
631
|
execute_sql("UPDATE posts SET pool_string = ? WHERE id = ?", pool_string, id)
|
0
|
632
|
pool.add_post!(self)
|
0
|
633
|
end
|
|
634
|
|
|
635
|
def remove_pool(pool)
|
1
|
636
|
self.pool_string.gsub!(/(?:\A| )pool:#{pool.id}(?:\Z| )/, " ")
|
0
|
637
|
self.pool_string.strip!
|
0
|
638
|
execute_sql("UPDATE posts SET pool_string = ? WHERE id = ?", pool_string, id)
|
0
|
639
|
pool.remove_post!(self)
|
0
|
640
|
end
|
|
641
|
end
|
|
642
|
|
|
643
|
module VoteMethods
|
1
|
644
|
def can_be_voted_by?(user)
|
1
|
645
|
!votes.exists?(["user_id = ?", user.id])
|
0
|
646
|
end
|
|
647
|
|
|
648
|
def vote!(score)
|
1
|
649
|
if can_be_voted_by?(CurrentUser.user)
|
0
|
650
|
if score == "up"
|
0
|
651
|
increment!(:score)
|
0
|
652
|
elsif score == "down"
|
|
653
|
decrement!(:score)
|
0
|
654
|
end
|
|
655
|
|
|
656
|
votes.create(:score => score)
|
0
|
657
|
else
|
|
658
|
raise PostVote::Error.new("You have already voted for this comment")
|
0
|
659
|
end
|
|
660
|
end
|
|
661
|
end
|
|
662
|
|
|
663
|
module CountMethods
|
1
|
664
|
def fast_count(tags = "")
|
1
|
665
|
tags = tags.to_s
|
18
|
666
|
count = Cache.get("pfc:#{Cache.sanitize(tags)}")
|
18
|
667
|
if count.nil?
|
18
|
668
|
count = Post.find_by_tags("#{tags}").count
|
18
|
669
|
if count > Danbooru.config.posts_per_page * 10
|
18
|
670
|
Cache.put("pfc:#{Cache.sanitize(tags)}", count, (count * 4).minutes)
|
0
|
671
|
end
|
|
672
|
end
|
|
673
|
count
|
18
|
674
|
end
|
|
675
|
end
|
|
676
|
|
|
677
|
module CacheMethods
|
1
|
678
|
def expire_cache(tag_name)
|
1
|
679
|
if Post.fast_count("") < 1000
|
18
|
680
|
Cache.delete("pfc:")
|
18
|
681
|
end
|
|
682
|
Cache.delete("pfc:#{Cache.sanitize(tag_name)}")
|
18
|
683
|
end
|
|
684
|
end
|
|
685
|
|
|
686
|
module ParentMethods
|
1
|
687
|
# A parent has many children. A child belongs to a parent.
|
|
688
|
# A parent cannot have a parent.
|
|
689
|
#
|
|
690
|
# After deleting a child:
|
|
691
|
# - Move favorites to parent.
|
|
692
|
# - Does the parent have any active children?
|
|
693
|
# - Yes: Done.
|
|
694
|
# - No: Update parent's has_children flag to false.
|
|
695
|
#
|
|
696
|
# After deleting a parent:
|
|
697
|
# - Move favorites to the first child.
|
|
698
|
# - Reparent all active children to the first active child.
|
|
699
|
|
|
700
|
module ClassMethods
|
1
|
701
|
def update_has_children_flag_for(post_id)
|
1
|
702
|
has_children = Post.exists?(["parent_id = ?", post_id])
|
0
|
703
|
execute_sql("UPDATE posts SET has_children = ? WHERE id = ?", has_children, post_id)
|
0
|
704
|
end
|
|
705
|
|
|
706
|
def recalculate_has_children_for_all_posts
|
1
|
707
|
transaction do
|
0
|
708
|
execute_sql("UPDATE posts SET has_children = false WHERE has_children = true")
|
0
|
709
|
execute_sql("UPDATE posts SET has_children = true WHERE id IN (SELECT p.parent_id FROM posts p WHERE p.parent_id IS NOT NULL)")
|
0
|
710
|
end
|
|
711
|
end
|
|
712
|
end
|
|
713
|
|
|
714
|
def self.included(m)
|
1
|
715
|
m.extend(ClassMethods)
|
1
|
716
|
end
|
|
717
|
|
|
718
|
def validate_parent_does_not_have_a_parent
|
1
|
719
|
return if parent.nil?
|
6
|
720
|
if !parent.parent.nil?
|
0
|
721
|
errors.add(:parent, "can not have a parent")
|
0
|
722
|
end
|
|
723
|
end
|
|
724
|
|
|
725
|
def update_parent_on_destroy
|
1
|
726
|
Post.update_has_children_flag_for(parent_id)
|
0
|
727
|
Post.update_has_children_flag_for(parent_id_was) if parent_id_was && parent_id != parent_id_was
|
0
|
728
|
end
|
|
729
|
|
|
730
|
def update_children_on_destroy
|
1
|
731
|
if children.size == 0
|
0
|
732
|
# do nothing
|
|
733
|
elsif children.size == 1
|
0
|
734
|
children.first.update_attribute(:parent_id, nil)
|
0
|
735
|
else
|
|
736
|
cached_children = children
|
0
|
737
|
cached_children[1..-1].each do |child|
|
0
|
738
|
child.update_attribute(:parent_id, cached_children[0].id)
|
0
|
739
|
end
|
|
740
|
cached_children[0].update_attribute(:parent_id, nil)
|
0
|
741
|
end
|
|
742
|
end
|
|
743
|
|
|
744
|
def update_parent_on_save
|
1
|
745
|
if parent_id == parent_id_was
|
6
|
746
|
# do nothing
|
|
747
|
elsif !parent_id_was.nil?
|
0
|
748
|
Post.update_has_children_flag_for(parent_id)
|
0
|
749
|
Post.update_has_children_flag_for(parent_id_was)
|
0
|
750
|
else
|
|
751
|
Post.update_has_children_flag_for(parent_id)
|
0
|
752
|
end
|
|
753
|
end
|
|
754
|
|
|
755
|
def give_favorites_to_parent
|
1
|
756
|
return if parent.nil?
|
0
|
757
|
|
|
758
|
favorited_user_ids.each do |user_id|
|
0
|
759
|
parent.add_favorite(user_id)
|
0
|
760
|
remove_favorite(user_id)
|
0
|
761
|
end
|
|
762
|
end
|
|
763
|
|
|
764
|
def delete_favorites
|
1
|
765
|
Favorite.destroy_for_post(self)
|
0
|
766
|
end
|
|
767
|
end
|
|
768
|
|
|
769
|
module RemovalMethods
|
1
|
770
|
def remove!
|
1
|
771
|
Post.transaction do
|
0
|
772
|
execute_sql("INSERT INTO removed_posts (#{Post.column_names.join(', ')}) SELECT #{Post.column_names.join(', ')} FROM posts WHERE posts.id = #{id}")
|
0
|
773
|
give_favorites_to_parent
|
0
|
774
|
update_children_on_destroy
|
0
|
775
|
delete_favorites
|
0
|
776
|
decrement_tag_post_counts
|
0
|
777
|
execute_sql("DELETE FROM posts WHERE id = #{id}")
|
0
|
778
|
update_parent_on_destroy
|
0
|
779
|
tag_array.each {|x| expire_cache(x)}
|
0
|
780
|
end
|
|
781
|
end
|
|
782
|
|
|
783
|
def is_removed?
|
1
|
784
|
false
|
0
|
785
|
end
|
|
786
|
end
|
|
787
|
|
|
788
|
include FileMethods
|
1
|
789
|
include ImageMethods
|
1
|
790
|
include ApprovalMethods
|
1
|
791
|
include PresenterMethods
|
1
|
792
|
include HistoryMethods
|
1
|
793
|
include TagMethods
|
1
|
794
|
include FavoriteMethods
|
1
|
795
|
include UploaderMethods
|
1
|
796
|
include PoolMethods
|
1
|
797
|
extend SearchMethods
|
1
|
798
|
include VoteMethods
|
1
|
799
|
extend CountMethods
|
1
|
800
|
include CacheMethods
|
1
|
801
|
include ParentMethods
|
1
|
802
|
include RemovalMethods
|
1
|
803
|
|
|
804
|
def reload(options = nil)
|
1
|
805
|
super
|
0
|
806
|
reset_tag_array_cache
|
0
|
807
|
end
|
|
808
|
|
|
809
|
def presenter
|
1
|
810
|
@presenter ||= PostPresenter.new(self)
|
0
|
811
|
end
|
|
812
|
end
|
|
813
|
|
|
814
|
Post.connection.extend(PostgresExtensions)
|
1
|