Added searching by tags and ids

This commit is contained in:
Marcin Kurczewski 2014-09-27 10:59:37 +02:00
parent 2763ff7ead
commit 5159214e80
19 changed files with 206 additions and 33 deletions

7
TODO
View file

@ -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

View file

@ -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() {

View file

@ -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>

View file

@ -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);
}
}

View file

@ -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());

View file

@ -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;
}

View file

@ -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();

View file

@ -3,4 +3,6 @@ namespace Szurubooru\SearchServices\Filters;
class PostFilter extends BasicFilter implements IFilter
{
const REQUIREMENT_TAG = 'tag';
const REQUIREMENT_ID = 'id';
}

View file

@ -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);
}

View file

@ -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)

View file

@ -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);
}

View file

@ -0,0 +1,6 @@
<?php
namespace Szurubooru\SearchServices\Requirements;
interface IRequirementValue
{
}

View file

@ -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;
}

View file

@ -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;
}
}

View 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;
}
}

View 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;
}
}

View file

@ -1,5 +1,5 @@
<?php
namespace Szurubooru\SearchServices;
namespace Szurubooru\SearchServices\Tokens;
class NamedSearchToken extends SearchToken
{

View file

@ -1,5 +1,5 @@
<?php
namespace Szurubooru\SearchServices;
namespace Szurubooru\SearchServices\Tokens;
class SearchToken
{

View file

@ -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();