Sequenzia/app/models/Post/SqlMethods.php
2013-11-08 11:37:29 -05:00

450 lines
18 KiB
PHP
Executable File

<?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'])) {
$joins[] = "JOIN users u ON p.user_id = u.id";
$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);
}
}