2013-10-27 01:06:58 +02:00
< ? 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 ()
{
2013-11-13 18:53:33 +01:00
$this -> runCallbacks ( 'delete' , function () {
$this -> updateAttributes ( array ( 'status' => 'deleted' ));
});
2013-10-27 01:06:58 +02:00
}
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' );
}
public function undelete ()
{
2013-11-13 18:53:33 +01:00
if ( $this -> status == 'active' ) {
return ;
2013-10-27 01:06:58 +02:00
}
2013-11-13 18:53:33 +01:00
$this -> runCallbacks ( 'undelete' , function () {
$this -> updateAttributes ([ 'status' => 'active' ]);
});
2013-10-27 01:06:58 +02:00
}
public function service_icon ()
{
return " /favicon.ico " ;
}
protected function callbacks ()
{
2013-11-08 17:37:29 +01:00
return [
2013-10-27 01:06:58 +02:00
'before_create' => [ 'set_index_timestamp' ],
'after_create' => [ 'after_creation' ],
2013-11-13 18:53:33 +01:00
'before_delete' => [ 'clear_avatars' ],
'after_delete' => [ 'give_favorites_to_parent' , 'decrement_count' ],
'after_undelete' => [ 'increment_count' ],
'before_save' => [ 'commit_tags' , 'filter_parent_id' ],
2013-10-27 01:06:58 +02:00
'after_save' => [ 'update_parent' , 'save_post_history' , 'expire_cache' ],
2013-11-13 18:53:33 +01:00
2013-10-27 01:06:58 +02:00
'after_destroy' => [ 'expire_cache' ],
2013-11-13 18:53:33 +01:00
2013-10-27 01:06:58 +02:00
'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' ,
2013-11-13 18:53:33 +01:00
'generate_jpeg' , 'generate_preview' , 'move_file'
],
'after_validation_on_create' => [ 'before_creation' ]
2013-11-08 17:37:29 +01:00
];
2013-10-27 01:06:58 +02:00
}
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 );
2013-11-08 17:37:29 +01:00
$this -> save ();
2013-10-27 01:06:58 +02:00
}
}
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 " );
}
];
}
}