2013-10-27 01:06:58 +02:00
< ? php
class PostController extends ApplicationController
{
public function activate ()
{
$ids = is_array ( $this -> params () -> post_ids ) ? array_map ( function ( $id ){ return ( int ) $id ;}, $this -> params () -> post_ids ) : array ();
$changed = Post :: batch_activate ( current_user () -> is_mod_or_higher () ? null : current_user () -> id , $ids );
$this -> respond_to_success ( " Posts activated " , '#moderate' , [ 'api' => [ 'count' => $changed ]]);
}
public function uploadProblem ()
{
}
public function upload ()
{
$this -> set_title ( 'Upload' );
$this -> deleted_posts = FlaggedPostDetail :: new_deleted_posts ( current_user ());
# if $this->params()->url
# $this->post = Post.find(:first, 'conditions' => ["source = ?", $this->params()->url])
# end
$this -> default_rating = CONFIG () -> default_rating_upload ? : " q " ;
if ( empty ( $this -> post )) {
$this -> post = new Post ();
}
}
public function create ()
{
if ( current_user () -> is_member_or_lower () && Post :: where ( " user_id = ? AND created_at > ? " , current_user () -> id , date ( 'Y-m-d H:i:s' , strtotime ( '-1 day' ))) -> count () >= CONFIG () -> member_post_limit ) {
$this -> respond_to_error ( " Daily limit exceeded " , '#error' , array ( 'status' => 421 ));
return ;
}
if ( current_user () -> is_privileged_or_higher ()) {
$status = " active " ;
} else {
$status = " pending " ;
}
if ( $this -> params () -> anonymous == '1' and current_user () -> is_contributor_or_higher ()) {
$user_id = null ;
// # FIXME: someone track down the user of Thread evilry here and nuke
// # it please?
// $this->session()->delete('danbooru-user');
// $this->session()->delete('danbooru-user_id');
// $this->session->danbooru-ip_addr = $this->request()->remoteIp());
} else {
$user_id = current_user () -> id ;
}
// $is_upload = array_key_exists('post', $_FILES);
# iTODO
$post_params = array_merge ( $this -> params () -> post ? : array (), array (
'updater_user_id' => current_user () -> id ,
'updater_ip_addr' => $this -> request () -> remoteIp (),
'user_id' => current_user () -> id ,
'ip_addr' => $this -> request () -> remoteIp (),
'status' => $status ,
'tempfile_path' => $_FILES [ 'post' ][ 'tmp_name' ][ 'file' ],
'tempfile_name' => $_FILES [ 'post' ][ 'name' ][ 'file' ],
'is_import' => false , # Make sure to keep this value false
// 'tempfile_path' => $is_upload ? $_FILES['post']['tmp_name']['file'] : null,
// 'tempfile_name' => $is_upload ? $_FILES['post']['name']['file'] : null,
// 'is_upload' => $is_upload,
));
$this -> post = Post :: create ( $post_params );
if ( $this -> post -> errors () -> blank ()) {
if ( $this -> params () -> md5 && $this -> post -> md5 != strtolower ( $this -> params () -> md5 )) {
$this -> post -> destroy ();
$this -> respond_to_error ( " MD5 mismatch " , '#error' , array ( 'status' => 420 ));
} else {
$api_data = array ( 'post_id' => $this -> post -> id , 'location' => $this -> urlFor ( array ( 'post#show' , 'id' => $this -> post -> id )));
if ( CONFIG () -> dupe_check_on_upload && $this -> post -> image () && ! $this -> post -> parent_id ) {
if ( $this -> params () -> format == " xml " || $this -> params () -> format == " json " ) {
# iTODO
// $options = array('services' => SimilarImages::get_services("local"), 'type' => 'post', 'source' => $this->post);
// $res = SimilarImages::similar_images($options);
// if (!empty($res['posts'])( {
// $this->post->tags = $this->post->tags() . " possible_duplicate";
// $this->post->save();
// $api_data['has_similar_hits'] = true;
// }
}
$api_data [ 'similar_location' ] = $this -> urlFor ( array ( 'post#similar' , 'id' => $this -> post -> id , 'initial' => 1 ));
$this -> respond_to_success ( " Post uploaded " , array ( 'post#similar' , 'id' => $this -> post -> id , 'initial' => 1 ), array ( 'api' => $api_data ));
} else {
$this -> respond_to_success ( " Post uploaded " , array ( 'post#show' , 'id' => $this -> post -> id , 'tag_title' => $this -> post -> tag_title ()), array ( 'api' => $api_data ));
}
}
} elseif ( $this -> post -> errors () -> on ( 'md5' )) {
$p = Post :: where ([ 'md5' => $this -> post -> md5 ]) -> first ();
$update = array ( 'tags' => $p -> cached_tags . " " . ( isset ( $this -> params () -> post [ 'tags' ]) ? $this -> params () -> post [ 'tags' ] : '' ), 'updater_user_id' => $this -> session () -> user_id , 'updater_ip_addr' => $this -> request () -> remoteIp ());
if ( ! $p -> source && $this -> post -> source )
$update [ 'source' ] = $this -> post -> source ;
$p -> updateAttributes ( $update );
$api_data = array (
'location' => $this -> urlFor ( array ( 'post#show' , 'id' => $p -> id )),
'post_id' => $p -> id
);
$this -> respond_to_error ( " Post already exists " , array ( 'post#show' , 'id' => $p -> id , 'tag_title' => $this -> post -> tag_title ()), array ( 'api' => $api_data , 'status' => 423 ));
} else {
$this -> respond_to_error ( $this -> post , '#error' );
}
}
public function moderate ()
{
$this -> set_title ( 'Moderation Queue' );
if ( $this -> request () -> isPost ()) {
$posts = new Rails\ActiveRecord\Collection ();
if ( $this -> params () -> ids ) {
foreach ( array_keys ( $this -> params () -> ids ) as $post_id ) {
$post = Post :: find ( $post_id );
if ( $this -> params () -> commit == " Approve " )
$post -> approve ( current_user () -> id );
elseif ( $this -> params () -> commit == " Delete " ) {
$post -> destroy_with_reason (( $this -> params () -> reason ? $this -> params () -> reason : $this -> params () -> reason2 ), current_user ());
# Include post data for the parent: deleted posts aren't counted as children, so
# their has_children attribute may change.
if ( $post -> parent_id )
$posts [] = $post -> get_parent ();
}
# Post may have been permanently deleted.
if ( ! CONFIG () -> delete_posts_permanently ) {
$post -> reload ();
}
$posts [] = $post ;
}
}
$posts -> unique ();
if ( $this -> request () -> format () == " json " || $this -> request () -> format () == " xml " )
$api_data = Post :: batch_api_data ( $posts -> members ());
else
$api_data = array ();
if ( $this -> params () -> commit == " Approve " )
$this -> respond_to_success ( " Post approved " , " #moderate " , array ( 'api' => $api_data ));
elseif ( $this -> params () -> commit == " Delete " )
$this -> respond_to_success ( " Post deleted " , " #moderate " , array ( 'api' => $api_data ));
} else {
if ( $this -> params () -> query ) {
list ( $sql , $params ) = Post :: generate_sql ( $this -> params () -> query , array ( 'pending' => true , 'order' => " id desc " ));
$this -> pending_posts = Post :: findBySql ( $sql , $params );
list ( $sql , $params ) = Post :: generate_sql ( $this -> params () -> query , array ( 'flagged' => true , 'order' => " id desc " ));
$this -> flagged_posts = Post :: findBySql ( $sql , $params );
} else {
$this -> pending_posts = Post :: where ( " status = 'pending' " ) -> order ( " id desc " ) -> take ();
$this -> flagged_posts = Post :: where ( " status = 'flagged' " ) -> order ( " id desc " ) -> take ();
}
}
}
public function update ()
{
$this -> post = Post :: find ( $this -> params () -> id );
if ( $this -> post -> is_deleted () and ! current_user () -> is_mod_or_higher ()) {
$this -> respond_to_error ( 'Post Locked' , array ( '#show' , 'id' => $this -> params () -> id ), array ( 'status' => 422 ));
return ;
}
$user_id = current_user () -> id ;
$post = $this -> params () -> post ;
Post :: filter_api_changes ( $post );
$post [ 'updater_user_id' ] = current_user () -> id ;
$post [ 'updater_ip_addr' ] = $this -> request () -> remoteIp ();
if ( $this -> post -> updateAttributes ( $post )) {
# Reload the post to send the new status back; not all changes will be reflected in
# $this->post due to after_save changes.
$this -> post -> reload ();
if ( $this -> params () -> format == " json " || $this -> params () -> format == " xml " )
$api_data = $this -> post -> api_data ();
else
$api_data = [];
$this -> respond_to_success ( " Post updated " , array ( '#show' , 'id' => $this -> post -> id , 'tag_title' => $this -> post -> tag_title ()), array ( 'api' => $api_data ));
} else {
$this -> respond_to_error ( $this -> post , array ( '#show' , 'id' => $this -> params () -> id ));
}
}
public function updateBatch ()
{
$user_id = current_user () -> id ;
$ids = array ();
if ( ! is_array ( $this -> params () -> post ))
$this -> params () -> post = [];
foreach ( $this -> params () -> post as $post ) {
if ( isset ( $post [ 0 ])) {
# We prefer { :id => 1, :rating => 's' }, but accept ["123", {:rating => 's'}], since that's
# what we'll get from HTML forms.
$post_id = $post [ 0 ];
$post = $post [ 1 ];
} else {
$post_id = $post [ 'id' ];
unset ( $post [ 'id' ]);
}
$p = Post :: find ( $post_id );
$ids [] = $p -> id ;
# If an entry has only an ID, it was just included in the list to receive changes to
# a post without changing it (for example, to receive the parent's data after reparenting
# a post under it).
if ( empty ( $post )) continue ;
$old_parent_id = $p -> parent_id ;
Post :: filter_api_changes ( $post );
if ( $p -> updateAttributes ( array_merge ( $post , array ( 'updater_user_id' => $user_id , 'updater_ip_addr' => $this -> request () -> remoteIp ())))) {
// post.merge(:updater_user_id => user_id, :updater_ip_addr => request.remoteIp))
# Reload the post to send the new status back; not all changes will be reflected in
# @post due to after_save changes.
// $p->reload();
}
if ( $p -> parent_id != $old_parent_id ) {
$p -> parent_id && $ids [] = $p -> parent_id ;
$old_parent_id && $ids [] = $old_parent_id ;
}
}
# Updates to one post may affect others, so only generate the return list after we've already
# updated everything.
# TODO: need better SQL functions.
$ids = implode ( ', ' , $ids );
$posts = Post :: where ( " id IN ( $ids ) " ) -> take ();
$api_data = Post :: batch_api_data ( $posts -> members ());
$url = $this -> params () -> url ? : '#index' ;
$this -> respond_to_success ( " Posts updated " , $url , array ( 'api' => $api_data ));
}
public function delete ()
{
$this -> post = Post :: find ( $this -> params () -> id );
if ( $this -> post && $this -> post -> parent_id )
$this -> post_parent = Post :: find ( $this -> post -> parent_id );
else
$this -> post_parent = null ;
}
public function destroy ()
{
if ( $this -> params () -> commit == " Cancel " ) {
$this -> redirectTo ( array ( '#show' , 'id' => $this -> params () -> id ));
return ;
}
$this -> post = Post :: find ( $this -> params () -> id );
if ( $this -> post -> can_user_delete ( current_user ())) {
if ( $this -> post -> status == " deleted " ) {
if ( $this -> params () -> destroy ) {
if ( current_user () -> is_mod_or_higher ()) {
$this -> post -> delete_from_database ();
$this -> respond_to_success ( " Post deleted permanently " , array ( '#show' , 'id' => $this -> params () -> id ));
} else {
$this -> access_denied ();
}
} else {
$this -> respond_to_success ( " Post already deleted " , array ( '#delete' , 'id' => $this -> params () -> id ));
}
} else {
Post :: static_destroy_with_reason ( $this -> post -> id , $this -> params () -> reason , current_user ());
$notice = " Post deleted " ;
if ( $this -> request () -> format () == 'json' )
$options = [ 'api' => Post :: batch_api_data ([ $this -> post ])];
else
$options = [];
$this -> respond_to_success ( $notice , array ( '#show' , 'id' => $this -> params () -> id ), $options );
}
} else {
$this -> access_denied ();
}
}
public function deletedIndex ()
{
if ( ! current_user () -> is_anonymous () && $this -> params () -> user_id && ( int ) $this -> params () -> user_id == current_user () -> id ) {
current_user () -> updateAttribute ( 'last_deleted_post_seen_at' , date ( 'Y-m-d H:i:s' ));
}
$page = $this -> page_number ();
$query = Post :: order ( " flagged_post_details.created_at DESC " )
-> select ( 'posts.*' )
-> group ( 'posts.id' )
-> joins ( " JOIN flagged_post_details ON flagged_post_details.post_id = posts.id " )
-> page ( $page ) -> perPage ( 25 );
if ( $this -> params () -> user_id ) {
$user_id = ( int ) $this -> params () -> user_id ;
$this -> posts = $query -> where ( " posts.status = 'deleted' AND posts.user_id = ? " , $user_id ) -> paginate ();
} else {
$this -> posts = $query -> where ( " posts.status = 'deleted' " ) -> paginate ();
}
}
public function acknowledgeNewDeletedPosts ()
{
if ( ! current_user () -> is_anonymous ())
current_user () -> updateAttribute ( 'last_deleted_post_seen_at' , date ( 'Y-m-d H:i:s' ));
$this -> respond_to_success ( " Success " , array ());
}
public function index ()
{
$tags = $this -> params () -> tags ;
$split_tags = $tags ? array_filter ( explode ( ' ' , $tags )) : array ();
$page = $this -> page_number ();
$this -> tag_suggestions = $this -> searching_pool = array ();
/* if $this -> current_user . is_member_or_lower ? && count ( split_tags ) > 2
# $this->respond_to_error("You can only search up to two tags at once with a basic account", 'action' => "error")
# return;
# elseif count(split_tags) > 6
*/
if ( count ( $split_tags ) > CONFIG () -> tag_query_limit ) {
$this -> respond_to_error ( " You can only search up to " . CONFIG () -> tag_query_limit . " tags at once " , " #error " );
return ;
}
$q = Tag :: parse_query ( $tags );
$limit = ( int ) $this -> params () -> limit ;
isset ( $q [ 'limit' ]) && $limit = ( int ) $q [ 'limit' ];
$limit <= 0 && $limit = CONFIG () -> post_index_default_limit ;
$limit > 1000 && $limit = 1000 ;
$count = 0 ;
$this -> set_title ( " / " . str_replace ( " _ " , " " , $tags ));
// try {
$count = Post :: fast_count ( $tags );
// vde($count);
// } catch(Exception $x) {
// $this->respond_to_error("Error: " . $x->getMessage(), "#error");
// return;
// }
$this -> ambiguous_tags = Tag :: select_ambiguous ( $split_tags );
if ( isset ( $q [ 'pool' ]) and is_int ( $q [ 'pool' ]))
$this -> searching_pool = Pool :: where ([ 'id' => $q [ 'pool' ]]) -> first ();
$from_api = ( $this -> params () -> format == " json " || $this -> params () -> format == " xml " );
// $this->posts = Post::find_all(array('page' => $page, 'per_page' => $limit, $count));
// $this->posts = WillPaginate::Collection.new(page, limit, count);
// $offset = $this->posts->offset();
// $posts_to_load = $this->posts->per_page();
$per_page = $limit ;
$offset = ( $page - 1 ) * $per_page ;
$posts_to_load = $per_page ;
if ( ! $from_api ) {
# For forward preloading:
// $posts_to_load += $this->posts->per_page();
$posts_to_load += $per_page ;
# If we're not on the first page, load the previous page for prefetching. Prefetching
# the previous page when the user is scanning forward should be free, since it'll already
# be in cache, so this makes scanning the index from back to front as responsive as from
# front to back.
if ( $page and $page > 1 ) {
// $offset -= $this->posts->per_page();
// $posts_to_load += $this->posts->per_page();
$offset -= $per_page ;
$posts_to_load += $per_page ;
}
}
$this -> showing_holds_only = isset ( $q [ 'show_holds' ]) && $q [ 'show_holds' ] == 'only' ;
list ( $sql , $params ) = Post :: generate_sql ( $q , array ( 'original_query' => $tags , 'from_api' => $from_api , 'order' => " p.id DESC " , 'offset' => $offset , 'limit' => $posts_to_load ));
$results = Post :: findBySql ( $sql , $params );
$this -> preload = new Rails\ActiveRecord\Collection ();
if ( ! $from_api ) {
if ( $page && $page > 1 ) {
$this -> preload = $results -> slice ( 0 , $limit );
$results = $results -> slice ( $limit );
}
$this -> preload -> merge ( $results -> slice ( $limit ));
$results = $results -> slice ( 0 , $limit );
}
# Apply can_be_seen_by filtering to the results. For API calls this is optional, and
# can be enabled by specifying filter=1.
if ( ! $from_api or $this -> params () -> filter ) {
$results -> deleteIf ( function ( $post ){ return ! $post -> can_be_seen_by ( current_user (), array ( 'show_deleted' => true ));});
$this -> preload -> deleteIf ( function ( $post ){ return ! $post -> can_be_seen_by ( current_user ());});
}
if ( $from_api and $this -> params () -> api_version == " 2 " and $this -> params () -> format != " json " ) {
$this -> respond_to_error ( " V2 API is JSON-only " , array (), array ( 'status' => 424 ));
return ;
}
$this -> posts = new Rails\ActiveRecord\Collection ( $results -> members (), [ 'page' => $page , 'perPage' => $per_page , 'totalRows' => $count ]);
if ( $count < CONFIG () -> post_index_default_limit && count ( $split_tags ) == 1 ) {
$this -> tag_suggestions = Tag :: find_suggestions ( $tags );
}
// exit;
$this -> respondTo ( array (
'html' => function () use ( $split_tags , $tags ) {
if ( $split_tags ) {
$this -> tags = Tag :: parse_query ( $tags );
} else {
$this -> tags = Rails :: cache () -> fetch ( '$poptags' , [ 'expires_in' => '1 hour' ], function () {
return array ( 'include' => Tag :: count_by_period ( date ( 'Y-m-d' , strtotime ( '-' . CONFIG () -> post_index_tags_limit )), date ( 'Y-m-d H:i:s' ), array ( 'limit' => 25 , 'exclude_types' => CONFIG () -> exclude_from_tag_sidebar )));
});
}
},
'xml' => function () {
$this -> setLayout ( false );
},
'json' => function () {
if ( $this -> params () -> api_version != " 2 " ) {
$this -> render ( array ( 'json' => array_map ( function ( $p ){ return $p -> api_attributes ();}, $this -> posts -> members ())));
return ;
}
$api_data = Post :: batch_api_data ( $this -> posts -> members (), array (
'exclude_tags' => $this -> params () -> include_tags != " 1 " ,
'exclude_votes' => $this -> params () -> include_votes != " 1 " ,
'exclude_pools' => $this -> params () -> include_pools != " 1 " ,
'fake_sample_url' => CONFIG () -> fake_sample_url
));
$this -> render ( array ( 'json' => json_encode ( $api_data )));
}
// ,
// 'atom'
));
}
// private function is_mobile_browser()
// {
// if ($agent = $this->request()->get("HTTP_USER_AGENT")) {
// return is_int(strpos($agent, "Android")) ||
// is_int(strpos($agent, "BlackBerry")) ||
// is_int(strpos($agent, "iPhone")) ||
// is_int(strpos($agent, "iPad")) ||
// is_int(strpos($agent, "iPod")) ||
// is_int(strpos($agent, "Opera Mini")) ||
// is_int(strpos($agent, "IEMobile"));
// }
// return false;
// }
// public function atom()
// {
// $this->posts = Post.findBySql(Post.generate_sql($this->params()->tags, 'limit' => 20, 'order' => "p.id DESC"))
// $this->respondTo(array(
// format.atom { render 'index' }
// ));
// }
// public function piclens()
// {
// $this->posts = WillPaginate::Collection.create(page_number, 16, Post.fast_count($this->params()->tags)) do |pager|
// pager.replace(Post.findBySql(Post.generate_sql($this->params()->tags, 'order' => "p.id DESC", 'offset' => pager.offset, 'limit' => pager.per_page)))
// end
// $this->respondTo(array(
// format.rss
// ));
// }
public function show ()
{
$this -> helper ( 'Avatar' );
try {
if ( $this -> params () -> cache )
$this -> response () -> headers () -> add ( " Cache-Control " , " max-age=300 " );
$this -> cache = $this -> params () -> cache ; # temporary
$this -> body_only = ( int ) $this -> params () -> body == 1 ;
if ( $this -> params () -> md5 ) {
if ( ! $this -> post = Post :: where ([ 'md5' => strtolower ( $this -> params ())]) -> first ())
throw Rails\ActiveRecord\Exception\RecordNotFoundException ();
} else {
$this -> post = Post :: find ( $this -> params () -> id );
}
$this -> pools = Pool :: where ( " pools_posts.post_id = { $this -> post -> id } AND pools_posts.active " ) -> joins ( " JOIN pools_posts ON pools_posts.pool_id = pools.id " ) -> order ( " pools.name " ) -> select ( " pools.name, pools.id " ) -> take ();
if ( $this -> params () -> pool_id ) {
$this -> following_pool_post = PoolPost :: where ( " pool_id = ? AND post_id = ? " , $this -> params () -> pool_id , $this -> post -> id ) -> first ();
} else {
$this -> following_pool_post = PoolPost :: where ( " post_id = ? " , $this -> post -> id ) -> first ();
}
$this -> tags = array ( 'include' => $this -> post -> tags ());
$this -> include_tag_reverse_aliases = true ;
$this -> set_title ( str_replace ( '_' , ' ' , $this -> post -> title_tags ()));
$this -> respondTo ([
'html'
]);
} catch ( Rails\ActiveRecord\Exception\RecordNotFoundException $e ) {
$this -> respondTo ([
'html' => function () {
$this -> render ( array ( 'action' => 'show_empty' , 'status' => 404 ));
}
]);
}
}
public function browse ()
{
$this -> response () -> headers () -> add ( " Cache-Control " , " max-age=300 " );
$this -> setLayout ( " bare " );
}
public function view ()
{
$this -> redirectTo ([ " #show " , 'id' => $this -> params () -> id ]);
}
public function popularRecent ()
{
switch ( $this -> params () -> period ) {
case " 1w " :
$this -> period_name = " last week " ;
$period = '1 week' ;
break ;
case " 1m " :
$this -> period_name = " last month " ;
$period = '1 month' ;
break ;
case " 1y " :
$this -> period_name = " last year " ;
$period = '1 year' ;
break ;
default :
$this -> params () -> period = " 1d " ;
$this -> period_name = " last 24 hours " ;
$period = '1 day' ;
break ;
}
$this -> post_params = $this -> params () -> all ();
$end = time ();
$this -> start = strtotime ( '-' . $period );
$this -> set_title ( 'Exploring ' . $this -> period_name );
$this -> posts = Post :: where ( " status <> 'deleted' AND posts.index_timestamp >= ? AND posts.index_timestamp <= ? " , date ( 'Y-m-d' , $this -> start ), date ( 'Y-m-d' , $end )) -> order ( " score DESC " ) -> limit ( 20 ) -> take ();
$this -> respond_to_list ( " posts " );
}
public function popularByDay ()
{
if ( ! $this -> params () -> year || ! $this -> params () -> month || ! $this -> params () -> day ||
! ( $this -> day = @ strtotime ( $this -> params () -> year . '-' . $this -> params () -> month . '-' . $this -> params () -> day ))) {
$this -> day = strtotime ( 'this day' );
}
$this -> set_title ( 'Exploring ' . date ( 'Y' , $this -> day ) . '/' . date ( 'm' , $this -> day ) . '/' . date ( 'd' , $this -> day ));
$this -> posts = Post :: available () -> where ( 'created_at BETWEEN ? AND ?' , date ( 'Y-m-d' , $this -> day ), date ( 'Y-m-d' , strtotime ( '+1 day' , $this -> day ))) -> order ( " score DESC " ) -> limit ( 20 ) -> take ();
$this -> respond_to_list ( " posts " );
}
public function popularByWeek ()
{
if ( ! $this -> params () -> year || ! $this -> params () -> month || ! $this -> params () -> day ||
! ( $this -> start = strtotime ( 'this week' , @ strtotime ( $this -> params () -> year . '-' . $this -> params () -> month . '-' . $this -> params () -> day )))) {
$this -> start = strtotime ( 'this week' );
}
$this -> end = strtotime ( 'next week' , $this -> start );
$this -> set_title ( 'Exploring ' . date ( 'Y' , $this -> start ) . '/' . date ( 'm' , $this -> start ) . '/' . date ( 'd' , $this -> start ) . ' - ' . date ( 'Y' , $this -> end ) . '/' . date ( 'm' , $this -> end ) . '/' . date ( 'd' , $this -> end ));
$this -> posts = Post :: available () -> where ( 'created_at BETWEEN ? AND ?' , date ( 'Y-m-d' , $this -> start ), date ( 'Y-m-d' , $this -> end )) -> order ( 'score DESC' ) -> limit ( 20 ) -> take ();
$this -> respond_to_list ( " posts " );
}
public function popularByMonth ()
{
if ( ! $this -> params () -> year || ! $this -> params () -> month || ! ( $this -> start = @ strtotime ( $this -> params () -> year . '-' . $this -> params () -> month . '-1' ))) {
$this -> start = strtotime ( 'first day of this month' );
}
$this -> end = strtotime ( '+1 month' , $this -> start );
$this -> set_title ( 'Exploring ' . date ( 'Y' , $this -> start ) . '/' . date ( 'm' , $this -> start ));
$this -> posts = Post :: available () -> where ( 'created_at BETWEEN ? AND ?' , date ( 'Y-m-d' , $this -> start ), date ( 'Y-m-d' , $this -> end )) -> order ( 'score DESC' ) -> limit ( 20 ) -> take ();
$this -> respond_to_list ( " posts " );
}
// public function revertTags()
// {
// user_id = current_user()->id
// $this->post = Post.find($this->params()->id)
// $this->post.updateAttributes('tags' => (int)PostTagHistory.find($this->params()->history_id).tags, 'updater_user_id' => user_id, 'updater_ip_addr' => $this->request()->remoteIp())
// $this->respond_to_success("Tags reverted", '#show', 'id' => $this->post.id, 'tag_title' => $this->post.tag_title)
// }
public function vote ()
{
if ( $this -> params () -> score === null ) {
$vote = PostVote :: where ([ 'user_id' => current_user () -> id , 'post_id' => $this -> params () -> id ]) -> first ();
$score = $vote ? $vote -> score : 0 ;
$this -> respond_to_success ( " " , array (), array ( 'vote' => $score ));
return ;
}
$p = Post :: find ( $this -> params () -> id );
$score = ( int ) $this -> params () -> score ;
if ( ! current_user () -> is_mod_or_higher () && ( $score < 0 || $score > 3 )) {
$this -> respond_to_error ( " Invalid score " , array ( " #show " , 'id' => $this -> params () -> id , 'tag_title' => $p -> tag_title (), 'status' => 424 ));
return ;
}
$vote_successful = $p -> vote ( $score , current_user ());
$api_data = Post :: batch_api_data ( array ( $p ));
$api_data [ 'voted_by' ] = $p -> voted_by ();
if ( $vote_successful )
$this -> respond_to_success ( " Vote saved " , array ( " #show " , 'id' => $this -> params () -> id , 'tag_title' => $p -> tag_title ()), array ( 'api' => $api_data ));
else
$this -> respond_to_error ( " Already voted " , array ( " #show " , 'id' => $this -> params () -> id , 'tag_title' => $p -> tag_title ()), array ( 'api' => $api_data , 'status' => 423 ));
}
public function flag ()
{
$post = Post :: find ( $this -> params () -> id );
if ( $this -> params () -> unflag == '1' ) {
# Allow the user who flagged a post to unflag it.
#
# posts
# "approve" is used both to mean "unflag post" and "approve pending post".
if ( $post -> status != " flagged " ) {
$this -> respond_to_error ( " Can only unflag flagged posts " , array ( " #show " , 'id' => $this -> params () -> id ));
return ;
}
if ( ! current_user () -> is_mod_or_higher () and current_user () -> id != $post -> flag_detail -> user_id ) {
$this -> access_denied ();
return ;
}
$post -> approve ( current_user () -> id );
$message = " Post approved " ;
} else {
if ( $post -> status != " active " ) {
$this -> respond_to_error ( " Can only flag active posts " , array ( " #show " , 'id' => $this -> params () -> id ));
return ;
}
$post -> flag ( $this -> params () -> reason , current_user () -> id );
$message = " Post flagged " ;
}
# Reload the post to pull in post.flag_reason.
$post -> reload ();
if ( $this -> request () -> format () == " json " || $this -> request () -> format () == " xml " )
$api_data = Post :: batch_api_data ( array ( $post ));
else
$api_data = [];
$this -> respond_to_success ( $message , array ( " #show " , 'id' => $this -> params () -> id ), array ( 'api' => $api_data ));
}
public function random ()
{
$max_id = Post :: maximum ( 'id' );
foreach ( range ( 1 , 10 ) as $i ) {
$post = Post :: where ( " id = ? AND status <> 'deleted' " , rand ( 1 , $max_id ) + 1 ) -> first ();
if ( $post && $post -> can_be_seen_by ( current_user ())) {
$this -> redirectTo ( array ( '#show' , 'id' => $post -> id , 'tag_title' => $post -> tag_title ()));
return ;
}
}
$this -> notice ( " Couldn't find a post in 10 tries. Try again. " );
$this -> redirectTo ( " #index " );
}
public function similar ()
{
$params = array_merge ([
'file' => null ,
'url' => null ,
'id' => $this -> params () -> id ,
'search_id' => null ,
'services' => null ,
'threshold' => null ,
'forcegray' => null ,
'initial' => null ,
'width' => null ,
'height' => null
], $this -> params () -> toArray ());
if ( ! empty ( $params [ 'data_search' ]) && ! current_user () -> is_mod_or_higher ())
unset ( $params [ 'data_search' ]);
if ( ! SimilarImages :: valid_saved_search ( $params [ 'search_id' ])) $params [ 'search_id' ] = null ;
if ( ! empty ( $params [ 'width' ])) $params [ 'width' ] = ( int ) $params [ 'width' ];
if ( ! empty ( $params [ 'height' ])) $params [ 'height' ] = ( int ) $params [ 'height' ];
$this -> initial = $params [ 'initial' ];
if ( $this -> initial && ! $params [ 'services' ]) {
$params [ 'services' ] = " local " ;
}
$this -> services = SimilarImages :: get_services ( $params [ 'services' ]);
if ( $this -> params () -> id ) {
$this -> compared_post = Post :: find ( $this -> params () -> id );
} else {
$this -> compared_post = new Post ();
}
$this -> errors = null ;
$this -> posts = Post :: emptyCollection ();
$this -> similar = [];
$similarity = [];
if ( $this -> compared_post && $this -> compared_post -> is_deleted ()) {
$this -> respond_to_error ( " Post deleted " , [ 'post#show' , 'id' => $this -> params () -> id , 'tag_title' => $this -> compared_post -> tag_title ()]);
return ;
}
# We can do these kinds of searches:
#
# File: Search from a specified file. The image is saved locally with an ID, and sent
# as a file to the search servers.
#
# URL: search from a remote URL. The URL is downloaded, and then treated as a :file
# search. This way, changing options doesn't repeatedly download the remote image,
# and it removes a layer of abstraction when an error happens during download
# compared to having the search server download it.
#
# Post ID: Search from a post ID. The preview image is sent as a URL.
#
# Search ID: Search using an image uploaded with a previous File search, using
# the search MD5 created. We're not allowed to repopulate filename fields in the
# user's browser, so we can't re-submit the form as a file search when changing search
# parameters. Instead, we hide the search ID in the form, and use it to recall the
# file from before. These files are expired after a while; we check for expired files
# when doing later searches, so we don't need a cron job.
$search = function ( array $params ) {
$options = array_merge ( $params , [
'services' => $this -> services ,
]);
# Check search_id first, so options links that include it will use it. If the
# user searches with the actual form, search_id will be cleared on submission.
if ( ! empty ( $params [ 'search_id' ])) {
$file_path = SimilarImages :: find_saved_search ( $params [ 'search_id' ]);
if ( ! $file_path ) {
# The file was probably purged. Delete :search_id before redirecting, so the
# error doesn't loop.
// unset($params.delete(:search_id)
return [ 'errors' => [ 'error' => " Search expired " ] ];
}
} elseif ( ! empty ( $params [ 'url' ]) || ! empty ( $_FILES [ 'file' ][ 'tmp_name' ])) {
# Save the file locally.
try {
if ( ! empty ( $params [ 'url' ])) {
$file = Danbooru :: http_get_streaming ( $params [ 'url' ]);
$search = SimilarImages :: save_search ( $file );
} else { # file
$file = file_get_contents ( $_FILES [ 'file' ][ 'tmp_name' ]);
$search = SimilarImages :: save_search ( $file );
$options [ 'type' ] = 'file' ;
}
} catch ( /*SocketError, URI::Error, SystemCallError,*/ Moebooru\Exception\ResizeErrorException $e ) {
return [ 'errors' => [ 'error' => $e -> getMessage () ] ];
} catch ( Danbooru\Exception\ExceptionInterface $e ) {
return [ 'errors' => [ 'error' => $e -> getMessage () ] ];
}
// } rescue Timeout::Error => e
// return { :errors => { :error => "Download timed out" } }
// end
$file_path = $search [ 'file_path' ];
# Set :search_id in params for generated URLs that point back here.
$params [ 'search_id' ] = $search [ 'search_id' ];
# The :width and :height params specify the size of the original image, for display
# in the results. The user can specify them; if not specified, fill it in.
empty ( $params [ 'width' ]) && $params [ 'width' ] = $search [ 'original_width' ];
empty ( $params [ 'height' ]) && $params [ 'height' ] = $search [ 'original_height' ];
} elseif ( ! empty ( $params [ 'id' ])) {
$options [ 'source' ] = $this -> compared_post ;
$options [ 'type' ] = 'post' ;
}
if ( ! empty ( $params [ 'search_id' ])) {
$options [ 'source' ] = $file_path ;
$options [ 'source_filename' ] = $params [ 'search_id' ];
$options [ 'source_thumb' ] = " /data/search/ " . $params [ 'search_id' ];
$options [ 'type' ] = 'file' ;
}
$options [ 'width' ] = $params [ 'width' ];
$options [ 'height' ] = $params [ 'height' ];
if ( $options [ 'type' ] == 'file' )
SimilarImages :: cull_old_searches ();
return SimilarImages :: similar_images ( $options );
};
$this -> searched = false ;
if ( $this -> params () -> url || $this -> params () -> id || ( ! empty ( $_FILES [ 'file' ]) && empty ( $_FILES [ 'file' ][ 'error' ])) || ! empty ( $this -> params () -> search_id )) {
$res = $search ( $params );
# Error when no service was selected and/or local search isn't supported
if ( is_string ( $res [ 'errors' ])) {
$this -> notice ( $res [ 'errors' ]);
} else {
$this -> errors = ! empty ( $res [ 'errors' ]) ? $res [ 'errors' ] : [];
$this -> searched = true ;
$this -> search_id = $this -> params () -> search_id ;
# Never pass :file on through generated URLs.
$params [ 'file' ] = null ;
}
} else {
$res = [];
$this -> errors = [];
}
if ( $res && $this -> searched ) {
! empty ( $res [ 'posts' ]) && $this -> posts = $res [ 'posts' ];
$this -> similar = $res ;
! empty ( $res [ 'similarity' ]) && $similarity = $res [ 'similarity' ];
}
if ( $this -> request () -> format () == " json " || $this -> request () -> format () == " xml " ) {
if ( ! empty ( $this -> errors [ 'error' ])) {
$this -> respond_to_error ( $this -> errors [ 'error' ], [ '#index' ], [ 'status' => 503 ]);
return ;
}
if ( ! $this -> searched ) {
$this -> respond_to_error ( " no search supplied " , [ '#index' ], [ 'status' => 503 ]);
return ;
}
}
$this -> respondTo ([
'html' => function () use ( $similarity , $res ) {
if ( $this -> initial && ! $this -> posts -> any ()) {
// flash.keep
$this -> redirectTo ([ 'post#show' , 'id' => $this -> params () -> id , 'tag_title' => $this -> compared_post -> tag_title ()]);
return ;
}
if ( ! empty ( $this -> errors [ 'error' ])) {
$this -> notice ( $this -> errors [ 'error' ]);
}
if ( $this -> searched ) {
! empty ( $res [ 'posts_external' ]) && $this -> posts -> merge ( $res [ 'posts_external' ]);
$this -> posts -> sort ( function ( $a , $b ) use ( $similarity ) {
$aid = spl_object_hash ( $a );
$bid = spl_object_hash ( $b );
if ( $similarity [ $aid ] == $similarity [ $bid ])
return 0 ;
elseif ( $similarity [ $aid ] > $similarity [ $bid ])
return 1 ;
return - 1 ;
});
# Add the original post to the start of the list.
if ( ! empty ( $res [ 'source' ]))
$this -> posts [] = $res [ 'source' ];
elseif ( ! empty ( $res [ 'external_source' ]))
$this -> posts [] = $res [ 'external_source' ];
}
},
'json' => function () use ( $res ) {
foreach ( $this -> posts as $post )
$post -> similarity = $res [ 'similarity' ][ spl_object_hash ( $post )];
if ( ! empty ( $res [ 'posts_external' ])) {
foreach ( $res [ 'posts_external' ] as $post )
$post -> similarity = $res [ 'similarity' ][ spl_object_hash ( $post )];
$this -> posts -> merge ( $res [ 'posts_external' ]);
}
$api_data = [
'posts' => $this -> posts ,
'search_id' => $this -> params () -> search_id
];
if ( ! empty ( $res [ 'source' ]))
$api_data [ 'source' ] = $res [ 'source' ];
elseif ( ! empty ( $res [ 'external_source' ]))
$api_data [ 'source' ] = $res [ 'external_source' ];
else
$api_data [ 'source' ] = '' ;
if ( ! empty ( $res [ 'errors' ])) {
$api_data [ 'error' ] = [];
foreach ( $res [ 'errors' ] as $server => $error ) {
$services = ! empty ( $error [ 'services' ]) ? implode ( ', ' , $error [ 'services' ]) : array ();
$api_data [ 'error' ][] = [ 'server' => $server , 'message' => $error [ 'message' ], 'services' => $services ];
}
}
$this -> respond_to_success ( '' , [], [ 'api' => $api_data ]);
}
// fmt.xml do
// x = Builder::XmlMarkup.new('indent' => 2)
// x.instruct!
// $this->render(array('xml' => x.posts() {)
// unless res[:errors].empty?
// res[:errors].map { |server, error|
// { :server=>server, :message=>error[:message], :services=>error[:services].join(",") }.to_xml('root' => "error", 'builder' => x, 'skip_instruct' => true)
// }
// end
// if (res[:source]) {
// x.source() {
// res[:source].to_xml('builder' => x, 'skip_instruct' => true)
// }
// } else {
// x.source() {
// res[:external_source].to_xml('builder' => x, 'skip_instruct' => true)
// }
// }
// $this->posts.each { |e|
// x.similar(:similarity=>res[:similarity][e]) {
// e.to_xml('builder' => x, 'skip_instruct' => true)
// }
// }
// res[:posts_external].each { |e|
// x.similar(:similarity=>res[:similarity][e]) {
// e.to_xml('builder' => x, 'skip_instruct' => true)
// }
// }
// }
]);
$this -> params = $params ;
}
public function undelete ()
{
$post = Post :: where ([ 'id' => $this -> params () -> id ]) -> first ();
if ( ! $post ) {
$this -> respond_to_error ( " Post not found " , [ '#show' , 'id' => $this -> params () -> id ]);
return ;
}
$post -> undelete ();
$affected_posts = [ $post ];
if ( $post -> parent_id )
$affected_posts [] = $post -> get_parent ();
if ( $this -> params () -> format == " json " || $this -> params () -> format == " xml " )
$api_data = Post :: batch_api_data ( $affected_posts );
else
$api_data = [];
$this -> respond_to_success ( " Post was undeleted " , [ '#show' , 'id' => $this -> params () -> id ], [ 'api' => $api_data ]);
}
public function error ()
{
}
public function exception ()
{
throw new Exception ();
}
public function import ()
{
$import_dir = Rails :: publicPath () . '/data/import/' ;
if ( $this -> request () -> isPost ()) {
if ( $this -> request () -> format () == 'json' ) {
foreach ( explode ( '::' , $this -> params () -> dupes ) as $file ) {
$file = stripslashes ( utf8_decode ( $file ));
$file = $import_dir . $file ;
if ( file_exists ( $file )) {
unlink ( $file );
} else {
$error = true ;
}
}
$resp = ! empty ( $error ) ? array ( 'reason' => 'Some files could not be deleted' ) : array ( 'success' => true );
$this -> render ( array ( 'json' => $resp ));
return ;
}
$this -> setLayout ( false );
$this -> errors = $this -> dupe = false ;
$post = $this -> params () -> post ;
$post [ 'filename' ] = stripslashes ( utf8_decode ( $post [ 'filename' ]));
$filepath = $import_dir . $post [ 'filename' ];
# Take folders as tags
if ( is_int ( strpos ( $post [ 'filename' ], '/' ))) {
$folders = str_replace ( '#' , ':' , $post [ 'filename' ]);
$tags = array_filter ( array_unique ( array_merge ( explode ( ' ' , $post [ 'tags' ]), explode ( '/' , $folders ))));
array_pop ( $tags );
$post [ 'tags' ] = trim ( $post [ 'tags' ] . ' ' . implode ( ' ' , $tags ));
}
$post = array_merge ( $post , array (
'ip_addr' => $this -> request () -> remoteIp (),
'user_id' => current_user () -> id ,
'status' => 'active' ,
'tempfile_path' => $filepath ,
'tempfile_name' => $post [ 'filename' ],
'is_import' => true ,
));
unset ( $post [ 'filename' ], $post [ 'i' ]);
$this -> post = Post :: create ( $post );
if ( $this -> post -> errors () -> blank ()) {
$this -> import_status = 'Posted' ;
} elseif ( $this -> post -> errors () -> invalid ( 'md5' )) {
$this -> dupe = true ;
$this -> import_status = 'Already exists' ;
$this -> post = Post :: where ([ 'md5' => $this -> post -> md5 ]) -> first ();
$this -> post -> status = 'flagged' ;
} else {
$this -> errors = $this -> post -> errors () -> fullMessages ( '<br />' );
$this -> import_status = 'Error' ;
}
} else {
$this -> set_title ( 'Import' );
$this -> invalid_files = $this -> files = [];
list ( $this -> files , $this -> invalid_files , $this -> invalid_folders ) = Post :: get_import_files ( $import_dir );
$pools = Pool :: where ( 'is_active' ) -> take ();
if ( $pools ) {
$this -> pool_list = '<datalist id="pool_list">' ;
foreach ( $pools as $pool )
$this -> pool_list .= '<option value="' . str_replace ( '_' , ' ' , $pool -> name ) . '" />' ;
$this -> pool_list .= '</datalist>' ;
} else
$this -> pool_list = null ;
}
}
public function searchExternalData ()
{
if ( ! CONFIG () -> enable_find_external_data )
throw new Rails\ActiveRecord\Exception\RecordNotFoundException ();
if ( $this -> params () -> ids ) {
$ids = $this -> params () -> ids ;
! is_array ( $ids ) && $ids = [ $ids ];
$this -> posts = Post :: where ( 'id IN (?)' , $ids ) -> take ();
} else {
$this -> posts = new Rails\ActiveRecord\Collection ();
}
$this -> services = SimilarImages :: get_services ( 'all' );
}
// public function download()
// {
// require 'base64'
// data = $this->params()->data
// filename = $this->params()->filename
// type = $this->params()->type
// if (filename.nil?) {
// filename = "file"
// }
// if (type.nil?) {
// type = "application/octet-stream"
// }
// data = Base64.decode64(data)
// send_data data, 'filename' => filename, 'disposition' => "attachment", 'type' => type
// }
protected function init ()
{
$this -> helper ( 'Avatar' , 'Wiki' , 'Tag' , 'Comment' , 'Pool' , 'Favorite' , 'Advertisements' );
}
protected function filters ()
{
return [
'before' => [
'user_can_see_posts' => [ 'only' => [ 'show' , 'browse' ]],
'member_only' => [ 'only' => [ 'create' , 'destroy' , 'delete' , 'flag' , 'revert_tags' , 'activate' , 'update_batch' , 'vote' ]],
'post_member_only' => [ 'only' => [ 'update' , 'upload' , 'flag' ]],
'janitor_only' => [ 'only' => [ 'moderate' , 'undelete' ]],
'admin_only' => [ 'only' => [ 'import' ]],
'mod_only' => [ 'only' => [ 'search_external_data' ]]
],
'after' => [
'save_tags_to_cookie' => [ 'only' => [ 'update' , 'create' ]]
],
2013-11-08 17:37:29 +01:00
# iTODO :
2013-10-27 01:06:58 +02:00
'around' => [
// 'cache_action' => ['only' => ['index', 'atom', 'piclens']]
]
];
}
}