344 lines
12 KiB
PHP
Executable File
344 lines
12 KiB
PHP
Executable File
<?php
|
|
foreach (glob(dirname(__FILE__).'/Post/*.php') as $trait) require $trait;
|
|
|
|
class Post extends Rails\ActiveRecord\Base
|
|
{
|
|
use PostSqlMethods, PostCommentMethods, PostImageStoreMethods,
|
|
PostVoteMethods, PostTagMethods, PostCountMethods,
|
|
Post\CacheMethods, PostParentMethods, PostFileMethods,
|
|
PostChangeSequenceMethods, PostRatingMethods, PostStatusMethods,
|
|
PostApiMethods, /*PostMirrorMethods, */PostFrameMethods;
|
|
|
|
use Moebooru\Versioning\VersioningTrait;
|
|
|
|
protected $previous_id;
|
|
|
|
protected $next_id;
|
|
|
|
// public $author;
|
|
|
|
public $updater_user_id;
|
|
|
|
public $updater_ip_addr;
|
|
|
|
static public function init_versioning($v)
|
|
{
|
|
$v->versioned_attributes([
|
|
'source' => ['default' => ''],
|
|
'cached_tags',
|
|
# MI: Allowing reverting to default.
|
|
'is_shown_in_index' => ['default' => true, 'allow_reverting_to_default' => true],
|
|
'rating',
|
|
'is_rating_locked' => ['default' => false],
|
|
'is_note_locked' => ['default' => false],
|
|
'parent_id' => ['default' => null],
|
|
// iTODO: uncomment when frames are enabled
|
|
// 'frames_pending' => ['default' => '', 'allow_reverting_to_default' => true]
|
|
]);
|
|
}
|
|
|
|
public function __call($method, $params)
|
|
{
|
|
switch(true) {
|
|
# Checking status: $paramsost->is_pending();
|
|
case (strpos($method, 'is_') === 0):
|
|
$status = str_replace('is_', '', $method);
|
|
return $this->status == $status;
|
|
default:
|
|
return parent::__call($method, $params);
|
|
}
|
|
}
|
|
|
|
public function next_id()
|
|
{
|
|
if ($this->next_id === null) {
|
|
$post = Post::available()->where('id > ?', $this->id)->limit(1)->first();
|
|
$this->next_id = $post ? $post->id : false;
|
|
}
|
|
return $this->next_id;
|
|
}
|
|
|
|
public function previous_id()
|
|
{
|
|
if ($this->previous_id === null) {
|
|
$post = Post::available()->where('id < ?', $this->id)->order('id DESC')->limit(1)->first();
|
|
$this->previous_id = $post ? $post->id : false;
|
|
}
|
|
return $this->previous_id;
|
|
}
|
|
|
|
public function author()
|
|
{
|
|
return $this->user ? $this->user->name : null;
|
|
}
|
|
|
|
public function can_be_seen_by($user = null, array $options = array())
|
|
{
|
|
if (empty($options['show_deleted']) && $this->status == 'deleted') {
|
|
return false;
|
|
}
|
|
|
|
return CONFIG()->can_see_post($user, $this);
|
|
}
|
|
|
|
public function normalized_source()
|
|
{
|
|
if (preg_match('/pixiv\.net\/img/', $this->source)) {
|
|
if (preg_match('/(\d+)(_s|_m|(_big)?_p\d+)?\.\w+(\?\d+)?\z/', $this->source, $m))
|
|
$img_id = $m[1];
|
|
else
|
|
$img_id = null;
|
|
return "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=" . $img_id;
|
|
} elseif (strpos($this->source, 'http://') === 0 || strpos($this->source, 'https://') === 0)
|
|
return $this->source;
|
|
else
|
|
return 'http://' . $this->source;
|
|
}
|
|
|
|
public function clear_avatars()
|
|
{
|
|
User::clear_avatars($this->id);
|
|
}
|
|
|
|
public function approve($approver_id)
|
|
{
|
|
$old_status = $this->status;
|
|
|
|
if ($this->flag_detail)
|
|
$this->flag_detail->updateAttribute('is_resolved', true);
|
|
|
|
$this->updateAttributes(array('status' => 'active', 'approver_id' => $approver_id));
|
|
|
|
# Don't bump posts if the status wasn't "pending"; it might be "flagged".
|
|
if ($old_status == 'pending' and CONFIG()->hide_pending_posts) {
|
|
// $this->touch_index_timestamp();
|
|
$this->save();
|
|
}
|
|
}
|
|
|
|
public function voted_by($score = null)
|
|
{
|
|
# Cache results
|
|
if (!$this->voted_by) {
|
|
foreach (range(1, 3) as $v) {
|
|
$this->voted_by[$v] =
|
|
User::where("v.post_id = ? and v.score = ?", $this->id, $v)
|
|
->joins("JOIN post_votes v ON v.user_id = users.id")
|
|
->select("users.name, users.id")
|
|
->order("v.updated_at DESC")
|
|
->take()
|
|
->getAttributes(['id', 'name']) ?: array();
|
|
}
|
|
}
|
|
|
|
if (func_num_args())
|
|
return $this->voted_by[$score];
|
|
return $this->voted_by;
|
|
}
|
|
|
|
public function can_user_delete(User $user = null)
|
|
{
|
|
if (!$user)
|
|
$user = current_user();
|
|
|
|
if (!$user->has_permission($this))
|
|
return false;
|
|
elseif (!$user->is_mod_or_higher() && !$this->is_held() && (strtotime(date('Y-m-d H:i:s')) - strtotime($this->created_at)) > 60*60*24)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public function favorited_by()
|
|
{
|
|
return $this->voted_by(3);
|
|
}
|
|
|
|
public function active_notes()
|
|
{
|
|
return $this->notes ? $this->notes->select(function($x){return $x->is_active;}) : array();
|
|
}
|
|
|
|
public function set_flag_detail($reason, $creator_id)
|
|
{
|
|
if ($this->flag_detail) {
|
|
$this->flag_detail->updateAttributes(array('reason' => $reason, 'user_id' => $creator_id, 'created_at' => date('Y-m-d H:i:s')));
|
|
} else {
|
|
FlaggedPostDetail::create(array('post_id' => $this->id, 'reason' => $reason, 'user_id' => $creator_id, 'is_resolved' => false));
|
|
}
|
|
}
|
|
|
|
public function flag($reason, $creator_id)
|
|
{
|
|
$this->updateAttribute('status', 'flagged');
|
|
$this->set_flag_detail($reason, $creator_id);
|
|
}
|
|
|
|
public function destroy_with_reason($reason, $current_user)
|
|
{
|
|
// Post.transaction do
|
|
if ($this->flag_detail)
|
|
$this->flag_detail->updateAttribute('is_resolved', true);
|
|
$this->flag($reason, $current_user->id);
|
|
$this->first_delete();
|
|
|
|
if (CONFIG()->delete_posts_permanently)
|
|
$this->delete_from_database();
|
|
// end
|
|
}
|
|
|
|
static public function static_destroy_with_reason($id, $reason, $current_user)
|
|
{
|
|
$post = Post::find($id);
|
|
return $post->destroy_with_reason($reason, $current_user);
|
|
}
|
|
|
|
public function first_delete()
|
|
{
|
|
$this->updateAttributes(array('status' => 'deleted'));
|
|
$this->runCallbacks('after_delete');
|
|
}
|
|
|
|
public function delete_from_database()
|
|
{
|
|
$this->delete_file();
|
|
self::connection()->executeSql('UPDATE pools SET post_count = post_count - 1 WHERE id IN (SELECT pool_id FROM pools_posts WHERE post_id = '.$this->id.')');
|
|
self::connection()->executeSql('UPDATE tags SET post_count = post_count - 1 WHERE id IN (SELECT tag_id FROM posts_tags WHERE post_id = '.$this->id.')');
|
|
# MI: Destroying pool posts manually so their histories are deleted by foreign keys.
|
|
# This is done in Pool too. This could be done with a MySQL trigger.
|
|
PoolPost::destroyAll('post_id = ?', $this->id);
|
|
self::connection()->executeSql("DELETE FROM posts WHERE id = ?", $this->id);
|
|
$this->runCallbacks('after_destroy');
|
|
}
|
|
|
|
# This method is in status_methods
|
|
public function undelete()
|
|
{
|
|
$this->status = 'active';
|
|
$this->save();
|
|
if ($this->parent_id) {
|
|
Post::update_has_children($this->parent_id);
|
|
}
|
|
}
|
|
|
|
public function service_icon()
|
|
{
|
|
return "/favicon.ico";
|
|
}
|
|
|
|
protected function callbacks()
|
|
{
|
|
return array_merge_recursive([
|
|
'before_save' => ['commit_tags', 'filter_parent_id'],
|
|
'before_create' => ['set_index_timestamp'],
|
|
'after_create' => ['after_creation'],
|
|
'after_delete' => ['clear_avatars', 'give_favorites_to_parent'],
|
|
'after_save' => ['update_parent', 'save_post_history', 'expire_cache'],
|
|
'after_destroy' => ['expire_cache'],
|
|
'after_validation_on_create' => ['before_creation'],
|
|
'before_validation_on_create' => [
|
|
'download_source', 'ensure_tempfile_exists', 'determine_content_type',
|
|
'validate_content_type', 'generate_hash', 'set_image_dimensions',
|
|
'set_image_status', 'check_pending_count', 'generate_sample',
|
|
'generate_jpeg', 'generate_preview', 'move_file']
|
|
], $this->versioning_callbacks());
|
|
}
|
|
|
|
protected function associations()
|
|
{
|
|
return [
|
|
'has_one' => ['flag_detail' => ['class_name' => "FlaggedPostDetail"]],
|
|
'belongs_to' => [
|
|
'user',
|
|
'approver' => ['class_name' => 'User']
|
|
],
|
|
'has_many' => [
|
|
'notes' => [function() { $this->order('id DESC')->where('is_active = 1'); }],
|
|
'comments' => [function() { $this->order("id"); }],
|
|
'children' => [
|
|
function() { $this->order('id')->where("status != 'deleted'"); },
|
|
'class_name' => 'Post',
|
|
'foreign_key' => 'parent_id'
|
|
],
|
|
'tag_history' => [function() { $this->order("id DESC"); }, 'class_name' => 'PostTagHistory'],
|
|
]
|
|
];
|
|
}
|
|
|
|
protected function before_creation()
|
|
{
|
|
$this->upload = !empty($_FILES['post']['tmp_name']['file']) ? true : false;
|
|
|
|
if (CONFIG()->tags_from_filename)
|
|
$this->get_tags_from_filename();
|
|
if (CONFIG()->source_from_filename)
|
|
$this->get_source_from_filename();
|
|
|
|
if (!$this->rating)
|
|
$this->rating = CONFIG()->default_rating_upload;
|
|
|
|
$this->rating = strtolower(substr($this->rating, 0, 1));
|
|
|
|
if ($this->gif() && CONFIG()->add_gif_tag_to_gif)
|
|
$this->new_tags[] = 'gif';
|
|
elseif ($this->flash() && CONFIG()->add_flash_tag_to_swf)
|
|
$this->new_tags[] = 'flash';
|
|
|
|
if ($this->new_tags)
|
|
$this->old_tags = 'tagme';
|
|
|
|
$this->cached_tags = 'tagme';
|
|
|
|
!$this->parent_id && $this->parent_id = null;
|
|
!$this->source && $this->source = null;
|
|
|
|
$this->random = mt_rand();
|
|
|
|
Tag::find_or_create_by_name('tagme');
|
|
}
|
|
|
|
protected function after_creation()
|
|
{
|
|
if ($this->new_tags) {
|
|
$this->commit_tags();
|
|
$sql = "UPDATE posts SET cached_tags = ? WHERE id = ?";
|
|
self::connection()->executeSql($sql, $this->cached_tags, $this->id);
|
|
$this->save();
|
|
}
|
|
}
|
|
|
|
protected function set_index_timestamp()
|
|
{
|
|
$this->index_timestamp = date('Y-m-d H:i:s');
|
|
}
|
|
|
|
# Added to avoid SQL constraint errors if parent_id passed isn't a valid post.
|
|
protected function filter_parent_id()
|
|
{
|
|
if (($parent_id = trim($this->parent_id)) && Post::where(['id' => $parent_id])->first())
|
|
$this->parent_id = $parent_id;
|
|
else
|
|
$this->parent_id = null;
|
|
}
|
|
|
|
protected function scopes()
|
|
{
|
|
return [
|
|
'available' => function() {
|
|
$this->where("posts.status <> ?", "deleted");
|
|
},
|
|
'has_any_tags' => function($tags) {
|
|
// where('posts.tags_index @@ ?', Array(tags).map { |t| t.to_escaped_for_tsquery }.join(' | '))
|
|
},
|
|
'has_all_tags' => function($tags) {
|
|
$this
|
|
->joins('INNER JOIN posts_tags pti ON p.id = pti.post_id JOIN tags ti ON pti.tag_id = ti.id')
|
|
->where('ti.name IN ('.implode(', ', array_fill(0, count($tags), '?')).')');
|
|
// where('posts.tags_index @@ ?', Array(tags).map { |t| t.to_escaped_for_tsquery }.join(' & '))
|
|
},
|
|
'flagged' => function() {
|
|
$this->where("status = ?", "flagged");
|
|
}
|
|
];
|
|
}
|
|
} |