This repository has been archived on 2025-02-26. You can view files and clone it, but cannot push or open issues or pull requests.
szurubooru/src/Models/SearchServices/PostSearchService.php

488 lines
12 KiB
PHP
Raw Normal View History

2013-10-28 11:19:15 +01:00
<?php
class PostSearchService extends AbstractSearchService
2013-10-28 11:19:15 +01:00
{
private static $enableTokenLimit = true;
2013-10-28 11:19:15 +01:00
public static function enableTokenLimit($enable)
2013-10-28 11:19:15 +01:00
{
self::$enableTokenLimit = $enable;
2013-10-28 11:19:15 +01:00
}
protected static function filterUserSafety(SqlQuery $sqlQuery)
2013-10-28 11:19:15 +01:00
{
2013-10-30 16:22:46 +01:00
$allowedSafety = PrivilegesHelper::getAllowedSafety();
if (empty($allowedSafety))
$sqlQuery->raw('0');
else
$sqlQuery->raw('safety')->in()->genSlots($allowedSafety)->put($allowedSafety);
2013-10-28 11:19:15 +01:00
}
protected static function filterChain(SqlQuery $sqlQuery)
2013-10-28 11:19:15 +01:00
{
if (isset($sqlQuery->__chained))
$sqlQuery->and();
2013-10-28 11:19:15 +01:00
else
$sqlQuery->where();
$sqlQuery->__chained = true;
2013-10-28 11:19:15 +01:00
}
protected static function filterNegate(SqlQuery $sqlQuery)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->not();
2013-10-28 11:19:15 +01:00
}
protected static function filterTag($sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$tag = TagModel::findByName($val);
$sqlQuery
2013-10-28 11:19:15 +01:00
->exists()
->open()
->select('1')
->from('post_tag')
->where('post_id = post.id')
->and('post_tag.tag_id = ?')->put($tag->id)
2013-10-28 11:19:15 +01:00
->close();
}
protected static function filterTokenId($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$ids = preg_split('/[;,]/', $val);
$ids = array_map('intval', $ids);
if (empty($ids))
$sqlQuery->raw('0');
else
$sqlQuery->raw('id')->in()->genSlots($ids)->put($ids);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenIdMin($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('id >= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenIdMax($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('id <= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenScoreMin($searchContext, SqlQuery $sqlQuery, $val)
2013-11-10 12:23:59 +01:00
{
$sqlQuery->raw('score >= ?')->put(intval($val));
2013-11-10 12:23:59 +01:00
}
protected static function filterTokenScoreMax($searchContext, SqlQuery $sqlQuery, $val)
2013-11-10 12:23:59 +01:00
{
$sqlQuery->raw('score <= ?')->put(intval($val));
2013-11-10 12:23:59 +01:00
}
protected static function filterTokenTagMin($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('tag_count >= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenTagMax($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('tag_count <= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenFavMin($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('fav_count >= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenFavMax($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('fav_count <= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenCommentMin($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('comment_count >= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenCommentMax($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$sqlQuery->raw('comment_count <= ?')->put(intval($val));
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenSpecial($searchContext, SqlQuery $sqlQuery, $val)
{
$context = \Chibi\Registry::getContext();
switch (strtolower($val))
{
case 'liked':
case 'likes':
$sqlQuery
->exists()
->open()
->select('1')
->from('post_score')
->where('post_id = post.id')
->and('score > 0')
->and('user_id = ?')->put($context->user->id)
->close();
break;
case 'disliked':
case 'dislikes':
$sqlQuery
->exists()
->open()
->select('1')
->from('post_score')
->where('post_id = post.id')
->and('score < 0')
->and('user_id = ?')->put($context->user->id)
->close();
break;
2014-02-01 09:51:37 +01:00
case 'hidden':
$sqlQuery->raw('hidden');
break;
default:
throw new SimpleException('Unknown special "' . $val . '"');
}
}
protected static function filterTokenType($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
switch (strtolower($val))
2013-10-28 11:19:15 +01:00
{
case 'swf':
$type = PostType::Flash;
break;
case 'img':
$type = PostType::Image;
break;
case 'yt':
case 'youtube':
$type = PostType::Youtube;
break;
default:
throw new SimpleException('Unknown type "' . $val . '"');
}
$sqlQuery->raw('type = ?')->put($type);
2013-10-28 11:19:15 +01:00
}
protected static function __filterTokenDateParser($val)
{
list ($year, $month, $day) = explode('-', $val . '-0-0');
$yearMin = $yearMax = intval($year);
$monthMin = $monthMax = intval($month);
$monthMin = $monthMin ?: 1;
$monthMax = $monthMax ?: 12;
$dayMin = $dayMax = intval($day);
$dayMin = $dayMin ?: 1;
$dayMax = $dayMax ?: intval(date('t', mktime(0, 0, 0, $monthMax, 1, $year)));
$timeMin = mktime(0, 0, 0, $monthMin, $dayMin, $yearMin);
$timeMax = mktime(0, 0, -1, $monthMax, $dayMax+1, $yearMax);
return [$timeMin, $timeMax];
}
protected static function filterTokenDate($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
$sqlQuery
->raw('upload_date >= ?')->put($timeMin)
->and('upload_date <= ?')->put($timeMax);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenDateMin($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
$sqlQuery->raw('upload_date >= ?')->put($timeMin);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenDateMax($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
$sqlQuery->raw('upload_date <= ?')->put($timeMax);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenFav($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$user = UserModel::findByNameOrEmail($val);
$sqlQuery
2013-10-28 11:19:15 +01:00
->exists()
->open()
->select('1')
->from('favoritee')
->where('post_id = post.id')
->and('favoritee.user_id = ?')->put($user->id)
2013-10-28 11:19:15 +01:00
->close();
}
protected static function filterTokenFavs($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
return self::filterTokenFav($searchContext, $sqlQuery, $val);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenComment($searchContext, SqlQuery $sqlQuery, $val)
{
$user = UserModel::findByNameOrEmail($val);
$sqlQuery
->exists()
->open()
->select('1')
->from('comment')
->where('post_id = post.id')
->and('commenter_id = ?')->put($user->id)
->close();
}
protected static function filterTokenCommenter($searchContext, SqlQuery $sqlQuery, $val)
{
return self::filterTokenComment($searchContext, $sqlQuery, $val);
}
protected static function filterTokenSubmit($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
$user = UserModel::findByNameOrEmail($val);
$sqlQuery->raw('uploader_id = ?')->put($user->id);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenUploader($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenUpload($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
}
protected static function filterTokenUploaded($searchContext, SqlQuery $sqlQuery, $val)
{
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenPrev($searchContext, SqlQuery $sqlQuery, $val)
2013-10-28 11:19:15 +01:00
{
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
2013-10-28 11:19:15 +01:00
}
protected static function filterTokenNext($searchContext, SqlQuery $sqlQuery, $val)
{
$searchContext->orderDir *= -1;
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
}
protected static function __filterTokenPrevNext($searchContext, SqlQuery $sqlQuery, $val)
{
$op1 = $searchContext->orderDir == 1 ? '<' : '>';
$op2 = $searchContext->orderDir != 1 ? '<' : '>';
$sqlQuery
->open()
->open()
->raw($searchContext->orderColumn . ' ' . $op1 . ' ')
->open()
->select($searchContext->orderColumn)
->from('post p2')
->where('p2.id = ?')->put(intval($val))
->close()
->and('id != ?')->put($val)
->close()
->or()
->open()
->raw($searchContext->orderColumn . ' = ')
->open()
->select($searchContext->orderColumn)
->from('post p2')
->where('p2.id = ?')->put(intval($val))
->close()
->and('id ' . $op1 . ' ?')->put(intval($val))
->close()
->close();
}
2013-10-28 11:19:15 +01:00
protected static function parseOrderToken($searchContext, $val)
2013-10-28 11:19:15 +01:00
{
$randomReset = true;
$orderDir = 1;
if (substr($val, -4) == 'desc')
{
$orderDir = 1;
$val = rtrim(substr($val, 0, -4), ',');
}
elseif (substr($val, -3) == 'asc')
{
$orderDir = -1;
$val = rtrim(substr($val, 0, -3), ',');
}
if ($val{0} == '-')
{
$orderDir *= -1;
$val = substr($val, 1);
}
switch ($val)
{
case 'id':
$orderColumn = 'id';
2013-10-28 11:19:15 +01:00
break;
case 'date':
$orderColumn = 'upload_date';
2013-10-28 11:19:15 +01:00
break;
case 'comment':
case 'comments':
case 'commentcount':
$orderColumn = 'comment_count';
break;
case 'fav':
case 'favs':
case 'favcount':
$orderColumn = 'fav_count';
break;
2013-11-10 12:23:59 +01:00
case 'score':
$orderColumn = 'score';
break;
2013-10-28 11:19:15 +01:00
case 'tag':
case 'tags':
case 'tagcount':
$orderColumn = 'tag_count';
break;
case 'random':
//seeding works like this: if you visit anything
//that triggers order other than random, the seed
//is going to reset. however, it stays the same as
//long as you keep visiting pages with order:random
//specified.
$randomReset = false;
if (!isset($_SESSION['browsing-seed']))
$_SESSION['browsing-seed'] = mt_rand();
$seed = $_SESSION['browsing-seed'];
$orderColumn = 'SUBSTR(id * ' . $seed .', LENGTH(id) + 2)';
break;
default:
throw new SimpleException('Unknown key "' . $val . '"');
}
if ($randomReset and isset($_SESSION['browsing-seed']))
2013-10-28 11:19:15 +01:00
unset($_SESSION['browsing-seed']);
$searchContext->orderColumn = $orderColumn;
$searchContext->orderDir = $orderDir;
2013-10-28 11:19:15 +01:00
}
protected static function iterateTokens($tokens, $callback)
2013-10-28 11:19:15 +01:00
{
$unparsedTokens = [];
2013-10-28 11:19:15 +01:00
foreach ($tokens as $origToken)
2013-10-28 11:19:15 +01:00
{
$token = $origToken;
$neg = false;
2013-10-28 11:19:15 +01:00
if ($token{0} == '-')
{
$token = substr($token, 1);
$neg = true;
}
$pos = strpos($token, ':');
if ($pos === false)
{
$key = null;
$val = $token;
2013-10-28 11:19:15 +01:00
}
else
2013-10-28 11:19:15 +01:00
{
$key = strtolower(substr($token, 0, $pos));
$val = substr($token, $pos + 1);
2013-10-28 11:19:15 +01:00
}
$parsed = $callback($neg, $key, $val);
2013-10-28 11:19:15 +01:00
if (!$parsed)
$unparsedTokens []= $origToken;
2013-10-28 11:19:15 +01:00
}
return $unparsedTokens;
}
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
{
$config = \Chibi\Registry::getConfig();
$sqlQuery->from('post');
self::filterChain($sqlQuery);
self::filterUserSafety($sqlQuery);
/* query tokens */
$tokens = array_filter(array_unique(explode(' ', $searchQuery)), function($x) { return $x != ''; });
if (self::$enableTokenLimit and count($tokens) > $config->browsing->maxSearchTokens)
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
if (\Chibi\Registry::getContext()->user->hasEnabledHidingDislikedPosts())
$tokens []= '-special:disliked';
2014-02-01 09:51:37 +01:00
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden') or !in_array('special:hidden', $tokens))
$tokens []= '-special:hidden';
2013-10-28 11:19:15 +01:00
$searchContext = new StdClass;
$searchContext->orderColumn = 'id';
$searchContext->orderDir = 1;
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery, &$orderToken)
{
if ($key != 'order')
return false;
if ($neg)
$orderToken = '-' . $val;
else
$orderToken = $val;
self::parseOrderToken($searchContext, $orderToken);
return true;
});
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
{
if ($key !== null)
return false;
self::filterChain($sqlQuery);
if ($neg)
self::filterNegate($sqlQuery);
self::filterTag($sqlQuery, $val);
return true;
});
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
{
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
if (!method_exists(__CLASS__, $methodName))
return false;
self::filterChain($sqlQuery);
if ($neg)
self::filterNegate($sqlQuery);
self::$methodName($searchContext, $sqlQuery, $val);
return true;
});
if (!empty($tokens))
throw new SimpleException('Unknown search token "' . array_shift($tokens) . '"');
$sqlQuery->orderBy($searchContext->orderColumn);
if ($searchContext->orderDir == 1)
$sqlQuery->desc();
else
$sqlQuery->asc();
if ($searchContext->orderColumn != 'id')
{
$sqlQuery->raw(', id');
if ($searchContext->orderDir == 1)
$sqlQuery->desc();
else
$sqlQuery->asc();
}
2013-10-28 11:19:15 +01:00
}
}