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->runCallbacks('delete', function() { $this->updateAttributes(array('status' => 'deleted')); }); } public function delete_from_database() { $this->runCallbacks('destroy', function() { $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); }); } public function undelete() { if ($this->status == 'active') { return; } $this->runCallbacks('undelete', function() { $this->updateAttributes(['status' => 'active']); }); } public function service() { return CONFIG()->local_image_service; } public function service_icon() { return "/favicon.ico"; } protected function callbacks() { return [ 'before_create' => ['set_index_timestamp'], 'after_create' => ['after_creation'], 'before_delete' => ['clear_avatars'], 'after_delete' => ['give_favorites_to_parent', 'decrement_count'], 'after_undelete'=> ['increment_count'], 'before_save' => ['commit_tags', 'filter_parent_id'], 'after_save' => ['update_parent', 'save_post_history', 'expire_cache'], 'after_destroy' => ['expire_cache'], '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' ], 'after_validation_on_create' => ['before_creation'] ]; } 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->clearChangedAttributes(); $this->commit_tags(); $update = []; foreach (array_keys($this->changedAttributes()) as $attrName) { $update[$attrName] = $this->getAttribute($attrName); } $this->updateColumns($update); } } 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"); } ]; } }