449 lines
17 KiB
PHP
449 lines
17 KiB
PHP
|
<?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'];
|
||
|
}
|
||
|
|
||
|
// if (is_string($q['subscriptions'])) {
|
||
|
// preg_match('/^(.+?):(.+)$/', $q['subscriptions'], $m);
|
||
|
// $username = $m[1] || $q['subscriptions'];
|
||
|
// $subscription_name = $m[2];
|
||
|
// $user = new User('find_by_name', $username);
|
||
|
|
||
|
// if ($user) {
|
||
|
// $paramsost_ids = TagSubscription.find_post_ids(user.id, subscription_name)
|
||
|
// $conds[] = "p.id IN (?)"
|
||
|
// $cond_params[] = post_ids
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
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'])) {
|
||
|
$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);
|
||
|
}
|
||
|
}
|