Added searching by tags and ids
This commit is contained in:
parent
2763ff7ead
commit
5159214e80
19 changed files with 206 additions and 33 deletions
7
TODO
7
TODO
|
@ -26,10 +26,10 @@ everything related to posts:
|
|||
- score
|
||||
- comment count
|
||||
- regard safety settings
|
||||
- regard disliked settings
|
||||
- add search form (query, order and safety) to post list presenter
|
||||
- add warning if no posts were found
|
||||
- search filters
|
||||
- tag
|
||||
- -tag
|
||||
- submit:rr-
|
||||
- comment:rr-
|
||||
- comment: 3..5
|
||||
|
@ -42,7 +42,6 @@ everything related to posts:
|
|||
- date:yyyy[-mm[-dd]]..yyyy[-mm[-dd]]
|
||||
- filesize:3K..5M
|
||||
- imgsize:huge/large/medium/small
|
||||
- id:3,4,5
|
||||
- hash:postName
|
||||
- type:img/flash/yt/video
|
||||
- special:liked
|
||||
|
@ -60,8 +59,6 @@ everything related to posts:
|
|||
- order:favdate
|
||||
- order:filesize
|
||||
- order:random (at least unstable version)
|
||||
- safe/sketchy/unsafe buttons and top navigation search goes to post
|
||||
list presenter
|
||||
|
||||
everything related to users:
|
||||
- banning
|
||||
|
|
|
@ -54,7 +54,7 @@ App.Presenters.PostListPresenter = function(
|
|||
loaded();
|
||||
|
||||
var searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
||||
pagedCollectionPresenter.reinit({page: searchArgs.page, searchParams: {order: searchArgs.order}});
|
||||
pagedCollectionPresenter.reinit({page: searchArgs.page, searchParams: {query: searchArgs.query, order: searchArgs.order}});
|
||||
}
|
||||
|
||||
function render() {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<ul class="tags">
|
||||
<% _.each(post.tags, function(tag) { %>
|
||||
<li>
|
||||
<a href="#/posts/search=<%= tag.name %>">
|
||||
<a href="#/posts/query=<%= tag.name %>">
|
||||
<%= tag.name %>
|
||||
<span class="usages"><%= (tag.usages) %></span>
|
||||
</a>
|
||||
|
|
|
@ -65,7 +65,7 @@ abstract class AbstractDao implements ICrudDao
|
|||
|
||||
public function findFiltered(\Szurubooru\SearchServices\Filters\IFilter $searchFilter)
|
||||
{
|
||||
$query = $this->fpdo->from($this->tableName);
|
||||
$query = $this->fpdo->from($this->tableName)->disableSmartJoin();
|
||||
|
||||
$orderByString = self::compileOrderBy($searchFilter->getOrder());
|
||||
if ($orderByString)
|
||||
|
@ -79,7 +79,7 @@ abstract class AbstractDao implements ICrudDao
|
|||
}
|
||||
$entities = $this->arrayToEntities(iterator_to_array($query));
|
||||
|
||||
$query = $this->fpdo->from($this->tableName);
|
||||
$query = $this->fpdo->from($this->tableName)->disableSmartJoin();
|
||||
$this->decorateQueryFromFilter($query, $searchFilter);
|
||||
$totalRecords = count($query);
|
||||
|
||||
|
@ -172,6 +172,52 @@ abstract class AbstractDao implements ICrudDao
|
|||
{
|
||||
}
|
||||
|
||||
protected function decorateQueryFromRequirement($query, \Szurubooru\SearchServices\Requirements\Requirement $requirement)
|
||||
{
|
||||
$value = $requirement->getValue();
|
||||
$sqlColumn = $requirement->getType();
|
||||
|
||||
if ($value instanceof \Szurubooru\SearchServices\Requirements\RequirementCompositeValue)
|
||||
{
|
||||
$sql = $sqlColumn;
|
||||
$bindings = [$value->getValues()];
|
||||
}
|
||||
|
||||
else if ($value instanceof \Szurubooru\SearchServices\Requirements\RequirementRangedValue)
|
||||
{
|
||||
if ($value->getMinValue() and $value->getMaxValue())
|
||||
{
|
||||
$sql = $sqlColumn . ' >= ? AND ' . $sqlColumn . ' <= ?';
|
||||
$bindings = [$value->getMinValue(), $value->getMaxValue()];
|
||||
}
|
||||
elseif ($value->getMinValue())
|
||||
{
|
||||
$sql = $sqlColumn . ' >= ?';
|
||||
$bindings = [$value->getMinValue()];
|
||||
}
|
||||
elseif ($value->getMaxValue())
|
||||
{
|
||||
$sql = $sqlColumn . ' <= ?';
|
||||
$bindings = [$value->getMaxValue()];
|
||||
}
|
||||
else
|
||||
throw new \RuntimeException('Neither min or max value was supplied');
|
||||
}
|
||||
|
||||
else if ($value instanceof \Szurubooru\SearchServices\Requirements\RequirementSingleValue)
|
||||
{
|
||||
$sql = $sqlColumn;
|
||||
$bindings = [$value->getValue()];
|
||||
}
|
||||
|
||||
else
|
||||
throw new \Exception('Bad value: ' . get_class($value));
|
||||
|
||||
if ($requirement->isNegated())
|
||||
$sql = 'NOT (' . $sql . ')';
|
||||
call_user_func_array([$query, 'where'], array_merge([$sql], $bindings));
|
||||
}
|
||||
|
||||
protected function arrayToEntities(array $arrayEntities)
|
||||
{
|
||||
$entities = [];
|
||||
|
@ -193,10 +239,7 @@ abstract class AbstractDao implements ICrudDao
|
|||
{
|
||||
foreach ($filter->getRequirements() as $requirement)
|
||||
{
|
||||
if ($requirement->isNegated())
|
||||
$query->where('NOT ' . $requirement->getType(), $requirement->getValue());
|
||||
else
|
||||
$query->where($requirement->getType(), $requirement->getValue());
|
||||
$this->decorateQueryFromRequirement($query, $requirement);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,6 +93,26 @@ class PostDao extends AbstractDao implements ICrudDao
|
|||
$this->syncPostRelations($post);
|
||||
}
|
||||
|
||||
protected function decorateQueryFromRequirement($query, \Szurubooru\SearchServices\Requirements\Requirement $requirement)
|
||||
{
|
||||
if ($requirement->getType() === \Szurubooru\SearchServices\Filters\PostFilter::REQUIREMENT_TAG)
|
||||
{
|
||||
$sql = 'EXISTS (
|
||||
SELECT 1 FROM postTags
|
||||
INNER JOIN tags ON postTags.tagId = tags.id
|
||||
WHERE postTags.postId = posts.id
|
||||
AND LOWER(tags.name) = LOWER(?))';
|
||||
|
||||
if ($requirement->isNegated())
|
||||
$sql = 'NOT ' . $sql;
|
||||
|
||||
$query->where($sql, $requirement->getValue()->getValue());
|
||||
return;
|
||||
}
|
||||
|
||||
parent::decorateQueryFromRequirement($query, $requirement);
|
||||
}
|
||||
|
||||
private function getTags(\Szurubooru\Entities\Post $post)
|
||||
{
|
||||
return $this->tagDao->findByPostId($post->getId());
|
||||
|
|
|
@ -23,7 +23,7 @@ class BasicFilter implements IFilter
|
|||
$this->order = $order;
|
||||
}
|
||||
|
||||
public function addRequirement(\Szurubooru\SearchServices\Requirement $requirement)
|
||||
public function addRequirement(\Szurubooru\SearchServices\Requirements\Requirement $requirement)
|
||||
{
|
||||
$this->requirements[] = $requirement;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ interface IFilter
|
|||
|
||||
public function getRequirements();
|
||||
|
||||
public function addRequirement(\Szurubooru\SearchServices\Requirement $requirement);
|
||||
public function addRequirement(\Szurubooru\SearchServices\Requirements\Requirement $requirement);
|
||||
|
||||
public function getPageSize();
|
||||
|
||||
|
|
|
@ -3,4 +3,6 @@ namespace Szurubooru\SearchServices\Filters;
|
|||
|
||||
class PostFilter extends BasicFilter implements IFilter
|
||||
{
|
||||
const REQUIREMENT_TAG = 'tag';
|
||||
const REQUIREMENT_ID = 'id';
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ namespace Szurubooru\SearchServices\Parsers;
|
|||
|
||||
abstract class AbstractSearchParser
|
||||
{
|
||||
const ALLOW_COMPOSITE = 1;
|
||||
const ALLOW_RANGES = 2;
|
||||
|
||||
public function createFilterFromInputReader(\Szurubooru\Helpers\InputReader $inputReader)
|
||||
{
|
||||
$filter = $this->createFilter();
|
||||
|
@ -18,9 +21,9 @@ abstract class AbstractSearchParser
|
|||
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if ($token instanceof \Szurubooru\SearchServices\NamedSearchToken)
|
||||
if ($token instanceof \Szurubooru\SearchServices\Tokens\NamedSearchToken)
|
||||
$this->decorateFilterFromNamedToken($filter, $token);
|
||||
elseif ($token instanceof \Szurubooru\SearchServices\SearchToken)
|
||||
elseif ($token instanceof \Szurubooru\SearchServices\Tokens\SearchToken)
|
||||
$this->decorateFilterFromToken($filter, $token);
|
||||
else
|
||||
throw new \RuntimeException('Invalid search token type: ' . get_class($token));
|
||||
|
@ -37,6 +40,27 @@ abstract class AbstractSearchParser
|
|||
|
||||
protected abstract function getOrderColumn($token);
|
||||
|
||||
protected function createRequirementValue($text, $flags = 0)
|
||||
{
|
||||
if ((($flags & self::ALLOW_RANGES) === self::ALLOW_RANGES) and substr_count($text, '..') === 1)
|
||||
{
|
||||
list ($minValue, $maxValue) = explode('..', $text);
|
||||
$tokenValue = new \Szurubooru\SearchServices\Requirements\RequirementRangedValue();
|
||||
$tokenValue->setMinValue($minValue);
|
||||
$tokenValue->setMaxValue($maxValue);
|
||||
return $tokenValue;
|
||||
}
|
||||
else if ((($flags & self::ALLOW_COMPOSITE) === self::ALLOW_COMPOSITE) and strpos($text, ',') !== false)
|
||||
{
|
||||
$values = explode(',', $text);
|
||||
$tokenValue = new \Szurubooru\SearchServices\Requirements\RequirementCompositeValue();
|
||||
$tokenValue->setValues($values);
|
||||
return $tokenValue;
|
||||
}
|
||||
|
||||
return new \Szurubooru\SearchServices\Requirements\RequirementSingleValue($text);
|
||||
}
|
||||
|
||||
private function getOrder($query)
|
||||
{
|
||||
$order = [];
|
||||
|
@ -75,14 +99,14 @@ abstract class AbstractSearchParser
|
|||
|
||||
if (strpos($tokenText, ':') !== false)
|
||||
{
|
||||
$searchToken = new \Szurubooru\SearchServices\NamedSearchToken();
|
||||
list ($tokenKey, $tokenValue) = explode(':', $tokenText, 1);
|
||||
$searchToken = new \Szurubooru\SearchServices\Tokens\NamedSearchToken();
|
||||
list ($tokenKey, $tokenValue) = explode(':', $tokenText, 2);
|
||||
$searchToken->setKey($tokenKey);
|
||||
$searchToken->setValue($tokenValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
$searchToken = new \Szurubooru\SearchServices\SearchToken();
|
||||
$searchToken = new \Szurubooru\SearchServices\Tokens\SearchToken();
|
||||
$searchToken->setValue($tokenText);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,12 +10,27 @@ class PostSearchParser extends AbstractSearchParser
|
|||
|
||||
protected function decorateFilterFromToken($filter, $token)
|
||||
{
|
||||
throw new \BadMethodCallException('Not supported');
|
||||
$requirement = new \Szurubooru\SearchServices\Requirements\Requirement();
|
||||
$requirement->setType(\Szurubooru\SearchServices\Filters\PostFilter::REQUIREMENT_TAG);
|
||||
$requirement->setValue($this->createRequirementValue($token->getValue()));
|
||||
$requirement->setNegated($token->isNegated());
|
||||
$filter->addRequirement($requirement);
|
||||
}
|
||||
|
||||
protected function decorateFilterFromNamedToken($filter, $namedToken)
|
||||
protected function decorateFilterFromNamedToken($filter, $token)
|
||||
{
|
||||
throw new \BadMethodCallException('Not supported');
|
||||
if ($token->getKey() === 'id')
|
||||
{
|
||||
$requirement = new \Szurubooru\SearchServices\Requirements\Requirement();
|
||||
$requirement->setType(\Szurubooru\SearchServices\Filters\PostFilter::REQUIREMENT_ID);
|
||||
$requirement->setValue($this->createRequirementValue($token->getValue(), self::ALLOW_COMPOSITE | self::ALLOW_RANGES));
|
||||
$requirement->setNegated($token->isNegated());
|
||||
$filter->addRequirement($requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \BadMethodCallException('Not supported');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getOrderColumn($token)
|
||||
|
|
|
@ -20,12 +20,12 @@ class SnapshotSearchParser extends AbstractSearchParser
|
|||
|
||||
$requirement = new \Szurubooru\SearchServices\Requirements\Requirement();
|
||||
$requirement->setType(\Szurubooru\SearchServices\Filters\SnapshotFilter::REQUIREMENT_PRIMARY_KEY);
|
||||
$requirement->setValue($primaryKey);
|
||||
$requirement->setValue($this->createRequirementValue($primaryKey));
|
||||
$filter->addRequirement($requirement);
|
||||
|
||||
$requirement = new \Szurubooru\SearchServices\Requirements\Requirement();
|
||||
$requirement->setType(\Szurubooru\SearchServices\Filters\SnapshotFilter::REQUIREMENT_TYPE);
|
||||
$requirement->setValue(\Szurubooru\Helpers\EnumHelper::snapshotTypeFromString($type));
|
||||
$requirement->setValue($this->createRequirementValue(\Szurubooru\Helpers\EnumHelper::snapshotTypeFromString($type)));
|
||||
$filter->addRequirement($requirement);
|
||||
}
|
||||
|
||||
|
|
6
src/SearchServices/Requirements/IRequirementValue.php
Normal file
6
src/SearchServices/Requirements/IRequirementValue.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Szurubooru\SearchServices\Requirements;
|
||||
|
||||
interface IRequirementValue
|
||||
{
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Szurubooru\SearchServices;
|
||||
namespace Szurubooru\SearchServices\Requirements;
|
||||
|
||||
class Requirement
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ class Requirement
|
|||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue($value)
|
||||
public function setValue(IRequirementValue $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Szurubooru\SearchServices\Requirements;
|
||||
|
||||
class RequirementCompositeValue implements IRequirementValue
|
||||
{
|
||||
private $values = [];
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
$this->values = $values;
|
||||
}
|
||||
}
|
28
src/SearchServices/Requirements/RequirementRangedValue.php
Normal file
28
src/SearchServices/Requirements/RequirementRangedValue.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Szurubooru\SearchServices\Requirements;
|
||||
|
||||
class RequirementRangedValue implements IRequirementValue
|
||||
{
|
||||
private $minValue = null;
|
||||
private $maxValue = null;
|
||||
|
||||
public function getMinValue()
|
||||
{
|
||||
return $this->minValue;
|
||||
}
|
||||
|
||||
public function setMinValue($minValue)
|
||||
{
|
||||
$this->minValue = $minValue;
|
||||
}
|
||||
|
||||
public function getMaxValue()
|
||||
{
|
||||
return $this->maxValue;
|
||||
}
|
||||
|
||||
public function setMaxValue($maxValue)
|
||||
{
|
||||
$this->maxValue = $maxValue;
|
||||
}
|
||||
}
|
22
src/SearchServices/Requirements/RequirementSingleValue.php
Normal file
22
src/SearchServices/Requirements/RequirementSingleValue.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
namespace Szurubooru\SearchServices\Requirements;
|
||||
|
||||
class RequirementSingleValue implements IRequirementValue
|
||||
{
|
||||
private $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Szurubooru\SearchServices;
|
||||
namespace Szurubooru\SearchServices\Tokens;
|
||||
|
||||
class NamedSearchToken extends SearchToken
|
||||
{
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Szurubooru\SearchServices;
|
||||
namespace Szurubooru\SearchServices\Tokens;
|
||||
|
||||
class SearchToken
|
||||
{
|
|
@ -91,14 +91,14 @@ class PostService
|
|||
{
|
||||
$filter = new \Szurubooru\SearchServices\Filters\SnapshotFilter();
|
||||
|
||||
$requirement = new \Szurubooru\SearchServices\Requirement();
|
||||
$requirement = new \Szurubooru\SearchServices\Requirements\Requirement();
|
||||
$requirement->setType(\Szurubooru\SearchServices\Filters\SnapshotFilter::REQUIREMENT_PRIMARY_KEY);
|
||||
$requirement->setValue($post->getId());
|
||||
$requirement->setValue(new \Szurubooru\SearchServices\Requirements\RequirementSingleValue($post->getId()));
|
||||
$filter->addRequirement($requirement);
|
||||
|
||||
$requirement = new \Szurubooru\SearchServices\Requirement();
|
||||
$requirement = new \Szurubooru\SearchServices\Requirements\Requirement();
|
||||
$requirement->setType(\Szurubooru\SearchServices\Filters\SnapshotFilter::REQUIREMENT_TYPE);
|
||||
$requirement->setValue(\Szurubooru\Entities\Snapshot::TYPE_POST);
|
||||
$requirement->setValue(new \Szurubooru\SearchServices\Requirements\RequirementSingleValue(\Szurubooru\Entities\Snapshot::TYPE_POST));
|
||||
$filter->addRequirement($requirement);
|
||||
|
||||
return $this->historyService->getFiltered($filter)->getEntities();
|
||||
|
|
Loading…
Reference in a new issue