2013-10-27 01:06:58 +02:00
< ? php
trait PostSqlMethods
{
static public function find_by_tag_join ( $tag , array $options = array ())
{
$options = array_merge ( array (
'order' => " posts.id DESC " ,
'limit' => null ,
'offset' => null
), $options );
$tag = strtolower ( str_replace ( ' ' , '_' , $tag ));
$posts = self :: where ( " tags.name = ? " , $tag ) -> select ( " posts.* " ) -> joins ( " JOIN posts_tags ON posts_tags.post_id = posts.id JOIN tags ON tags.id = posts_tags.tag_id " ) -> limit ( $options [ 'limit' ]) -> offset ( $options [ 'offset' ]) -> order ( $options [ 'order' ]) -> take ();
if ( ! $posts )
$posts = new ModelCollection ( array ());
return $posts ;
}
static public function generate_sql_range_helper ( $arr , $field , & $c , & $params = null )
{
$query_passed = is_object ( $c );
switch ( $arr [ 0 ]) {
case 'eq' :
if ( $query_passed )
$c -> where ( $field . " = ? " , $arr [ 1 ]);
else {
$c [] = $field . " = ? " ;
$params [] = $arr [ 1 ];
}
break ;
case 'gt' :
if ( $query_passed )
$c -> where ( $field . " > ? " , $arr [ 1 ]);
else {
$c [] = $field . " > ? " ;
$params [] = $arr [ 1 ];
}
break ;
case 'gte' :
if ( $query_passed )
$c -> where ( $field . " >= ? " , $arr [ 1 ]);
else {
$c [] = $field . " >= ? " ;
$params [] = $arr [ 1 ];
}
break ;
case 'lt' :
if ( $query_passed )
$c -> where ( $field . " < ? " , $arr [ 1 ]);
else {
$c [] = $field . " < ? " ;
$params [] = $arr [ 1 ];
}
break ;
case 'lte' :
if ( $query_passed )
$c -> where ( $field . " <= ? " , $arr [ 1 ]);
else {
$c [] = $field . " <= ? " ;
$params [] = $arr [ 1 ];
}
break ;
case 'between' :
if ( $query_passed )
$c -> where ( $field . " BETWEEN ? AND ? " , $arr [ 1 ], $arr [ 2 ]);
else {
$c [] = $field . " BETWEEN ? AND ? " ;
$params [] = $arr [ 1 ];
$params [] = $arr [ 2 ];
}
break ;
case 'in' :
if ( $query_passed )
$c -> where ( $field . " IN ( " . implode ( ', ' , array_fill ( 0 , count ( $arr [ 1 ]), '?' )) . " ) " , current ( $arr [ 1 ]));
else {
$c [] = $field . " IN ( " . implode ( ', ' , array_fill ( 0 , count ( $arr [ 1 ]), '?' )) . " ) " ;
$params [] = current ( $arr [ 1 ]);
}
break ;
}
}
static public function generate_sql_escape_helper ( $array )
{
return array_filter ( $array );
}
// def generate_sql_escape_helper(array)
// array.map do |token|
// next if token.empty?
// escaped_token = token.gsub(/\\|'/, '\0\0\0\0').gsub("?", "\\\\77").gsub("%", "\\\\45")
// "''" + escaped_token + "''"
// end.compact
// end
static public function generate_sql ( $q , $options = array ())
{
if ( is_array ( $q )) {
$original_query = isset ( $options [ 'original_query' ]) ? $options [ 'original_query' ] : null ;
} else {
$original_query = $q ;
$q = Tag :: parse_query ( $q );
}
# Filling default values.
$q = array_merge ( array_fill_keys ( array ( 'md5' , 'ext' , 'source' , 'fav' , 'user' , 'rating' , 'rating_negated' , 'unlocked_rating' , 'show_holds' , 'shown_in_index' , 'exclude' , 'related' , 'post_id' , 'mpixels' , 'width' , 'height' , 'score' , 'date' , 'change' ), null ), $q );
$options = array_merge ( array_fill_keys ( array ( 'pending' , 'flagged' , 'from_api' , 'limit' , 'offset' , 'count' , 'select' , 'having' ), null ), $options );
$conds = array ( 'true' );
$joins = array (
'posts p' ,
);
$join_params = array ();
$cond_params = array ();
if ( ! empty ( $q [ 'error' ]))
$conds [] = " FALSE " ;
self :: generate_sql_range_helper ( $q [ 'post_id' ], " p.id " , $conds , $cond_params );
self :: generate_sql_range_helper ( $q [ 'mpixels' ], " p.width*p.height/1000000.0 " , $conds , $cond_params );
self :: generate_sql_range_helper ( $q [ 'width' ], " p.width " , $conds , $cond_params );
self :: generate_sql_range_helper ( $q [ 'height' ], " p.height " , $conds , $cond_params );
self :: generate_sql_range_helper ( $q [ 'score' ], " p.score " , $conds , $cond_params );
self :: generate_sql_range_helper ( $q [ 'date' ], " DATE(p.created_at) " , $conds , $cond_params );
self :: generate_sql_range_helper ( $q [ 'change' ], " p.change_seq " , $conds , $cond_params );
if ( is_string ( $q [ 'md5' ])) {
$conds [] = " p.md5 IN (?) " ;
$cond_params [] = explode ( ',' , $q [ 'md5' ]);
}
if ( is_string ( $q [ 'ext' ])) {
$conds [] = " p.file_ext IN (?) " ;
$cond_params [] = explode ( ',' , strtolower ( $q [ 'ext' ]));
}
if ( isset ( $q [ 'show_deleted_only' ])) {
if ( $q [ 'show_deleted_only' ])
$conds [] = " p.status = 'deleted' " ;
} elseif ( empty ( $q [ 'post_id' ])) {
# If a specific post_id isn't specified, default to filtering deleted posts.
$conds [] = " p.status <> 'deleted' " ;
}
if ( isset ( $q [ 'parent_id' ]) && is_numeric ( $q [ 'parent_id' ])) {
$conds [] = " (p.parent_id = ? or p.id = ?) " ;
$cond_params [] = $q [ 'parent_id' ];
$cond_params [] = $q [ 'parent_id' ];
} elseif ( isset ( $q [ 'parent_id' ]) && $q [ 'parent_id' ] == false ) {
$conds [] = " p.parent_id is null " ;
}
if ( is_string ( $q [ 'source' ])) {
$conds [] = " lower(p.source) LIKE lower(?) " ;
$cond_params [] = $q [ 'source' ];
}
2014-01-12 13:05:06 +01:00
if ( isset ( $q [ 'subscriptions' ])) {
preg_match ( '/^(.+?):(.+)$/' , $q [ 'subscriptions' ], $m );
$username = $m [ 1 ] ? : $q [ 'subscriptions' ];
$subscription_name = $m [ 2 ];
$user = User :: find_by_name ( $username );
if ( $user ) {
if ( $post_ids = TagSubscription :: find_post_ids ( $user -> id , $subscription_name )) {
$conds [] = 'p.id IN (?)' ;
$cond_params [] = $post_ids ;
}
}
}
2013-10-27 01:06:58 +02:00
if ( is_string ( $q [ 'fav' ])) {
$joins [] = " JOIN favorites f ON f.post_id = p.id JOIN users fu ON f.user_id = fu.id " ;
$conds [] = " lower(fu.name) = lower(?) " ;
$cond_params [] = $q [ 'fav' ];
}
if ( isset ( $q [ 'vote_negated' ])) {
$joins [] = " LEFT JOIN post_votes v ON p.id = v.post_id AND v.user_id = ? " ;
$join_params [] = $q [ 'vote_negated' ];
$conds [] = " v.score IS NULL " ;
}
if ( isset ( $q [ 'vote' ])) {
$joins [] = " JOIN post_votes v ON p.id = v.post_id " ;
// $conds[] = sprintf("v.user_id = %d", $q['vote'][1]);
$conds [] = 'v.user_id = ?' ;
$cond_params [] = $q [ 'vote' ][ 1 ];
self :: generate_sql_range_helper ( $q [ 'vote' ][ 0 ], " v.score " , $conds , $cond_params );
}
if ( is_string ( $q [ 'user' ])) {
2013-11-03 19:15:36 +01:00
$joins [] = " JOIN users u ON p.user_id = u.id " ;
2013-10-27 01:06:58 +02:00
$conds [] = " lower(u.name) = lower(?) " ;
$cond_params [] = $q [ 'user' ];
}
if ( isset ( $q [ 'exclude_pools' ])) {
foreach ( array_keys ( $q [ 'exclude_pools' ]) as $i ) {
if ( is_int ( $q [ 'exclude_pools' ][ $i ])) {
$joins [] = " LEFT JOIN pools_posts ep ${ i } ON (ep ${ i } .post_id = p.id AND ep ${ i } .pool_id = ?) " ;
$join_params [] = $q [ 'exclude_pools' ][ $i ];
$conds [] = " ep ${ i } .id IS NULL " ;
}
if ( is_string ( $q [ 'exclude_pools' ][ $i ])) {
$joins [] = " LEFT JOIN pools_posts ep ${ i } ON ep ${ i } .post_id = p.id LEFT JOIN pools epp ${ i } ON (ep ${ i } .pool_id = epp ${ i } .id AND LOWER(epp ${ i } .name) LIKE ?) " ;
$join_params [] = " % " . strtolower ( $q [ 'exclude_pools' ][ $i ]) . " % " ;
$conds [] = " ep ${ i } .id IS NULL " ;
}
}
}
if ( isset ( $q [ 'pool' ])) {
$conds [] = " pools_posts.active = true " ;
if ( ! isset ( $q [ 'order' ]))
$paramsool_ordering = " ORDER BY pools_posts.pool_id ASC, CAST(pools_posts.sequence AS UNSIGNED), pools_posts.post_id " ;
if ( is_int ( $q [ 'pool' ])) {
$joins [] = " JOIN pools_posts ON pools_posts.post_id = p.id JOIN pools ON pools_posts.pool_id = pools.id " ;
$conds [] = " pools.id = " . $q [ 'pool' ];
}
if ( is_string ( $q [ 'pool' ])) {
if ( $q [ 'pool' ] == " * " )
$joins [] = " JOIN pools_posts ON pools_posts.post_id = p.id JOIN pools ON pools_posts.pool_id = pools.id " ;
else {
$joins [] = " JOIN pools_posts ON pools_posts.post_id = p.id JOIN pools ON pools_posts.pool_id = pools.id " ;
$conds [] = " LOWER(pools.name) LIKE ? " ;
$cond_params [] = " % " . strtolower ( $q [ 'pool' ]) . " % " ;
}
}
}
# http://stackoverflow.com/questions/8106547/how-to-search-on-mysql-using-joins/8107017
$tags_index_query = array ();
if ( ! empty ( $q [ 'include' ]) && ( $tags_include = self :: generate_sql_escape_helper ( $q [ 'include' ]))) {
$joins [] = 'INNER JOIN posts_tags pti ON p.id = pti.post_id JOIN tags ti ON pti.tag_id = ti.id' ;
$tags_index_query [] = 'ti.name IN (' . implode ( ', ' , array_fill ( 0 , count ( $tags_include ), '?' )) . ')' ;
$cond_params = array_merge ( $cond_params , $tags_include );
}
if ( ! empty ( $q [ 'related' ])) {
if ( count ( $q [ 'exclude' ]) > CONFIG () -> tag_query_limit ) {
throw new Exception ( " You cannot search for more than " . CONFIG () -> tag_query_limit . " tags at a time " );
}
$tags_index_query [] = '(' . implode ( ', ' , array_map ( function ( $v , $k ){ return 't' . ( $k + 1 ) . '.name' ;}, $q [ 'related' ], array_keys ( $q [ 'related' ]))) . ') = (' . implode ( ', ' , array_fill ( 0 , count ( $q [ 'related' ]), '?' )) . ')' ;
$cond_params = array_merge ( $cond_params , $q [ 'related' ]);
$joins [] = implode ( ' ' , array_map ( function ( $k ){ return 'INNER JOIN posts_tags pt' . ( $k + 1 ) . ' ON p.id = pt' . ( $k + 1 ) . '.post_id INNER JOIN tags t' . ( $k + 1 ) . ' ON pt' . ( $k + 1 ) . '.tag_id = t' . ( $k + 1 ) . '.id' ;}, array_keys ( $q [ 'related' ])));
}
if ( ! empty ( $q [ 'exclude' ])) {
if ( count ( $q [ 'exclude' ]) > CONFIG () -> tag_query_limit ) {
throw new Exception ( " You cannot search for more than " . CONFIG () -> tag_query_limit . " tags at a time " );
}
$tags_index_query [] = ' NOT EXISTS
( SELECT *
FROM posts_tags pt
INNER JOIN tags t ON pt . tag_id = t . id
WHERE p . id = pt . post_id
AND t . name IN ( '.implode(' , ', array_fill(0, count($q[' exclude ']), ' ? ')).' )
) ' ;
$cond_params = array_merge ( $cond_params , $q [ 'exclude' ]);
}
if ( ! empty ( $tags_index_query )) {
$conds [] = implode ( ' AND ' , $tags_index_query );
}
if ( is_string ( $q [ 'rating' ])) {
$r = strtolower ( substr ( $q [ 'rating' ], 0 , 1 ));
if ( $r == " s " )
$conds [] = " p.rating = 's' " ;
elseif ( $r == " q " )
$conds [] = " p.rating = 'q' " ;
elseif ( $r == " e " )
$conds [] = " p.rating = 'e' " ;
}
if ( is_string ( $q [ 'rating_negated' ])) {
$r = strtolower ( substr ( $q [ 'rating_negated' ], 0 , 1 ));
if ( $r == " s " )
$conds [] = " p.rating <> 's' " ;
elseif ( $r == " q " )
$conds [] = " p.rating <> 'q' " ;
elseif ( $r == " e " )
$conds [] = " p.rating <> 'e' " ;
}
if ( $q [ 'unlocked_rating' ] == true )
$conds [] = " p.is_rating_locked = FALSE " ;
if ( isset ( $options [ 'flagged' ]))
$conds [] = " p.status = 'flagged' " ;
if ( isset ( $q [ 'show_holds' ])) {
if ( $q [ 'show_holds' ] == 'only' )
$conds [] = " p.is_held " ;
elseif ( $q [ 'show_holds' ] == 'hide' )
$conds [] = " NOT p.is_held " ;
elseif ( $q [ 'show_holds' ] == 'yes' ) { /*do nothing?*/ }
} else {
# Hide held posts by default only when not using the API.
if ( ! $options [ 'from_api' ])
$conds [] = " NOT p.is_held " ;
}
/**
* MyImouto : Moved the following condition here so only one
* of the conditions that set the pending status is met .
* Before this , in post #moderate, when searching for a user's
* pending posts , the SQL query would end up like this :
* ... AND p . status = 'pending' ... AND p . status <> 'pending' ...
*/
if ( isset ( $options [ 'pending' ]))
$conds [] = " p.status = 'pending' " ;
else {
if ( isset ( $q [ 'show_pending' ])) {
if ( $q [ 'show_pending' ] == 'only' )
$conds [] = " p.status = 'pending' " ;
elseif ( $q [ 'show_pending' ] == 'hide' )
$conds [] = " p.status <> 'pending' " ;
elseif ( $q [ 'show_pending' ] == 'yes' ) { /*do nothing?*/ }
} else {
# Hide pending posts by default only when not using the API.
if ( CONFIG () -> hide_pending_posts && ! isset ( $options [ 'from_api' ]))
$conds [] = " p.status <> 'pending' " ;
}
}
if ( isset ( $q [ 'shown_in_index' ])) {
if ( $q [ 'shown_in_index' ])
$conds [] = " p.is_shown_in_index " ;
else
$conds [] = " NOT p.is_shown_in_index " ;
} elseif ( ! $original_query && ! $options [ 'from_api' ]) {
# Hide not shown posts by default only when not using the API.
$conds [] = " p.is_shown_in_index " ;
}
$sql = " SELECT " ;
if ( $options [ 'count' ])
$sql .= " COUNT(*) " ;
elseif ( $options [ 'select' ])
$sql .= ' ' . $options [ 'select' ];
else
$sql .= " p.* " ;
$sql .= " FROM " . implode ( ' ' , $joins );
$sql .= " WHERE " . implode ( ' AND ' , $conds );
if ( ! $options [ 'count' ])
$sql .= ' GROUP BY p.id ' ;
if ( isset ( $q [ 'order' ]) && ! $options [ 'count' ]) {
if ( $q [ 'order' ] == " id " )
$sql .= " ORDER BY p.id " ;
elseif ( $q [ 'order' ] == " id_desc " )
$sql .= " ORDER BY p.id DESC " ;
elseif ( $q [ 'order' ] == " score " )
$sql .= " ORDER BY p.score DESC " ;
elseif ( $q [ 'order' ] == " score_asc " )
$sql .= " ORDER BY p.score " ;
elseif ( $q [ 'order' ] == " mpixels " )
# Use "w*h/1000000", even though "w*h" would give the same result, so this can use
# the posts_mpixels index.
$sql .= " ORDER BY width*height/1000000.0 DESC " ;
elseif ( $q [ 'order' ] == " mpixels_asc " )
$sql .= " ORDER BY width*height/1000000.0 " ;
elseif ( $q [ 'order' ] == " portrait " )
$sql .= " ORDER BY 1.0*width/GREATEST(1, height) " ;
elseif ( $q [ 'order' ] == " landscape " )
$sql .= " ORDER BY 1.0*width/GREATEST(1, height) DESC " ;
elseif ( $q [ 'order' ] == " portrait_pool " ) {
# We can only do this if we're searching for a pool.
if ( isset ( $q [ 'pool' ]))
$sql .= " ORDER BY 1.0*width / GREATEST(1, height), CAST(pools_posts.sequence AS UNSIGNED), pools_posts.post_id " ;
} elseif ( $q [ 'order' ] == " change " || $q [ 'order' ] == " change_asc " )
$sql .= " ORDER BY change_seq " ;
elseif ( $q [ 'order' ] == " change_desc " )
$sql .= " ORDER BY change_seq DESC " ;
elseif ( $q [ 'order' ] == " vote " ) {
if ( isset ( $q [ 'vote' ]))
$sql .= " ORDER BY v.updated_at DESC " ;
} elseif ( $q [ 'order' ] == " fav " ) {
if ( is_string ( $q [ 'fav' ]))
$sql .= " ORDER BY f.id DESC " ;
} elseif ( $q [ 'order' ] == " random " )
$sql .= " ORDER BY RAND() " ;
else
$use_default_order = true ;
} else {
$use_default_order = true ;
}
if ( isset ( $use_default_order ) && ! $options [ 'count' ]) {
if ( isset ( $paramsool_ordering ))
$sql .= $paramsool_ordering ;
else {
if ( ! empty ( $options [ 'from_api' ])) {
# When using the API, default to sorting by ID.
$sql .= " ORDER BY p.id DESC " ;
} else {
# MI: Added p.id DESC so posts with same index_timestamp are ordered by id.
$sql .= " ORDER BY p.index_timestamp DESC, p.id DESC " ;
}
}
}
if ( isset ( $options [ 'limit' ]) && isset ( $options [ 'offset' ]))
$sql .= ' LIMIT ' . $options [ 'offset' ] . ', ' . $options [ 'limit' ];
elseif ( isset ( $options [ 'limit' ]))
$sql .= " LIMIT " . $options [ 'limit' ];
$params = array_merge ( $join_params , $cond_params );
return array ( $sql , $params );
}
}