Simplified search parsing
Reduced execution flow dependencies and made all search parsers share the basic code rather than implementing everything all over again in each parser through awkward protected functions.
This commit is contained in:
parent
5aa75a4150
commit
5305bb68a4
16 changed files with 710 additions and 767 deletions
|
@ -3,7 +3,8 @@ namespace Szurubooru\Routes;
|
||||||
use Szurubooru\Helpers\InputReader;
|
use Szurubooru\Helpers\InputReader;
|
||||||
use Szurubooru\Privilege;
|
use Szurubooru\Privilege;
|
||||||
use Szurubooru\Routes\AbstractRoute;
|
use Szurubooru\Routes\AbstractRoute;
|
||||||
use Szurubooru\Search\Parsers\SnapshotSearchParser;
|
use Szurubooru\Search\ParserConfigs\SnapshotSearchParserConfig;
|
||||||
|
use Szurubooru\Search\SearchParser;
|
||||||
use Szurubooru\Services\HistoryService;
|
use Szurubooru\Services\HistoryService;
|
||||||
use Szurubooru\Services\PrivilegeService;
|
use Szurubooru\Services\PrivilegeService;
|
||||||
use Szurubooru\ViewProxies\SnapshotViewProxy;
|
use Szurubooru\ViewProxies\SnapshotViewProxy;
|
||||||
|
@ -12,20 +13,20 @@ class GetHistory extends AbstractRoute
|
||||||
{
|
{
|
||||||
private $historyService;
|
private $historyService;
|
||||||
private $privilegeService;
|
private $privilegeService;
|
||||||
private $snapshotSearchParser;
|
private $searchParser;
|
||||||
private $inputReader;
|
private $inputReader;
|
||||||
private $snapshotViewProxy;
|
private $snapshotViewProxy;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
HistoryService $historyService,
|
HistoryService $historyService,
|
||||||
PrivilegeService $privilegeService,
|
PrivilegeService $privilegeService,
|
||||||
SnapshotSearchParser $snapshotSearchParser,
|
SnapshotSearchParserConfig $searchParserConfig,
|
||||||
InputReader $inputReader,
|
InputReader $inputReader,
|
||||||
SnapshotViewProxy $snapshotViewProxy)
|
SnapshotViewProxy $snapshotViewProxy)
|
||||||
{
|
{
|
||||||
$this->historyService = $historyService;
|
$this->historyService = $historyService;
|
||||||
$this->privilegeService = $privilegeService;
|
$this->privilegeService = $privilegeService;
|
||||||
$this->snapshotSearchParser = $snapshotSearchParser;
|
$this->searchParser = new SearchParser($searchParserConfig);
|
||||||
$this->inputReader = $inputReader;
|
$this->inputReader = $inputReader;
|
||||||
$this->snapshotViewProxy = $snapshotViewProxy;
|
$this->snapshotViewProxy = $snapshotViewProxy;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +45,7 @@ class GetHistory extends AbstractRoute
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(Privilege::VIEW_HISTORY);
|
$this->privilegeService->assertPrivilege(Privilege::VIEW_HISTORY);
|
||||||
|
|
||||||
$filter = $this->snapshotSearchParser->createFilterFromInputReader($this->inputReader);
|
$filter = $this->searchParser->createFilterFromInputReader($this->inputReader);
|
||||||
$filter->setPageSize(50);
|
$filter->setPageSize(50);
|
||||||
$result = $this->historyService->getFiltered($filter);
|
$result = $this->historyService->getFiltered($filter);
|
||||||
$entities = $this->snapshotViewProxy->fromArray($result->getEntities());
|
$entities = $this->snapshotViewProxy->fromArray($result->getEntities());
|
||||||
|
|
|
@ -3,7 +3,8 @@ namespace Szurubooru\Routes\Posts;
|
||||||
use Szurubooru\Config;
|
use Szurubooru\Config;
|
||||||
use Szurubooru\Helpers\InputReader;
|
use Szurubooru\Helpers\InputReader;
|
||||||
use Szurubooru\Privilege;
|
use Szurubooru\Privilege;
|
||||||
use Szurubooru\Search\Parsers\PostSearchParser;
|
use Szurubooru\Search\SearchParser;
|
||||||
|
use Szurubooru\Search\ParserConfigs\PostSearchParserConfig;
|
||||||
use Szurubooru\Services\PostService;
|
use Szurubooru\Services\PostService;
|
||||||
use Szurubooru\Services\PrivilegeService;
|
use Szurubooru\Services\PrivilegeService;
|
||||||
use Szurubooru\ViewProxies\PostViewProxy;
|
use Szurubooru\ViewProxies\PostViewProxy;
|
||||||
|
@ -13,7 +14,7 @@ class GetPosts extends AbstractPostRoute
|
||||||
private $config;
|
private $config;
|
||||||
private $privilegeService;
|
private $privilegeService;
|
||||||
private $postService;
|
private $postService;
|
||||||
private $postSearchParser;
|
private $searchParser;
|
||||||
private $inputReader;
|
private $inputReader;
|
||||||
private $postViewProxy;
|
private $postViewProxy;
|
||||||
|
|
||||||
|
@ -21,14 +22,14 @@ class GetPosts extends AbstractPostRoute
|
||||||
Config $config,
|
Config $config,
|
||||||
PrivilegeService $privilegeService,
|
PrivilegeService $privilegeService,
|
||||||
PostService $postService,
|
PostService $postService,
|
||||||
PostSearchParser $postSearchParser,
|
PostSearchParserConfig $searchParserConfig,
|
||||||
InputReader $inputReader,
|
InputReader $inputReader,
|
||||||
PostViewProxy $postViewProxy)
|
PostViewProxy $postViewProxy)
|
||||||
{
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->privilegeService = $privilegeService;
|
$this->privilegeService = $privilegeService;
|
||||||
$this->postService = $postService;
|
$this->postService = $postService;
|
||||||
$this->postSearchParser = $postSearchParser;
|
$this->searchParser = new SearchParser($searchParserConfig);
|
||||||
$this->inputReader = $inputReader;
|
$this->inputReader = $inputReader;
|
||||||
$this->postViewProxy = $postViewProxy;
|
$this->postViewProxy = $postViewProxy;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +48,7 @@ class GetPosts extends AbstractPostRoute
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(Privilege::LIST_POSTS);
|
$this->privilegeService->assertPrivilege(Privilege::LIST_POSTS);
|
||||||
|
|
||||||
$filter = $this->postSearchParser->createFilterFromInputReader($this->inputReader);
|
$filter = $this->searchParser->createFilterFromInputReader($this->inputReader);
|
||||||
$filter->setPageSize($this->config->posts->postsPerPage);
|
$filter->setPageSize($this->config->posts->postsPerPage);
|
||||||
$this->postService->decorateFilterFromBrowsingSettings($filter);
|
$this->postService->decorateFilterFromBrowsingSettings($filter);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
namespace Szurubooru\Routes\Tags;
|
namespace Szurubooru\Routes\Tags;
|
||||||
use Szurubooru\Helpers\InputReader;
|
use Szurubooru\Helpers\InputReader;
|
||||||
use Szurubooru\Privilege;
|
use Szurubooru\Privilege;
|
||||||
use Szurubooru\Search\Parsers\TagSearchParser;
|
use Szurubooru\Search\ParserConfigs\TagSearchParserConfig;
|
||||||
|
use Szurubooru\Search\SearchParser;
|
||||||
use Szurubooru\Services\PrivilegeService;
|
use Szurubooru\Services\PrivilegeService;
|
||||||
use Szurubooru\Services\TagService;
|
use Szurubooru\Services\TagService;
|
||||||
use Szurubooru\ViewProxies\TagViewProxy;
|
use Szurubooru\ViewProxies\TagViewProxy;
|
||||||
|
@ -12,20 +13,20 @@ class GetTags extends AbstractTagRoute
|
||||||
private $privilegeService;
|
private $privilegeService;
|
||||||
private $tagService;
|
private $tagService;
|
||||||
private $tagViewProxy;
|
private $tagViewProxy;
|
||||||
private $tagSearchParser;
|
private $searchParserConfig;
|
||||||
private $inputReader;
|
private $inputReader;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
PrivilegeService $privilegeService,
|
PrivilegeService $privilegeService,
|
||||||
TagService $tagService,
|
TagService $tagService,
|
||||||
TagViewProxy $tagViewProxy,
|
TagViewProxy $tagViewProxy,
|
||||||
TagSearchParser $tagSearchParser,
|
TagSearchParserConfig $searchParserConfig,
|
||||||
InputReader $inputReader)
|
InputReader $inputReader)
|
||||||
{
|
{
|
||||||
$this->privilegeService = $privilegeService;
|
$this->privilegeService = $privilegeService;
|
||||||
$this->tagService = $tagService;
|
$this->tagService = $tagService;
|
||||||
$this->tagViewProxy = $tagViewProxy;
|
$this->tagViewProxy = $tagViewProxy;
|
||||||
$this->tagSearchParser = $tagSearchParser;
|
$this->searchParser = new SearchParser($searchParserConfig);
|
||||||
$this->inputReader = $inputReader;
|
$this->inputReader = $inputReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ class GetTags extends AbstractTagRoute
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(Privilege::LIST_TAGS);
|
$this->privilegeService->assertPrivilege(Privilege::LIST_TAGS);
|
||||||
|
|
||||||
$filter = $this->tagSearchParser->createFilterFromInputReader($this->inputReader);
|
$filter = $this->searchParser->createFilterFromInputReader($this->inputReader);
|
||||||
$filter->setPageSize(50);
|
$filter->setPageSize(50);
|
||||||
|
|
||||||
$result = $this->tagService->getFiltered($filter);
|
$result = $this->tagService->getFiltered($filter);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Szurubooru\Routes\Users;
|
namespace Szurubooru\Routes\Users;
|
||||||
use Szurubooru\Privilege;
|
use Szurubooru\Privilege;
|
||||||
use Szurubooru\Search\Parsers\UserSearchParser;
|
use Szurubooru\Search\ParserConfigs\UserSearchParserConfig;
|
||||||
|
use Szurubooru\Search\SearchParser;
|
||||||
use Szurubooru\Services\PrivilegeService;
|
use Szurubooru\Services\PrivilegeService;
|
||||||
use Szurubooru\Services\UserService;
|
use Szurubooru\Services\UserService;
|
||||||
use Szurubooru\ViewProxies\UserViewProxy;
|
use Szurubooru\ViewProxies\UserViewProxy;
|
||||||
|
@ -10,18 +11,18 @@ class GetUser extends AbstractUserRoute
|
||||||
{
|
{
|
||||||
private $privilegeService;
|
private $privilegeService;
|
||||||
private $userService;
|
private $userService;
|
||||||
private $userSearchParser;
|
private $searchParserConfig;
|
||||||
private $userViewProxy;
|
private $userViewProxy;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
PrivilegeService $privilegeService,
|
PrivilegeService $privilegeService,
|
||||||
UserService $userService,
|
UserService $userService,
|
||||||
UserSearchParser $userSearchParser,
|
UserSearchParserConfig $searchParserConfig,
|
||||||
UserViewProxy $userViewProxy)
|
UserViewProxy $userViewProxy)
|
||||||
{
|
{
|
||||||
$this->privilegeService = $privilegeService;
|
$this->privilegeService = $privilegeService;
|
||||||
$this->userService = $userService;
|
$this->userService = $userService;
|
||||||
$this->userSearchParser = $userSearchParser;
|
$this->searchParser = new SearchParser($searchParserConfig);
|
||||||
$this->userViewProxy = $userViewProxy;
|
$this->userViewProxy = $userViewProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ namespace Szurubooru\Routes\Users;
|
||||||
use Szurubooru\Config;
|
use Szurubooru\Config;
|
||||||
use Szurubooru\Helpers\InputReader;
|
use Szurubooru\Helpers\InputReader;
|
||||||
use Szurubooru\Privilege;
|
use Szurubooru\Privilege;
|
||||||
use Szurubooru\Search\Parsers\UserSearchParser;
|
use Szurubooru\Search\ParserConfigs\UserSearchParserConfig;
|
||||||
|
use Szurubooru\Search\SearchParser;
|
||||||
use Szurubooru\Services\PrivilegeService;
|
use Szurubooru\Services\PrivilegeService;
|
||||||
use Szurubooru\Services\UserService;
|
use Szurubooru\Services\UserService;
|
||||||
use Szurubooru\ViewProxies\UserViewProxy;
|
use Szurubooru\ViewProxies\UserViewProxy;
|
||||||
|
@ -13,7 +14,7 @@ class GetUsers extends AbstractUserRoute
|
||||||
private $config;
|
private $config;
|
||||||
private $privilegeService;
|
private $privilegeService;
|
||||||
private $userService;
|
private $userService;
|
||||||
private $userSearchParser;
|
private $searchParser;
|
||||||
private $inputReader;
|
private $inputReader;
|
||||||
private $userViewProxy;
|
private $userViewProxy;
|
||||||
|
|
||||||
|
@ -21,14 +22,14 @@ class GetUsers extends AbstractUserRoute
|
||||||
Config $config,
|
Config $config,
|
||||||
PrivilegeService $privilegeService,
|
PrivilegeService $privilegeService,
|
||||||
UserService $userService,
|
UserService $userService,
|
||||||
UserSearchParser $userSearchParser,
|
UserSearchParserConfig $searchParserConfig,
|
||||||
InputReader $inputReader,
|
InputReader $inputReader,
|
||||||
UserViewProxy $userViewProxy)
|
UserViewProxy $userViewProxy)
|
||||||
{
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->privilegeService = $privilegeService;
|
$this->privilegeService = $privilegeService;
|
||||||
$this->userService = $userService;
|
$this->userService = $userService;
|
||||||
$this->userSearchParser = $userSearchParser;
|
$this->searchParser = new SearchParser($searchParserConfig);
|
||||||
$this->inputReader = $inputReader;
|
$this->inputReader = $inputReader;
|
||||||
$this->userViewProxy = $userViewProxy;
|
$this->userViewProxy = $userViewProxy;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +48,7 @@ class GetUsers extends AbstractUserRoute
|
||||||
{
|
{
|
||||||
$this->privilegeService->assertPrivilege(Privilege::LIST_USERS);
|
$this->privilegeService->assertPrivilege(Privilege::LIST_USERS);
|
||||||
|
|
||||||
$filter = $this->userSearchParser->createFilterFromInputReader($this->inputReader);
|
$filter = $this->searchParser->createFilterFromInputReader($this->inputReader);
|
||||||
$filter->setPageSize($this->config->users->usersPerPage);
|
$filter->setPageSize($this->config->users->usersPerPage);
|
||||||
$result = $this->userService->getFiltered($filter);
|
$result = $this->userService->getFiltered($filter);
|
||||||
$entities = $this->userViewProxy->fromArray($result->getEntities());
|
$entities = $this->userViewProxy->fromArray($result->getEntities());
|
||||||
|
|
266
src/Search/ParserConfigs/AbstractSearchParserConfig.php
Normal file
266
src/Search/ParserConfigs/AbstractSearchParserConfig.php
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Search\ParserConfigs;
|
||||||
|
use Szurubooru\NotSupportedException;
|
||||||
|
use Szurubooru\Search\Requirements\Requirement;
|
||||||
|
use Szurubooru\Search\Requirements\RequirementCompositeValue;
|
||||||
|
use Szurubooru\Search\Requirements\RequirementRangedValue;
|
||||||
|
use Szurubooru\Search\Requirements\RequirementSingleValue;
|
||||||
|
use Szurubooru\Search\Tokens\NamedSearchToken;
|
||||||
|
use Szurubooru\Search\Tokens\SearchToken;
|
||||||
|
|
||||||
|
abstract class AbstractSearchParserConfig
|
||||||
|
{
|
||||||
|
const ALLOW_COMPOSITE = 1;
|
||||||
|
const ALLOW_RANGE = 2;
|
||||||
|
|
||||||
|
private $orderAliasMap = [];
|
||||||
|
private $basicTokenParser = null;
|
||||||
|
private $namedTokenParsers = [];
|
||||||
|
private $specialTokenParsers = [];
|
||||||
|
|
||||||
|
public abstract function createFilter();
|
||||||
|
|
||||||
|
public function getColumnForTokenValue($tokenValue)
|
||||||
|
{
|
||||||
|
$map = $this->orderAliasMap;
|
||||||
|
|
||||||
|
foreach ($map as $item)
|
||||||
|
{
|
||||||
|
list ($aliases, $value) = $item;
|
||||||
|
if (self::matches($tokenValue, $aliases))
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotSupportedException('Unknown order term: ' . $tokenValue
|
||||||
|
. '. Possible order terms: '
|
||||||
|
. join(', ', array_map(function($term) { return join('/', $term[0]); }, $map)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequirementForBasicToken(SearchToken $token)
|
||||||
|
{
|
||||||
|
if ($this->basicTokenParser)
|
||||||
|
{
|
||||||
|
$tmp = $this->basicTokenParser;
|
||||||
|
return $tmp($token);
|
||||||
|
}
|
||||||
|
throw new NotSupportedException('Basic tokens are not valid in this search');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequirementForNamedToken(NamedSearchToken $token)
|
||||||
|
{
|
||||||
|
if (self::matches($token->getKey(), ['special']))
|
||||||
|
{
|
||||||
|
foreach ($this->specialTokenParsers as $item)
|
||||||
|
{
|
||||||
|
if (!self::matches($token->getValue(), $item->aliases))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$tmp = $item->callback;
|
||||||
|
return $tmp($token);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->raiseNamedTokenError($token->getValue(), $this->specialTokenParsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((strpos($token->getKey(), 'min') !== false
|
||||||
|
|| strpos($token->getKey(), 'max') !== false)
|
||||||
|
&& strpos($token->getValue(), '..') === false)
|
||||||
|
{
|
||||||
|
foreach ($this->namedTokenParsers as $item)
|
||||||
|
{
|
||||||
|
if (is_callable($item->flagsOrCallback) ||
|
||||||
|
!($item->flagsOrCallback & self::ALLOW_RANGE))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($item->aliases as $alias)
|
||||||
|
{
|
||||||
|
if (!self::matches($token->getKey(), [$alias . '_min', $alias . '_max']))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$pseudoToken = new NamedSearchToken();
|
||||||
|
$pseudoToken->setKey($alias);
|
||||||
|
$pseudoToken->setValue(strpos($token->getKey(), 'min') !== false
|
||||||
|
? $token->getValue() . '..'
|
||||||
|
: '..' . $token->getValue());
|
||||||
|
return $this->getRequirementForNamedToken($pseudoToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->namedTokenParsers as $item)
|
||||||
|
{
|
||||||
|
if (!self::matches($token->getKey(), $item->aliases))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (is_callable($item->flagsOrCallback))
|
||||||
|
{
|
||||||
|
$tmp = $item->flagsOrCallback;
|
||||||
|
$requirementValue = $tmp($token->getValue());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$requirementValue = $this->createRequirementValue(
|
||||||
|
$token->getValue(),
|
||||||
|
$item->flagsOrCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
$requirement = new Requirement();
|
||||||
|
$requirement->setType($item->columnName);
|
||||||
|
$requirement->setValue($requirementValue);
|
||||||
|
$requirement->setNegated($token->isNegated());
|
||||||
|
return $requirement;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->raiseNamedTokenError($token->getKey(), $this->namedTokenParsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineOrder($columnName, array $aliases)
|
||||||
|
{
|
||||||
|
$this->orderAliasMap []= [$aliases, $columnName];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineBasicTokenParser($parser)
|
||||||
|
{
|
||||||
|
$this->basicTokenParser = $parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineNamedTokenParser(
|
||||||
|
$columnName,
|
||||||
|
array $aliases,
|
||||||
|
$flagsOrCallback = 0)
|
||||||
|
{
|
||||||
|
$item = new \StdClass;
|
||||||
|
$item->columnName = $columnName;
|
||||||
|
$item->aliases = $aliases;
|
||||||
|
$item->flagsOrCallback = $flagsOrCallback;
|
||||||
|
$this->namedTokenParsers []= $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineSpecialTokenParser(
|
||||||
|
array $aliases,
|
||||||
|
$callback)
|
||||||
|
{
|
||||||
|
$item = new \StdClass;
|
||||||
|
$item->aliases = $aliases;
|
||||||
|
$item->callback = $callback;
|
||||||
|
$this->specialTokenParsers []= $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function createRequirementValue(
|
||||||
|
$text, $flags = 0)
|
||||||
|
{
|
||||||
|
if (($flags & self::ALLOW_RANGE) === self::ALLOW_RANGE
|
||||||
|
&& substr_count($text, '..') === 1)
|
||||||
|
{
|
||||||
|
list ($minValue, $maxValue) = explode('..', $text);
|
||||||
|
$value = new RequirementRangedValue();
|
||||||
|
$value->setMinValue($minValue);
|
||||||
|
$value->setMaxValue($maxValue);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($flags & self::ALLOW_COMPOSITE) === self::ALLOW_COMPOSITE
|
||||||
|
&& strpos($text, ',') !== false)
|
||||||
|
{
|
||||||
|
$values = explode(',', $text);
|
||||||
|
$value = new RequirementCompositeValue();
|
||||||
|
$value->setValues($values);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RequirementSingleValue($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createDateRequirementValue($value)
|
||||||
|
{
|
||||||
|
if (substr_count($value, '..') === 1)
|
||||||
|
{
|
||||||
|
list ($dateMin, $dateMax) = explode('..', $value);
|
||||||
|
$timeMin = self::convertDateTime($dateMin)[0];
|
||||||
|
$timeMax = self::convertDateTime($dateMax)[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$date = $value;
|
||||||
|
list ($timeMin, $timeMax) = self::convertDateTime($date);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = new RequirementRangedValue();
|
||||||
|
$value->setMinValue(date('c', $timeMin));
|
||||||
|
$value->setMaxValue(date('c', $timeMax));
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function matches($text, $array)
|
||||||
|
{
|
||||||
|
$text = self::transformText($text);
|
||||||
|
foreach ($array as $elem)
|
||||||
|
{
|
||||||
|
if (self::transformText($elem) === $text)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function transformText($text)
|
||||||
|
{
|
||||||
|
return str_replace('_', '', strtolower($text));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function convertDateTime($value)
|
||||||
|
{
|
||||||
|
$value = strtolower(trim($value));
|
||||||
|
if (!$value)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
elseif ($value === 'today')
|
||||||
|
{
|
||||||
|
$timeMin = mktime(0, 0, 0);
|
||||||
|
$timeMax = mktime(24, 0, -1);
|
||||||
|
}
|
||||||
|
elseif ($value === 'yesterday')
|
||||||
|
{
|
||||||
|
$timeMin = mktime(-24, 0, 0);
|
||||||
|
$timeMax = mktime(0, 0, -1);
|
||||||
|
}
|
||||||
|
elseif (preg_match('/^(\d{4})$/', $value, $matches))
|
||||||
|
{
|
||||||
|
$year = intval($matches[1]);
|
||||||
|
$timeMin = mktime(0, 0, 0, 1, 1, $year);
|
||||||
|
$timeMax = mktime(0, 0, -1, 1, 1, $year + 1);
|
||||||
|
}
|
||||||
|
elseif (preg_match('/^(\d{4})-(\d{1,2})$/', $value, $matches))
|
||||||
|
{
|
||||||
|
$year = intval($matches[1]);
|
||||||
|
$month = intval($matches[2]);
|
||||||
|
$timeMin = mktime(0, 0, 0, $month, 1, $year);
|
||||||
|
$timeMax = mktime(0, 0, -1, $month + 1, 1, $year);
|
||||||
|
}
|
||||||
|
elseif (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value, $matches))
|
||||||
|
{
|
||||||
|
$year = intval($matches[1]);
|
||||||
|
$month = intval($matches[2]);
|
||||||
|
$day = intval($matches[3]);
|
||||||
|
$timeMin = mktime(0, 0, 0, $month, $day, $year);
|
||||||
|
$timeMax = mktime(0, 0, -1, $month, $day + 1, $year);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new \Exception('Invalid date format: ' . $value);
|
||||||
|
|
||||||
|
return [$timeMin, $timeMax];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function raiseNamedTokenError($key, array $parsers)
|
||||||
|
{
|
||||||
|
if (empty($parsers))
|
||||||
|
throw new NotSupportedException('Such search is not supported in this context.');
|
||||||
|
|
||||||
|
throw new NotSupportedException(
|
||||||
|
'Unknown search key: ' . $key
|
||||||
|
. '. Possible search keys: '
|
||||||
|
. join(', ', array_map(function($item) { return join('/', $item->aliases); }, $parsers)));
|
||||||
|
}
|
||||||
|
}
|
182
src/Search/ParserConfigs/PostSearchParserConfig.php
Normal file
182
src/Search/ParserConfigs/PostSearchParserConfig.php
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Search\ParserConfigs;
|
||||||
|
use Szurubooru\Helpers\EnumHelper;
|
||||||
|
use Szurubooru\NotSupportedException;
|
||||||
|
use Szurubooru\Search\Filters\IFilter;
|
||||||
|
use Szurubooru\Search\Filters\PostFilter;
|
||||||
|
use Szurubooru\Search\ParserConfigs\AbstractSearchParserConfig;
|
||||||
|
use Szurubooru\Search\Requirements\Requirement;
|
||||||
|
use Szurubooru\Search\Requirements\RequirementSingleValue;
|
||||||
|
use Szurubooru\Search\Requirements\RequirementCompositeValue;
|
||||||
|
use Szurubooru\Search\Tokens\NamedSearchToken;
|
||||||
|
use Szurubooru\Search\Tokens\SearchToken;
|
||||||
|
use Szurubooru\Services\AuthService;
|
||||||
|
use Szurubooru\Services\PrivilegeService;
|
||||||
|
|
||||||
|
class PostSearchParserConfig extends AbstractSearchParserConfig
|
||||||
|
{
|
||||||
|
private $authService;
|
||||||
|
private $privilegeService;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AuthService $authService,
|
||||||
|
PrivilegeService $privilegeService)
|
||||||
|
{
|
||||||
|
$this->authService = $authService;
|
||||||
|
$this->privilegeService = $privilegeService;
|
||||||
|
|
||||||
|
$this->defineOrder(PostFilter::ORDER_ID, ['id']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_RANDOM, ['random']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_CREATION_TIME, ['creation_time', 'creation_date', 'date']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_LAST_EDIT_TIME, ['edit_time', 'edit_date']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_SCORE, ['score']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_FILE_SIZE, ['file_size']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_TAG_COUNT, ['tag_count', 'tags', 'tag']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_FAV_COUNT, ['fav_count', 'fags', 'fav']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_COMMENT_COUNT, ['comment_count', 'comments', 'comment']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_NOTE_COUNT, ['note_count', 'notes', 'note']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_LAST_FAV_TIME, ['fav_time', 'fav_date']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_LAST_COMMENT_TIME, ['comment_time', 'comment_date']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_LAST_FEATURE_TIME, ['feature_time', 'feature_date']);
|
||||||
|
$this->defineOrder(PostFilter::ORDER_FEATURE_COUNT, ['feature_count', 'features', 'featured']);
|
||||||
|
|
||||||
|
$this->defineBasicTokenParser(
|
||||||
|
function(SearchToken $token)
|
||||||
|
{
|
||||||
|
$requirement = new Requirement();
|
||||||
|
$requirement->setNegated($token->isNegated());
|
||||||
|
$requirement->setType(PostFilter::REQUIREMENT_TAG);
|
||||||
|
$requirement->setValue(new RequirementSingleValue($token->getValue()));
|
||||||
|
return $requirement;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_ID,
|
||||||
|
['id'],
|
||||||
|
self::ALLOW_COMPOSITE | self::ALLOW_RANGE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_HASH,
|
||||||
|
['hash', 'name'],
|
||||||
|
self::ALLOW_COMPOSITE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_CREATION_TIME,
|
||||||
|
['creation_date', 'creation_time', 'date', 'time'],
|
||||||
|
function ($value)
|
||||||
|
{
|
||||||
|
return self::createDateRequirementValue($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_LAST_EDIT_TIME,
|
||||||
|
['edit_date', 'edit_time'],
|
||||||
|
function ($value)
|
||||||
|
{
|
||||||
|
return self::createDateRequirementValue($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_TAG_COUNT,
|
||||||
|
['tag_count', 'tags'],
|
||||||
|
self::ALLOW_COMPOSITE | self::ALLOW_RANGE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_FAV_COUNT,
|
||||||
|
['fav_count', 'favs'],
|
||||||
|
self::ALLOW_COMPOSITE | self::ALLOW_RANGE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_COMMENT_COUNT,
|
||||||
|
['comment_count', 'comments'],
|
||||||
|
self::ALLOW_COMPOSITE | self::ALLOW_RANGE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_NOTE_COUNT,
|
||||||
|
['note_count', 'notes'],
|
||||||
|
self::ALLOW_COMPOSITE | self::ALLOW_RANGE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_SCORE,
|
||||||
|
['score'],
|
||||||
|
self::ALLOW_COMPOSITE | self::ALLOW_RANGE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_UPLOADER,
|
||||||
|
['uploader', 'uploader', 'uploaded', 'submit', 'submitter', 'submitted'],
|
||||||
|
self::ALLOW_COMPOSITE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_SAFETY,
|
||||||
|
['safety', 'rating'],
|
||||||
|
function ($value)
|
||||||
|
{
|
||||||
|
return self::createRequirementValue(
|
||||||
|
EnumHelper::postSafetyFromString($value),
|
||||||
|
self::ALLOW_COMPOSITE);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_FAVORITE,
|
||||||
|
['fav'],
|
||||||
|
self::ALLOW_COMPOSITE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_TYPE,
|
||||||
|
['type'],
|
||||||
|
function ($value)
|
||||||
|
{
|
||||||
|
return new RequirementSingleValue(
|
||||||
|
EnumHelper::postTypeFromString($value),
|
||||||
|
self::ALLOW_COMPOSITE);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
PostFilter::REQUIREMENT_COMMENT_AUTHOR,
|
||||||
|
['comment', 'comment_author', 'commented'],
|
||||||
|
self::ALLOW_COMPOSITE);
|
||||||
|
|
||||||
|
$this->defineSpecialTokenParser(
|
||||||
|
['liked'],
|
||||||
|
function (SearchToken $token)
|
||||||
|
{
|
||||||
|
return $this->createOwnScoreRequirement(1, $token->isNegated());
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineSpecialTokenParser(
|
||||||
|
['disliked'],
|
||||||
|
function (SearchToken $token)
|
||||||
|
{
|
||||||
|
return $this->createOwnScoreRequirement(-1, $token->isNegated());
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineSpecialTokenParser(
|
||||||
|
['fav'],
|
||||||
|
function (SearchToken $token)
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertLoggedIn();
|
||||||
|
$token = new NamedSearchToken();
|
||||||
|
$token->setKey('fav');
|
||||||
|
$token->setValue($this->authService->getLoggedInUser()->getName());
|
||||||
|
return $this->getRequirementForNamedToken($token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFilter()
|
||||||
|
{
|
||||||
|
return new PostFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createOwnScoreRequirement($score, $isNegated)
|
||||||
|
{
|
||||||
|
$this->privilegeService->assertLoggedIn();
|
||||||
|
$userName = $this->authService->getLoggedInUser()->getName();
|
||||||
|
$tokenValue = new RequirementCompositeValue();
|
||||||
|
$tokenValue->setValues([$userName, $score]);
|
||||||
|
$requirement = new Requirement();
|
||||||
|
$requirement->setType(PostFilter::REQUIREMENT_USER_SCORE);
|
||||||
|
$requirement->setValue($tokenValue);
|
||||||
|
$requirement->setNegated($isNegated);
|
||||||
|
return $requirement;
|
||||||
|
}
|
||||||
|
}
|
11
src/Search/ParserConfigs/SnapshotSearchParserConfig.php
Normal file
11
src/Search/ParserConfigs/SnapshotSearchParserConfig.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Search\ParserConfigs;
|
||||||
|
use Szurubooru\Search\Filters\SnapshotFilter;
|
||||||
|
|
||||||
|
class SnapshotSearchParserConfig extends AbstractSearchParserConfig
|
||||||
|
{
|
||||||
|
public function createFilter()
|
||||||
|
{
|
||||||
|
return new SnapshotFilter;
|
||||||
|
}
|
||||||
|
}
|
63
src/Search/ParserConfigs/TagSearchParserConfig.php
Normal file
63
src/Search/ParserConfigs/TagSearchParserConfig.php
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Search\ParserConfigs;
|
||||||
|
use Szurubooru\NotSupportedException;
|
||||||
|
use Szurubooru\Search\Filters\IFilter;
|
||||||
|
use Szurubooru\Search\Filters\TagFilter;
|
||||||
|
use Szurubooru\Search\ParserConfigs\AbstractSearchParserConfig;
|
||||||
|
use Szurubooru\Search\Requirements\Requirement;
|
||||||
|
use Szurubooru\Search\Requirements\RequirementSingleValue;
|
||||||
|
use Szurubooru\Search\Tokens\NamedSearchToken;
|
||||||
|
use Szurubooru\Search\Tokens\SearchToken;
|
||||||
|
|
||||||
|
class TagSearchParserConfig extends AbstractSearchParserConfig
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->defineOrder(TagFilter::ORDER_ID, ['id']);
|
||||||
|
$this->defineOrder(TagFilter::ORDER_NAME, ['name']);
|
||||||
|
$this->defineOrder(TagFilter::ORDER_CREATION_TIME, ['creation_time']);
|
||||||
|
$this->defineOrder(TagFilter::ORDER_LAST_EDIT_TIME, ['edit_time']);
|
||||||
|
$this->defineOrder(TagFilter::ORDER_USAGE_COUNT, ['usage_count']);
|
||||||
|
|
||||||
|
$this->defineBasicTokenParser(
|
||||||
|
function(SearchToken $token)
|
||||||
|
{
|
||||||
|
$requirement = new Requirement();
|
||||||
|
$requirement->setType(TagFilter::REQUIREMENT_PARTIAL_TAG_NAME);
|
||||||
|
$requirement->setValue(new RequirementSingleValue($token->getValue()));
|
||||||
|
$requirement->setNegated($token->isNegated());
|
||||||
|
return $requirement;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
TagFilter::REQUIREMENT_CREATION_TIME,
|
||||||
|
['creation_time', 'creation_date', 'date', 'time'],
|
||||||
|
function ($value)
|
||||||
|
{
|
||||||
|
return self::createDateRequirementValue($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
TagFilter::REQUIREMENT_LAST_EDIT_TIME,
|
||||||
|
['edit_time', 'edit_date'],
|
||||||
|
function ($value)
|
||||||
|
{
|
||||||
|
return self::createDateRequirementValue($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
TagFilter::REQUIREMENT_USAGE_COUNT,
|
||||||
|
['usage_count', 'usages', 'usage'],
|
||||||
|
self::ALLOW_RANGE | self::ALLOW_COMPOSITE);
|
||||||
|
|
||||||
|
$this->defineNamedTokenParser(
|
||||||
|
TagFilter::REQUIREMENT_CATEGORY,
|
||||||
|
['category'],
|
||||||
|
self::ALLOW_COMPOSITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFilter()
|
||||||
|
{
|
||||||
|
return new TagFilter;
|
||||||
|
}
|
||||||
|
}
|
22
src/Search/ParserConfigs/UserSearchParserConfig.php
Normal file
22
src/Search/ParserConfigs/UserSearchParserConfig.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Search\ParserConfigs;
|
||||||
|
use Szurubooru\NotSupportedException;
|
||||||
|
use Szurubooru\Search\Filters\IFilter;
|
||||||
|
use Szurubooru\Search\Filters\UserFilter;
|
||||||
|
use Szurubooru\Search\ParserConfigs\AbstractSearchParserConfig;
|
||||||
|
use Szurubooru\Search\Tokens\NamedSearchToken;
|
||||||
|
use Szurubooru\Search\Tokens\SearchToken;
|
||||||
|
|
||||||
|
class UserSearchParserConfig extends AbstractSearchParserConfig
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->defineOrder(UserFilter::ORDER_NAME, ['name']);
|
||||||
|
$this->defineOrder(UserFilter::ORDER_CREATION_TIME, ['creation_time', 'creation_date']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFilter()
|
||||||
|
{
|
||||||
|
return new UserFilter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,275 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Szurubooru\Search\Parsers;
|
|
||||||
use Szurubooru\Helpers\InputReader;
|
|
||||||
use Szurubooru\NotSupportedException;
|
|
||||||
use Szurubooru\Search\Filters\IFilter;
|
|
||||||
use Szurubooru\Search\Requirements\Requirement;
|
|
||||||
use Szurubooru\Search\Requirements\RequirementCompositeValue;
|
|
||||||
use Szurubooru\Search\Requirements\RequirementRangedValue;
|
|
||||||
use Szurubooru\Search\Requirements\RequirementSingleValue;
|
|
||||||
use Szurubooru\Search\Tokens\NamedSearchToken;
|
|
||||||
use Szurubooru\Search\Tokens\SearchToken;
|
|
||||||
|
|
||||||
abstract class AbstractSearchParser
|
|
||||||
{
|
|
||||||
const ALLOW_COMPOSITE = 1;
|
|
||||||
const ALLOW_RANGES = 2;
|
|
||||||
|
|
||||||
public function createFilterFromInputReader(InputReader $inputReader)
|
|
||||||
{
|
|
||||||
$filter = $this->createFilter();
|
|
||||||
$filter->setOrder($this->getOrder($inputReader->order, false) + $filter->getOrder());
|
|
||||||
|
|
||||||
if ($inputReader->page)
|
|
||||||
{
|
|
||||||
$filter->setPageNumber($inputReader->page);
|
|
||||||
$filter->setPageSize(25);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tokens = $this->tokenize($inputReader->query);
|
|
||||||
|
|
||||||
foreach ($tokens as $token)
|
|
||||||
{
|
|
||||||
if ($token instanceof NamedSearchToken)
|
|
||||||
{
|
|
||||||
if ($token->getKey() === 'order')
|
|
||||||
$filter->setOrder($this->getOrder($token->getValue(), $token->isNegated()) + $filter->getOrder());
|
|
||||||
else
|
|
||||||
$this->decorateFilterFromNamedToken($filter, $token);
|
|
||||||
}
|
|
||||||
elseif ($token instanceof SearchToken)
|
|
||||||
$this->decorateFilterFromToken($filter, $token);
|
|
||||||
else
|
|
||||||
throw new \RuntimeException('Invalid search token type: ' . get_class($token));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract function createFilter();
|
|
||||||
|
|
||||||
protected abstract function decorateFilterFromToken(IFilter $filter, SearchToken $token);
|
|
||||||
|
|
||||||
protected abstract function decorateFilterFromNamedToken(IFilter $filter, NamedSearchToken $namedToken);
|
|
||||||
|
|
||||||
protected abstract function getOrderColumnMap();
|
|
||||||
|
|
||||||
protected function createRequirementValue($text, $flags = 0, callable $valueDecorator = null)
|
|
||||||
{
|
|
||||||
if ($valueDecorator === null)
|
|
||||||
{
|
|
||||||
$valueDecorator = function($value)
|
|
||||||
{
|
|
||||||
return $value;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((($flags & self::ALLOW_RANGES) === self::ALLOW_RANGES) && substr_count($text, '..') === 1)
|
|
||||||
{
|
|
||||||
list ($minValue, $maxValue) = explode('..', $text);
|
|
||||||
$minValue = $valueDecorator($minValue);
|
|
||||||
$maxValue = $valueDecorator($maxValue);
|
|
||||||
$tokenValue = new RequirementRangedValue();
|
|
||||||
$tokenValue->setMinValue($minValue);
|
|
||||||
$tokenValue->setMaxValue($maxValue);
|
|
||||||
return $tokenValue;
|
|
||||||
}
|
|
||||||
else if ((($flags & self::ALLOW_COMPOSITE) === self::ALLOW_COMPOSITE) && strpos($text, ',') !== false)
|
|
||||||
{
|
|
||||||
$values = explode(',', $text);
|
|
||||||
$values = array_map($valueDecorator, $values);
|
|
||||||
$tokenValue = new RequirementCompositeValue();
|
|
||||||
$tokenValue->setValues($values);
|
|
||||||
return $tokenValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = $valueDecorator($text);
|
|
||||||
return new RequirementSingleValue($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function addRequirementFromToken($filter, $token, $type, $flags, callable $valueDecorator = null)
|
|
||||||
{
|
|
||||||
$requirement = new Requirement();
|
|
||||||
$requirement->setType($type);
|
|
||||||
$requirement->setValue($this->createRequirementValue($token->getValue(), $flags, $valueDecorator));
|
|
||||||
$requirement->setNegated($token->isNegated());
|
|
||||||
$filter->addRequirement($requirement);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function addRequirementFromDateRangeToken($filter, $token, $type)
|
|
||||||
{
|
|
||||||
if (substr_count($token->getValue(), '..') === 1)
|
|
||||||
{
|
|
||||||
list ($dateMin, $dateMax) = explode('..', $token->getValue());
|
|
||||||
$timeMin = $this->dateToTime($dateMin)[0];
|
|
||||||
$timeMax = $this->dateToTime($dateMax)[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$date = $token->getValue();
|
|
||||||
list ($timeMin, $timeMax) = $this->dateToTime($date);
|
|
||||||
}
|
|
||||||
|
|
||||||
$finalString = '';
|
|
||||||
if ($timeMin)
|
|
||||||
$finalString .= date('c', $timeMin);
|
|
||||||
$finalString .= '..';
|
|
||||||
if ($timeMax)
|
|
||||||
$finalString .= date('c', $timeMax);
|
|
||||||
|
|
||||||
$token->setValue($finalString);
|
|
||||||
$this->addRequirementFromToken($filter, $token, $type, self::ALLOW_RANGES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getOrderColumn($tokenText)
|
|
||||||
{
|
|
||||||
$map = $this->getOrderColumnMap();
|
|
||||||
|
|
||||||
foreach ($map as $item)
|
|
||||||
{
|
|
||||||
list ($aliases, $value) = $item;
|
|
||||||
if ($this->matches($tokenText, $aliases))
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotSupportedException('Unknown order term: ' . $tokenText
|
|
||||||
. '. Possible order terms: '
|
|
||||||
. join(', ', array_map(function($term) { return join('/', $term[0]); }, $map)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getOrder($query, $negated)
|
|
||||||
{
|
|
||||||
$order = [];
|
|
||||||
$tokens = array_filter(preg_split('/\s+/', trim($query)));
|
|
||||||
|
|
||||||
foreach ($tokens as $token)
|
|
||||||
{
|
|
||||||
$token = preg_split('/,|\s+/', $token);
|
|
||||||
$orderToken = $token[0];
|
|
||||||
|
|
||||||
if (count($token) === 1)
|
|
||||||
{
|
|
||||||
$orderDir = IFilter::ORDER_DESC;
|
|
||||||
}
|
|
||||||
elseif (count($token) === 2)
|
|
||||||
{
|
|
||||||
if ($token[1] === 'desc')
|
|
||||||
$orderDir = IFilter::ORDER_DESC;
|
|
||||||
elseif ($token[1] === 'asc')
|
|
||||||
$orderDir = IFilter::ORDER_ASC;
|
|
||||||
else
|
|
||||||
throw new \Exception('Wrong search order direction');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new \Exception('Wrong search order token');
|
|
||||||
|
|
||||||
$orderColumn = $this->getOrderColumn($orderToken);
|
|
||||||
if ($orderColumn === null)
|
|
||||||
throw new \InvalidArgumentException('Invalid search order token: ' . $orderToken);
|
|
||||||
|
|
||||||
if ($negated)
|
|
||||||
{
|
|
||||||
$orderDir = $orderDir == IFilter::ORDER_DESC ? IFilter::ORDER_ASC : IFilter::ORDER_DESC;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$order[$orderColumn] = $orderDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $order;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function tokenize($query)
|
|
||||||
{
|
|
||||||
$searchTokens = [];
|
|
||||||
|
|
||||||
foreach (array_filter(preg_split('/\s+/', trim($query))) as $tokenText)
|
|
||||||
{
|
|
||||||
$negated = false;
|
|
||||||
if (substr($tokenText, 0, 1) === '-')
|
|
||||||
{
|
|
||||||
$negated = true;
|
|
||||||
$tokenText = substr($tokenText, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$colonPosition = strpos($tokenText, ':');
|
|
||||||
if (($colonPosition !== false) && ($colonPosition > 0))
|
|
||||||
{
|
|
||||||
$searchToken = new NamedSearchToken();
|
|
||||||
list ($tokenKey, $tokenValue) = explode(':', $tokenText, 2);
|
|
||||||
$searchToken->setKey($tokenKey);
|
|
||||||
$searchToken->setValue($tokenValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$searchToken = new SearchToken();
|
|
||||||
$searchToken->setValue($tokenText);
|
|
||||||
}
|
|
||||||
|
|
||||||
$searchToken->setNegated($negated);
|
|
||||||
$searchTokens[] = $searchToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $searchTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function matches($text, $array)
|
|
||||||
{
|
|
||||||
$text = $this->transformText($text);
|
|
||||||
foreach ($array as $elem)
|
|
||||||
{
|
|
||||||
if ($this->transformText($elem) === $text)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function transformText($text)
|
|
||||||
{
|
|
||||||
return str_replace('_', '', strtolower($text));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function dateToTime($value)
|
|
||||||
{
|
|
||||||
$value = strtolower(trim($value));
|
|
||||||
if (!$value)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
elseif ($value === 'today')
|
|
||||||
{
|
|
||||||
$timeMin = mktime(0, 0, 0);
|
|
||||||
$timeMax = mktime(24, 0, -1);
|
|
||||||
}
|
|
||||||
elseif ($value === 'yesterday')
|
|
||||||
{
|
|
||||||
$timeMin = mktime(-24, 0, 0);
|
|
||||||
$timeMax = mktime(0, 0, -1);
|
|
||||||
}
|
|
||||||
elseif (preg_match('/^(\d{4})$/', $value, $matches))
|
|
||||||
{
|
|
||||||
$year = intval($matches[1]);
|
|
||||||
$timeMin = mktime(0, 0, 0, 1, 1, $year);
|
|
||||||
$timeMax = mktime(0, 0, -1, 1, 1, $year + 1);
|
|
||||||
}
|
|
||||||
elseif (preg_match('/^(\d{4})-(\d{1,2})$/', $value, $matches))
|
|
||||||
{
|
|
||||||
$year = intval($matches[1]);
|
|
||||||
$month = intval($matches[2]);
|
|
||||||
$timeMin = mktime(0, 0, 0, $month, 1, $year);
|
|
||||||
$timeMax = mktime(0, 0, -1, $month + 1, 1, $year);
|
|
||||||
}
|
|
||||||
elseif (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value, $matches))
|
|
||||||
{
|
|
||||||
$year = intval($matches[1]);
|
|
||||||
$month = intval($matches[2]);
|
|
||||||
$day = intval($matches[3]);
|
|
||||||
$timeMin = mktime(0, 0, 0, $month, $day, $year);
|
|
||||||
$timeMax = mktime(0, 0, -1, $month, $day + 1, $year);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new \Exception('Invalid date format: ' . $value);
|
|
||||||
|
|
||||||
return [$timeMin, $timeMax];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,309 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Szurubooru\Search\Parsers;
|
|
||||||
use Szurubooru\Helpers\EnumHelper;
|
|
||||||
use Szurubooru\NotSupportedException;
|
|
||||||
use Szurubooru\Search\Filters\IFilter;
|
|
||||||
use Szurubooru\Search\Filters\PostFilter;
|
|
||||||
use Szurubooru\Search\Requirements\Requirement;
|
|
||||||
use Szurubooru\Search\Requirements\RequirementCompositeValue;
|
|
||||||
use Szurubooru\Search\Tokens\NamedSearchToken;
|
|
||||||
use Szurubooru\Search\Tokens\SearchToken;
|
|
||||||
use Szurubooru\Services\AuthService;
|
|
||||||
use Szurubooru\Services\PrivilegeService;
|
|
||||||
|
|
||||||
class PostSearchParser extends AbstractSearchParser
|
|
||||||
{
|
|
||||||
private $authService;
|
|
||||||
private $privilegeService;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
AuthService $authService,
|
|
||||||
PrivilegeService $privilegeService)
|
|
||||||
{
|
|
||||||
$this->authService = $authService;
|
|
||||||
$this->privilegeService = $privilegeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createFilter()
|
|
||||||
{
|
|
||||||
return new PostFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromToken(IFilter $filter, SearchToken $token)
|
|
||||||
{
|
|
||||||
$requirement = new Requirement();
|
|
||||||
$requirement->setType(PostFilter::REQUIREMENT_TAG);
|
|
||||||
$requirement->setValue($this->createRequirementValue($token->getValue()));
|
|
||||||
$requirement->setNegated($token->isNegated());
|
|
||||||
$filter->addRequirement($requirement);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromNamedToken(IFilter $filter, NamedSearchToken $token)
|
|
||||||
{
|
|
||||||
$tokenKey = $token->getKey();
|
|
||||||
$tokenValue = $token->getValue();
|
|
||||||
|
|
||||||
$countAliases =
|
|
||||||
[
|
|
||||||
'tag_count' => 'tags',
|
|
||||||
'fav_count' => 'favs',
|
|
||||||
'score' => 'score',
|
|
||||||
'comment_count' => 'comments',
|
|
||||||
'note_count' => 'notes',
|
|
||||||
];
|
|
||||||
foreach ($countAliases as $realKey => $baseAlias)
|
|
||||||
{
|
|
||||||
if ($this->matches($tokenKey, [$baseAlias . '_min', $baseAlias . '_max']))
|
|
||||||
{
|
|
||||||
$token = new NamedSearchToken();
|
|
||||||
$token->setKey($realKey);
|
|
||||||
$token->setValue(strpos($tokenKey, 'min') !== false ? $tokenValue . '..' : '..' . $tokenValue);
|
|
||||||
return $this->decorateFilterFromNamedToken($filter, $token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$map =
|
|
||||||
[
|
|
||||||
[['id'], [$this, 'addIdRequirement']],
|
|
||||||
[['hash', 'name'], [$this, 'addHashRequirement']],
|
|
||||||
[['date', 'time', 'creation_date', 'creation_time'], [$this, 'addCreationTimeRequirement']],
|
|
||||||
[['edit_date', 'edit_time'], [$this, 'addLastEditTimeRequirement']],
|
|
||||||
[['tag_count', 'tags'], [$this, 'addTagCountRequirement']],
|
|
||||||
[['fav_count', 'favs'], [$this, 'addFavCountRequirement']],
|
|
||||||
[['comment_count', 'comments'], [$this, 'addCommentCountRequirement']],
|
|
||||||
[['note_count', 'notes'], [$this, 'addNoteCountRequirement']],
|
|
||||||
[['score'], [$this, 'addScoreRequirement']],
|
|
||||||
[['uploader', 'uploader', 'uploaded', 'submit', 'submitter', 'submitted'], [$this, 'addUploaderRequirement']],
|
|
||||||
[['safety', 'rating'], [$this, 'addSafetyRequirement']],
|
|
||||||
[['fav'], [$this, 'addFavRequirement']],
|
|
||||||
[['type'], [$this, 'addTypeRequirement']],
|
|
||||||
[['comment', 'comment_author', 'commented'], [$this, 'addCommentAuthorRequirement']],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($map as $item)
|
|
||||||
{
|
|
||||||
list ($aliases, $callback) = $item;
|
|
||||||
if ($this->matches($tokenKey, $aliases))
|
|
||||||
return $callback($filter, $token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->matches($tokenKey, ['special']))
|
|
||||||
{
|
|
||||||
$specialMap =
|
|
||||||
[
|
|
||||||
[['liked'], [$this, 'addOwnLikedRequirement']],
|
|
||||||
[['disliked'], [$this, 'addOwnDislikedRequirement']],
|
|
||||||
[['fav'], [$this, 'addOwnFavRequirement']],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($specialMap as $item)
|
|
||||||
{
|
|
||||||
list ($aliases, $callback) = $item;
|
|
||||||
if ($this->matches($token->getValue(), $aliases))
|
|
||||||
return $callback($filter, $token);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotSupportedException(
|
|
||||||
'Unknown value for special search term: ' . $token->getValue()
|
|
||||||
. '. Possible search terms: '
|
|
||||||
. join(', ', array_map(function($term) { return join('/', $term[0]); }, $specialMap)));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotSupportedException('Unknown search term: ' . $token->getKey()
|
|
||||||
. '. Possible search terms: special, '
|
|
||||||
. join(', ', array_map(function($term) { return join('/', $term[0]); }, $map)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getOrderColumnMap()
|
|
||||||
{
|
|
||||||
return
|
|
||||||
[
|
|
||||||
[['id'], PostFilter::ORDER_ID],
|
|
||||||
[['random'], PostFilter::ORDER_RANDOM],
|
|
||||||
[['creation_time', 'creation_date', 'date'], PostFilter::ORDER_CREATION_TIME],
|
|
||||||
[['edit_time', 'edit_date'], PostFilter::ORDER_LAST_EDIT_TIME],
|
|
||||||
[['score'], PostFilter::ORDER_SCORE],
|
|
||||||
[['file_size'], PostFilter::ORDER_FILE_SIZE],
|
|
||||||
[['tag_count', 'tags', 'tag'], PostFilter::ORDER_TAG_COUNT],
|
|
||||||
[['fav_count', 'fags', 'fav'], PostFilter::ORDER_FAV_COUNT],
|
|
||||||
[['comment_count', 'comments', 'comment'], PostFilter::ORDER_COMMENT_COUNT],
|
|
||||||
[['note_count', 'notes', 'note'], PostFilter::ORDER_NOTE_COUNT],
|
|
||||||
[['fav_time', 'fav_date'], PostFilter::ORDER_LAST_FAV_TIME],
|
|
||||||
[['comment_time', 'comment_date'], PostFilter::ORDER_LAST_COMMENT_TIME],
|
|
||||||
[['feature_time', 'feature_date'], PostFilter::ORDER_LAST_FEATURE_TIME],
|
|
||||||
[['feature_count', 'features', 'featured'], PostFilter::ORDER_FEATURE_COUNT],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addOwnLikedRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->privilegeService->assertLoggedIn();
|
|
||||||
$this->addUserScoreRequirement(
|
|
||||||
$filter,
|
|
||||||
$this->authService->getLoggedInUser()->getName(),
|
|
||||||
1,
|
|
||||||
$token->isNegated());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addOwnDislikedRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->privilegeService->assertLoggedIn();
|
|
||||||
$this->addUserScoreRequirement(
|
|
||||||
$filter,
|
|
||||||
$this->authService->getLoggedInUser()->getName(),
|
|
||||||
-1,
|
|
||||||
$token->isNegated());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addOwnFavRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->privilegeService->assertLoggedIn();
|
|
||||||
$token = new NamedSearchToken();
|
|
||||||
$token->setKey('fav');
|
|
||||||
$token->setValue($this->authService->getLoggedInUser()->getName());
|
|
||||||
$this->decorateFilterFromNamedToken($filter, $token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addIdRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_ID,
|
|
||||||
self::ALLOW_COMPOSITE | self::ALLOW_RANGES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addHashRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_HASH,
|
|
||||||
self::ALLOW_COMPOSITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addCreationTimeRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromDateRangeToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_CREATION_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addLastEditTimeRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromDateRangeToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_LAST_EDIT_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addTagCountRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_TAG_COUNT,
|
|
||||||
self::ALLOW_COMPOSITE | self::ALLOW_RANGES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addFavCountRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_FAV_COUNT,
|
|
||||||
self::ALLOW_COMPOSITE | self::ALLOW_RANGES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addCommentCountRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_COMMENT_COUNT,
|
|
||||||
self::ALLOW_COMPOSITE | self::ALLOW_RANGES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addNoteCountRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_NOTE_COUNT,
|
|
||||||
self::ALLOW_COMPOSITE | self::ALLOW_RANGES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addScoreRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_SCORE,
|
|
||||||
self::ALLOW_COMPOSITE | self::ALLOW_RANGES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addUploaderRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_UPLOADER,
|
|
||||||
self::ALLOW_COMPOSITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addSafetyRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_SAFETY,
|
|
||||||
self::ALLOW_COMPOSITE,
|
|
||||||
function ($value)
|
|
||||||
{
|
|
||||||
return EnumHelper::postSafetyFromString($value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addFavRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_FAVORITE,
|
|
||||||
self::ALLOW_COMPOSITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addTypeRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_TYPE,
|
|
||||||
self::ALLOW_COMPOSITE,
|
|
||||||
function ($value)
|
|
||||||
{
|
|
||||||
return EnumHelper::postTypeFromString($value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addCommentAuthorRequirement($filter, $token)
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
PostFilter::REQUIREMENT_COMMENT_AUTHOR,
|
|
||||||
self::ALLOW_COMPOSITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function addUserScoreRequirement($filter, $userName, $score, $isNegated)
|
|
||||||
{
|
|
||||||
$tokenValue = new RequirementCompositeValue();
|
|
||||||
$tokenValue->setValues([$userName, $score]);
|
|
||||||
$requirement = new Requirement();
|
|
||||||
$requirement->setType(PostFilter::REQUIREMENT_USER_SCORE);
|
|
||||||
$requirement->setValue($tokenValue);
|
|
||||||
$requirement->setNegated($isNegated);
|
|
||||||
$filter->addRequirement($requirement);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Szurubooru\Search\Parsers;
|
|
||||||
use Szurubooru\Helpers\EnumHelper;
|
|
||||||
use Szurubooru\NotSupportedException;
|
|
||||||
use Szurubooru\Search\Filters\IFilter;
|
|
||||||
use Szurubooru\Search\Filters\SnapshotFilter;
|
|
||||||
use Szurubooru\Search\Requirements\Requirement;
|
|
||||||
use Szurubooru\Search\Tokens\NamedSearchToken;
|
|
||||||
use Szurubooru\Search\Tokens\SearchToken;
|
|
||||||
|
|
||||||
class SnapshotSearchParser extends AbstractSearchParser
|
|
||||||
{
|
|
||||||
protected function createFilter()
|
|
||||||
{
|
|
||||||
return new SnapshotFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromToken(IFilter $filter, SearchToken $token)
|
|
||||||
{
|
|
||||||
if (substr_count($token->getValue(), ',') !== 1)
|
|
||||||
throw new NotSupportedException('Expected token in form of "type,id"');
|
|
||||||
|
|
||||||
if ($token->isNegated())
|
|
||||||
throw new NotSupportedException('Negative searches are not supported in this context');
|
|
||||||
|
|
||||||
list ($type, $primaryKey) = explode(',', $token->getValue());
|
|
||||||
|
|
||||||
$requirement = new Requirement();
|
|
||||||
$requirement->setType(SnapshotFilter::REQUIREMENT_PRIMARY_KEY);
|
|
||||||
$requirement->setValue($this->createRequirementValue($primaryKey));
|
|
||||||
$filter->addRequirement($requirement);
|
|
||||||
|
|
||||||
$requirement = new Requirement();
|
|
||||||
$requirement->setType(SnapshotFilter::REQUIREMENT_TYPE);
|
|
||||||
$requirement->setValue($this->createRequirementValue(EnumHelper::snapshotTypeFromString($type)));
|
|
||||||
$filter->addRequirement($requirement);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromNamedToken(IFilter $filter, NamedSearchToken $namedToken)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException('Named tokens are not supported in this context');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getOrderColumnMap()
|
|
||||||
{
|
|
||||||
throw new NotSupportedException('Search order is not supported in this context');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Szurubooru\Search\Parsers;
|
|
||||||
use Szurubooru\NotSupportedException;
|
|
||||||
use Szurubooru\Search\Filters\IFilter;
|
|
||||||
use Szurubooru\Search\Filters\TagFilter;
|
|
||||||
use Szurubooru\Search\Requirements\Requirement;
|
|
||||||
use Szurubooru\Search\Requirements\RequirementSingleValue;
|
|
||||||
use Szurubooru\Search\Tokens\NamedSearchToken;
|
|
||||||
use Szurubooru\Search\Tokens\SearchToken;
|
|
||||||
|
|
||||||
class TagSearchParser extends AbstractSearchParser
|
|
||||||
{
|
|
||||||
protected function createFilter()
|
|
||||||
{
|
|
||||||
return new TagFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromToken(IFilter $filter, SearchToken $token)
|
|
||||||
{
|
|
||||||
$requirement = new Requirement();
|
|
||||||
$requirement->setType(TagFilter::REQUIREMENT_PARTIAL_TAG_NAME);
|
|
||||||
$requirement->setValue(new RequirementSingleValue($token->getValue()));
|
|
||||||
$requirement->setNegated($token->isNegated());
|
|
||||||
$filter->addRequirement($requirement);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromNamedToken(IFilter $filter, NamedSearchToken $token)
|
|
||||||
{
|
|
||||||
if ($this->matches($token->getKey(), ['creation_time', 'creation_date', 'date']))
|
|
||||||
{
|
|
||||||
$this->addRequirementFromDateRangeToken(
|
|
||||||
$filter, $token, TagFilter::REQUIREMENT_CREATION_TIME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->matches($token->getKey(), ['edit_time', 'edit_date']))
|
|
||||||
{
|
|
||||||
$this->addRequirementFromDateRangeToken(
|
|
||||||
$filter, $token, TagFilter::REQUIREMENT_LAST_EDIT_TIME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->matches($token->getKey(), ['usage_count', 'usages', 'usage']))
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
TagFilter::REQUIREMENT_USAGE_COUNT,
|
|
||||||
self::ALLOW_RANGES | self::ALLOW_COMPOSITE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->matches($token->getKey(), ['category']))
|
|
||||||
{
|
|
||||||
$this->addRequirementFromToken(
|
|
||||||
$filter,
|
|
||||||
$token,
|
|
||||||
TagFilter::REQUIREMENT_CATEGORY,
|
|
||||||
self::ALLOW_COMPOSITE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotSupportedException('Unknown token: ' . $token->getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getOrderColumnMap()
|
|
||||||
{
|
|
||||||
return
|
|
||||||
[
|
|
||||||
[['id'], TagFilter::ORDER_ID],
|
|
||||||
[['name'], TagFilter::ORDER_NAME],
|
|
||||||
[['creation_time', 'creation_date', 'date'], TagFilter::ORDER_CREATION_TIME],
|
|
||||||
[['edit_time', 'edit_date'], TagFilter::ORDER_LAST_EDIT_TIME],
|
|
||||||
[['usage_count', 'usages'], TagFilter::ORDER_USAGE_COUNT],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Szurubooru\Search\Parsers;
|
|
||||||
use Szurubooru\NotSupportedException;
|
|
||||||
use Szurubooru\Search\Filters\IFilter;
|
|
||||||
use Szurubooru\Search\Filters\UserFilter;
|
|
||||||
use Szurubooru\Search\Tokens\NamedSearchToken;
|
|
||||||
use Szurubooru\Search\Tokens\SearchToken;
|
|
||||||
|
|
||||||
class UserSearchParser extends AbstractSearchParser
|
|
||||||
{
|
|
||||||
protected function createFilter()
|
|
||||||
{
|
|
||||||
return new UserFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromToken(IFilter $filter, SearchToken $token)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function decorateFilterFromNamedToken(IFilter $filter, NamedSearchToken $namedToken)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getOrderColumnMap()
|
|
||||||
{
|
|
||||||
return
|
|
||||||
[
|
|
||||||
[['name'], UserFilter::ORDER_NAME],
|
|
||||||
[['creation_time', 'creation_date'], UserFilter::ORDER_CREATION_TIME],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
137
src/Search/SearchParser.php
Normal file
137
src/Search/SearchParser.php
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Search;
|
||||||
|
use Szurubooru\Helpers\InputReader;
|
||||||
|
use Szurubooru\Search\Filters\IFilter;
|
||||||
|
use Szurubooru\Search\ParserConfigs\AbstractSearchParserConfig;
|
||||||
|
use Szurubooru\Search\Tokens\NamedSearchToken;
|
||||||
|
use Szurubooru\Search\Tokens\SearchToken;
|
||||||
|
|
||||||
|
class SearchParser
|
||||||
|
{
|
||||||
|
private $parserConfig;
|
||||||
|
|
||||||
|
public function __construct(AbstractSearchParserConfig $parserConfig)
|
||||||
|
{
|
||||||
|
$this->parserConfig = $parserConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFilterFromInputReader(InputReader $inputReader)
|
||||||
|
{
|
||||||
|
$filter = $this->parserConfig->createFilter();
|
||||||
|
$filter->setOrder($this->getOrder($inputReader->order, false) + $filter->getOrder());
|
||||||
|
|
||||||
|
if ($inputReader->page)
|
||||||
|
{
|
||||||
|
$filter->setPageNumber($inputReader->page);
|
||||||
|
$filter->setPageSize(25);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens = $this->tokenize($inputReader->query);
|
||||||
|
|
||||||
|
foreach ($tokens as $token)
|
||||||
|
{
|
||||||
|
if ($token instanceof NamedSearchToken)
|
||||||
|
{
|
||||||
|
if ($token->getKey() === 'order')
|
||||||
|
{
|
||||||
|
$filter->setOrder(
|
||||||
|
$this->getOrder($token->getValue(), $token->isNegated())
|
||||||
|
+ $filter->getOrder());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$requirement = $this->parserConfig->getRequirementForNamedToken($token);
|
||||||
|
$filter->addRequirement($requirement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($token instanceof SearchToken)
|
||||||
|
{
|
||||||
|
$requirement = $this->parserConfig->getRequirementForBasicToken($token);
|
||||||
|
$filter->addRequirement($requirement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Invalid search token type: ' . get_class($token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getOrder($query, $negated)
|
||||||
|
{
|
||||||
|
$order = [];
|
||||||
|
$tokens = array_filter(preg_split('/\s+/', trim($query)));
|
||||||
|
|
||||||
|
foreach ($tokens as $token)
|
||||||
|
{
|
||||||
|
$token = preg_split('/,|\s+/', $token);
|
||||||
|
$orderToken = $token[0];
|
||||||
|
|
||||||
|
if (count($token) === 1)
|
||||||
|
{
|
||||||
|
$orderDir = IFilter::ORDER_DESC;
|
||||||
|
}
|
||||||
|
elseif (count($token) === 2)
|
||||||
|
{
|
||||||
|
if ($token[1] === 'desc')
|
||||||
|
$orderDir = IFilter::ORDER_DESC;
|
||||||
|
elseif ($token[1] === 'asc')
|
||||||
|
$orderDir = IFilter::ORDER_ASC;
|
||||||
|
else
|
||||||
|
throw new \Exception('Wrong search order direction');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new \Exception('Wrong search order token');
|
||||||
|
|
||||||
|
$orderColumn = $this->parserConfig->getColumnForTokenValue($orderToken);
|
||||||
|
if ($orderColumn === null)
|
||||||
|
throw new \InvalidArgumentException('Invalid search order token: ' . $orderToken);
|
||||||
|
|
||||||
|
if ($negated)
|
||||||
|
{
|
||||||
|
$orderDir = $orderDir == IFilter::ORDER_DESC
|
||||||
|
? IFilter::ORDER_ASC
|
||||||
|
: IFilter::ORDER_DESC;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order[$orderColumn] = $orderDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function tokenize($query)
|
||||||
|
{
|
||||||
|
$searchTokens = [];
|
||||||
|
|
||||||
|
foreach (array_filter(preg_split('/\s+/', trim($query))) as $tokenText)
|
||||||
|
{
|
||||||
|
$negated = false;
|
||||||
|
if (substr($tokenText, 0, 1) === '-')
|
||||||
|
{
|
||||||
|
$negated = true;
|
||||||
|
$tokenText = substr($tokenText, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$colonPosition = strpos($tokenText, ':');
|
||||||
|
if (($colonPosition !== false) && ($colonPosition > 0))
|
||||||
|
{
|
||||||
|
$searchToken = new NamedSearchToken();
|
||||||
|
list ($tokenKey, $tokenValue) = explode(':', $tokenText, 2);
|
||||||
|
$searchToken->setKey($tokenKey);
|
||||||
|
$searchToken->setValue($tokenValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$searchToken = new SearchToken();
|
||||||
|
$searchToken->setValue($tokenText);
|
||||||
|
}
|
||||||
|
|
||||||
|
$searchToken->setNegated($negated);
|
||||||
|
$searchTokens[] = $searchToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $searchTokens;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue