mirror of
https://github.com/flarum/framework.git
synced 2024-11-25 17:57:04 +08:00
Search Filter Split, Use Same Controller (#2454)
This commit is contained in:
parent
1c578a83e4
commit
023871ef86
|
@ -11,10 +11,10 @@ namespace Flarum\Api\Controller;
|
|||
|
||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Filter\DiscussionFilterer;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
|
@ -43,11 +43,21 @@ class ListDiscussionsController extends AbstractListController
|
|||
'lastPost'
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public $sort = ['lastPostedAt' => 'desc'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $sortFields = ['lastPostedAt', 'commentCount', 'createdAt'];
|
||||
|
||||
/**
|
||||
* @var DiscussionFilterer
|
||||
*/
|
||||
protected $filterer;
|
||||
|
||||
/**
|
||||
* @var DiscussionSearcher
|
||||
*/
|
||||
|
@ -59,11 +69,13 @@ class ListDiscussionsController extends AbstractListController
|
|||
protected $url;
|
||||
|
||||
/**
|
||||
* @param DiscussionFilterer $filterer
|
||||
* @param DiscussionSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(DiscussionSearcher $searcher, UrlGenerator $url)
|
||||
public function __construct(DiscussionFilterer $filterer, DiscussionSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->filterer = $filterer;
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
@ -74,16 +86,19 @@ class ListDiscussionsController extends AbstractListController
|
|||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$filters = $this->extractFilter($request);
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = array_merge($this->extractInclude($request), ['state']);
|
||||
$include = array_merge($this->extractInclude($request), ['state']);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
$criteria = new SearchCriteria($actor, $filters, $sort);
|
||||
if (array_key_exists('q', $filters)) {
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
} else {
|
||||
$results = $this->filterer->filter($criteria, $limit, $offset);
|
||||
}
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('discussions.index'),
|
||||
|
@ -95,9 +110,9 @@ class ListDiscussionsController extends AbstractListController
|
|||
|
||||
Discussion::setStateUser($actor);
|
||||
|
||||
$results = $results->getResults()->load($load);
|
||||
$results = $results->getResults()->load($include);
|
||||
|
||||
if ($relations = array_intersect($load, ['firstPost', 'lastPost'])) {
|
||||
if ($relations = array_intersect($include, ['firstPost', 'lastPost'])) {
|
||||
foreach ($results as $discussion) {
|
||||
foreach ($relations as $relation) {
|
||||
if ($discussion->$relation) {
|
||||
|
|
|
@ -12,8 +12,8 @@ namespace Flarum\Api\Controller;
|
|||
use Flarum\Api\Serializer\UserSerializer;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\User\Filter\UserFilterer;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
|
@ -40,6 +40,11 @@ class ListUsersController extends AbstractListController
|
|||
'joinedAt'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var UserFilterer
|
||||
*/
|
||||
protected $filterer;
|
||||
|
||||
/**
|
||||
* @var UserSearcher
|
||||
*/
|
||||
|
@ -51,11 +56,13 @@ class ListUsersController extends AbstractListController
|
|||
protected $url;
|
||||
|
||||
/**
|
||||
* @param UserFilterer $filterer
|
||||
* @param UserSearcher $searcher
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(UserSearcher $searcher, UrlGenerator $url)
|
||||
public function __construct(UserFilterer $filterer, UserSearcher $searcher, UrlGenerator $url)
|
||||
{
|
||||
$this->filterer = $filterer;
|
||||
$this->searcher = $searcher;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
@ -69,16 +76,19 @@ class ListUsersController extends AbstractListController
|
|||
|
||||
$actor->assertCan('viewUserList');
|
||||
|
||||
$query = Arr::get($this->extractFilter($request), 'q');
|
||||
$filters = $this->extractFilter($request);
|
||||
$sort = $this->extractSort($request);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query, $sort);
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
$load = $this->extractInclude($request);
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
$results = $this->searcher->search($criteria, $limit, $offset, $load);
|
||||
$criteria = new SearchCriteria($actor, $filters, $sort);
|
||||
if (array_key_exists('q', $filters)) {
|
||||
$results = $this->searcher->search($criteria, $limit, $offset);
|
||||
} else {
|
||||
$results = $this->filterer->filter($criteria, $limit, $offset);
|
||||
}
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('users.index'),
|
||||
|
@ -88,6 +98,6 @@ class ListUsersController extends AbstractListController
|
|||
$results->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
return $results->getResults();
|
||||
return $results->getResults()->load($include);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
namespace Flarum\Discussion\Event;
|
||||
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\Search\SearchState;
|
||||
|
||||
/**
|
||||
* @deprecated beta 16, remove beta 17
|
||||
|
@ -18,7 +18,7 @@ use Flarum\Search\SearchCriteria;
|
|||
class Searching
|
||||
{
|
||||
/**
|
||||
* @var DiscussionSearch
|
||||
* @var SearchState
|
||||
*/
|
||||
public $search;
|
||||
|
||||
|
@ -28,10 +28,10 @@ class Searching
|
|||
public $criteria;
|
||||
|
||||
/**
|
||||
* @param DiscussionSearch $search
|
||||
* @param SearchState $search
|
||||
* @param \Flarum\Search\SearchCriteria $criteria
|
||||
*/
|
||||
public function __construct(DiscussionSearch $search, SearchCriteria $criteria)
|
||||
public function __construct(SearchState $search, SearchCriteria $criteria)
|
||||
{
|
||||
$this->search = $search;
|
||||
$this->criteria = $criteria;
|
||||
|
|
72
src/Discussion/Filter/AuthorFilterGambit.php
Normal file
72
src/Discussion/Filter/AuthorFilterGambit.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Filter;
|
||||
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\User\UserRepository;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class AuthorFilterGambit extends AbstractRegexGambit implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\User\UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param \Flarum\User\UserRepository $users
|
||||
*/
|
||||
public function __construct(UserRepository $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGambitPattern()
|
||||
{
|
||||
return 'author:(.+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(SearchState $search, array $matches, $negate)
|
||||
{
|
||||
$this->constrain($search->getQuery(), $matches[1], $negate);
|
||||
}
|
||||
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'author';
|
||||
}
|
||||
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate)
|
||||
{
|
||||
$this->constrain($filterState->getQuery(), $filterValue, $negate);
|
||||
}
|
||||
|
||||
protected function constrain(Builder $query, $rawUsernames, $negate)
|
||||
{
|
||||
$usernames = trim($rawUsernames, '"');
|
||||
$usernames = explode(',', $usernames);
|
||||
|
||||
$ids = [];
|
||||
foreach ($usernames as $username) {
|
||||
$ids[] = $this->users->getIdForUsername($username);
|
||||
}
|
||||
|
||||
$query->whereIn('discussions.user_id', $ids, 'and', $negate);
|
||||
}
|
||||
}
|
61
src/Discussion/Filter/CreatedFilterGambit.php
Normal file
61
src/Discussion/Filter/CreatedFilterGambit.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Filter;
|
||||
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\SearchState;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CreatedFilterGambit extends AbstractRegexGambit implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGambitPattern()
|
||||
{
|
||||
return 'created:(\d{4}\-\d\d\-\d\d)(\.\.(\d{4}\-\d\d\-\d\d))?';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(SearchState $search, array $matches, $negate)
|
||||
{
|
||||
$this->constrain($search->getQuery(), Arr::get($matches, 1), Arr::get($matches, 3), $negate);
|
||||
}
|
||||
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'created';
|
||||
}
|
||||
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate)
|
||||
{
|
||||
preg_match('/^'.$this->getGambitPattern().'$/i', 'created:'.$filterValue, $matches);
|
||||
|
||||
$this->constrain($filterState->getQuery(), Arr::get($matches, 1), Arr::get($matches, 3), $negate);
|
||||
}
|
||||
|
||||
public function constrain(Builder $query, ?string $firstDate, ?string $secondDate, $negate)
|
||||
{
|
||||
// If we've just been provided with a single YYYY-MM-DD date, then find
|
||||
// discussions that were started on that exact date. But if we've been
|
||||
// provided with a YYYY-MM-DD..YYYY-MM-DD range, then find discussions
|
||||
// that were started during that period.
|
||||
if (empty($secondDate)) {
|
||||
$query->whereDate('created_at', $negate ? '!=' : '=', $firstDate);
|
||||
} else {
|
||||
$query->whereBetween('created_at', [$firstDate, $secondDate], 'and', $negate);
|
||||
}
|
||||
}
|
||||
}
|
40
src/Discussion/Filter/DiscussionFilterer.php
Normal file
40
src/Discussion/Filter/DiscussionFilterer.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Filter;
|
||||
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Filter\AbstractFilterer;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DiscussionFilterer extends AbstractFilterer
|
||||
{
|
||||
/**
|
||||
* @var DiscussionRepository
|
||||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @param DiscussionRepository $discussions
|
||||
* @param array $filters
|
||||
* @param array $filterMutators
|
||||
*/
|
||||
public function __construct(DiscussionRepository $discussions, array $filters, array $filterMutators)
|
||||
{
|
||||
parent::__construct($filters, $filterMutators);
|
||||
|
||||
$this->discussions = $discussions;
|
||||
}
|
||||
|
||||
protected function getQuery(User $actor): Builder
|
||||
{
|
||||
return $this->discussions->query()->select('discussions.*')->whereVisibleTo($actor);
|
||||
}
|
||||
}
|
56
src/Discussion/Filter/HiddenFilterGambit.php
Normal file
56
src/Discussion/Filter/HiddenFilterGambit.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Filter;
|
||||
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\SearchState;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class HiddenFilterGambit extends AbstractRegexGambit implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGambitPattern()
|
||||
{
|
||||
return 'is:hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(SearchState $search, array $matches, $negate)
|
||||
{
|
||||
$this->constrain($search->getQuery(), $negate);
|
||||
}
|
||||
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'hidden';
|
||||
}
|
||||
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate)
|
||||
{
|
||||
$this->constrain($filterState->getQuery(), $negate);
|
||||
}
|
||||
|
||||
protected function constrain(Builder $query, bool $negate)
|
||||
{
|
||||
$query->where(function ($query) use ($negate) {
|
||||
if ($negate) {
|
||||
$query->whereNull('hidden_at')->where('comment_count', '>', 0);
|
||||
} else {
|
||||
$query->whereNotNull('hidden_at')->orWhere('comment_count', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,21 +7,18 @@
|
|||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
namespace Flarum\Discussion\Filter;
|
||||
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use LogicException;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class UnreadGambit extends AbstractRegexGambit
|
||||
class UnreadFilterGambit extends AbstractRegexGambit implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'is:unread';
|
||||
|
||||
/**
|
||||
* @var \Flarum\Discussion\DiscussionRepository
|
||||
*/
|
||||
|
@ -38,18 +35,35 @@ class UnreadGambit extends AbstractRegexGambit
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
public function getGambitPattern()
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
return 'is:unread';
|
||||
}
|
||||
|
||||
$actor = $search->getActor();
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(SearchState $search, array $matches, $negate)
|
||||
{
|
||||
$this->constrain($search->getQuery(), $search->getActor(), $negate);
|
||||
}
|
||||
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'unread';
|
||||
}
|
||||
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate)
|
||||
{
|
||||
$this->constrain($filterState->getQuery(), $filterState->getActor(), $negate);
|
||||
}
|
||||
|
||||
protected function constrain(Builder $query, User $actor, bool $negate)
|
||||
{
|
||||
if ($actor->exists) {
|
||||
$readIds = $this->discussions->getReadIds($actor);
|
||||
|
||||
$search->getQuery()->where(function ($query) use ($readIds, $negate, $actor) {
|
||||
$query->where(function ($query) use ($readIds, $negate, $actor) {
|
||||
if (! $negate) {
|
||||
$query->whereNotIn('id', $readIds)->where('last_posted_at', '>', $actor->marked_all_as_read_at ?: 0);
|
||||
} else {
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search;
|
||||
|
||||
use Flarum\Search\AbstractSearch;
|
||||
|
||||
/**
|
||||
* An object which represents the internal state of a search for discussions:
|
||||
* the search query, the user performing the search, the fallback sort order,
|
||||
* relevant post information, and a log of which gambits have been used.
|
||||
*/
|
||||
class DiscussionSearch extends AbstractSearch
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultSort = ['lastPostedAt' => 'desc'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $relevantPostIds = [];
|
||||
|
||||
/**
|
||||
* Get the related IDs for each result.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getRelevantPostIds()
|
||||
{
|
||||
return $this->relevantPostIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relevant post IDs for the results.
|
||||
*
|
||||
* @param array $relevantPostIds
|
||||
* @return void
|
||||
*/
|
||||
public function setRelevantPostIds(array $relevantPostIds)
|
||||
{
|
||||
$this->relevantPostIds = $relevantPostIds;
|
||||
}
|
||||
}
|
|
@ -11,10 +11,10 @@ namespace Flarum\Discussion\Search;
|
|||
|
||||
use Flarum\Discussion\DiscussionRepository;
|
||||
use Flarum\Discussion\Event\Searching;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\Search\AbstractSearcher;
|
||||
use Flarum\Search\GambitManager;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
@ -50,15 +50,10 @@ class DiscussionSearcher extends AbstractSearcher
|
|||
return $this->discussions->query()->select('discussions.*')->whereVisibleTo($actor);
|
||||
}
|
||||
|
||||
protected function getSearch(Builder $query, User $actor): AbstractSearch
|
||||
{
|
||||
return new DiscussionSearch($query->getQuery(), $actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated along with the Searching event, remove in Beta 17.
|
||||
*/
|
||||
protected function mutateSearch(AbstractSearch $search, SearchCriteria $criteria)
|
||||
protected function mutateSearch(SearchState $search, SearchCriteria $criteria)
|
||||
{
|
||||
parent::mutateSearch($search, $criteria);
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\User\UserRepository;
|
||||
use LogicException;
|
||||
|
||||
class AuthorGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'author:(.+)';
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param \Flarum\User\UserRepository $users
|
||||
*/
|
||||
public function __construct(UserRepository $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$usernames = trim($matches[1], '"');
|
||||
$usernames = explode(',', $usernames);
|
||||
|
||||
$ids = [];
|
||||
foreach ($usernames as $username) {
|
||||
$ids[] = $this->users->getIdForUsername($username);
|
||||
}
|
||||
|
||||
$search->getQuery()->whereIn('discussions.user_id', $ids, 'and', $negate);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use LogicException;
|
||||
|
||||
class CreatedGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'created:(\d{4}\-\d\d\-\d\d)(\.\.(\d{4}\-\d\d\-\d\d))?';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
// If we've just been provided with a single YYYY-MM-DD date, then find
|
||||
// discussions that were started on that exact date. But if we've been
|
||||
// provided with a YYYY-MM-DD..YYYY-MM-DD range, then find discussions
|
||||
// that were started during that period.
|
||||
if (empty($matches[3])) {
|
||||
$search->getQuery()->whereDate('created_at', $negate ? '!=' : '=', $matches[1]);
|
||||
} else {
|
||||
$search->getQuery()->whereBetween('created_at', [$matches[1], $matches[3]], 'and', $negate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,24 +9,18 @@
|
|||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\Search\GambitInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use LogicException;
|
||||
|
||||
class FulltextGambit implements GambitInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $bit)
|
||||
public function apply(SearchState $search, $bit)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
// Replace all non-word characters with spaces.
|
||||
// We do this to prevent MySQL fulltext search boolean mode from taking
|
||||
// effect: https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Discussion\Search\DiscussionSearch;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use LogicException;
|
||||
|
||||
class HiddenGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'is:hidden';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof DiscussionSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
|
||||
}
|
||||
|
||||
$search->getQuery()->where(function ($query) use ($negate) {
|
||||
if ($negate) {
|
||||
$query->whereNull('hidden_at')->where('comment_count', '>', 0);
|
||||
} else {
|
||||
$query->whereNotNull('hidden_at')->orWhere('comment_count', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
74
src/Extend/Filter.php
Normal file
74
src/Extend/Filter.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Filter implements ExtenderInterface
|
||||
{
|
||||
private $filtererClass;
|
||||
private $filters = [];
|
||||
private $filterMutators = [];
|
||||
|
||||
/**
|
||||
* @param string $filtererClass: The ::class attribute of the filterer to extend
|
||||
*/
|
||||
public function __construct($filtererClass)
|
||||
{
|
||||
$this->filtererClass = $filtererClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to run when the filtererClass is filtered.
|
||||
*
|
||||
* @param string $filterClass: The ::class attribute of the filter you are adding.
|
||||
*/
|
||||
public function addFilter(string $filterClass)
|
||||
{
|
||||
$this->filters[] = $filterClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback through which to run all filter queries after filters have been applied.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or an invokable class, and should accept:
|
||||
* - Flarum\Filter\FilterState $filter
|
||||
* - Flarum\Search\SearchCriteria $criteria
|
||||
*/
|
||||
public function addFilterMutator($callback)
|
||||
{
|
||||
$this->filterMutators[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.filter.filters', function ($originalFilters) {
|
||||
foreach ($this->filters as $filter) {
|
||||
$originalFilters[$this->filtererClass][] = $filter;
|
||||
}
|
||||
|
||||
return $originalFilters;
|
||||
});
|
||||
$container->extend('flarum.filter.filter_mutators', function ($originalMutators) {
|
||||
foreach ($this->filterMutators as $mutator) {
|
||||
$originalMutators[$this->filtererClass][] = $mutator;
|
||||
}
|
||||
|
||||
return $originalMutators;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -60,7 +60,7 @@ class SimpleFlarumSearch implements ExtenderInterface
|
|||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or an invokable class, and should accept:
|
||||
* - Flarum\Search\AbstractSearch $search
|
||||
* - Flarum\Search\SearchState $search
|
||||
* - Flarum\Search\SearchCriteria $criteria
|
||||
*/
|
||||
public function addSearchMutator($callback)
|
||||
|
|
86
src/Filter/AbstractFilterer.php
Normal file
86
src/Filter/AbstractFilterer.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
use Flarum\Search\ApplySearchParametersTrait;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\Search\SearchResults;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
||||
abstract class AbstractFilterer
|
||||
{
|
||||
use ApplySearchParametersTrait;
|
||||
|
||||
protected $filters;
|
||||
|
||||
protected $filterMutators;
|
||||
|
||||
/**
|
||||
* @param array $filters
|
||||
* @param array $filterMutators
|
||||
*/
|
||||
public function __construct(array $filters, array $filterMutators)
|
||||
{
|
||||
$this->filters = $filters;
|
||||
$this->filterMutators = $filterMutators;
|
||||
}
|
||||
|
||||
abstract protected function getQuery(User $actor): Builder;
|
||||
|
||||
/**
|
||||
* @param SearchCriteria $criteria
|
||||
* @param mixed|null $limit
|
||||
* @param int $offset
|
||||
*
|
||||
* @return SearchResults
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function filter(SearchCriteria $criteria, int $limit = null, int $offset = 0): SearchResults
|
||||
{
|
||||
$actor = $criteria->actor;
|
||||
|
||||
$query = $this->getQuery($actor);
|
||||
|
||||
$filterState = new FilterState($query->getQuery(), $actor);
|
||||
|
||||
foreach ($criteria->query as $filterKey => $filterValue) {
|
||||
$negate = false;
|
||||
if (substr($filterKey, 0, 1) == '-') {
|
||||
$negate = true;
|
||||
$filterKey = substr($filterKey, 1);
|
||||
}
|
||||
foreach (Arr::get($this->filters, $filterKey, []) as $filter) {
|
||||
$filter->filter($filterState, $filterValue, $negate);
|
||||
}
|
||||
}
|
||||
|
||||
$this->applySort($filterState, $criteria->sort);
|
||||
$this->applyOffset($filterState, $offset);
|
||||
$this->applyLimit($filterState, $limit + 1);
|
||||
|
||||
foreach ($this->filterMutators as $mutator) {
|
||||
$mutator($query, $actor, $criteria->query, $criteria->sort);
|
||||
}
|
||||
|
||||
// Execute the filter query and retrieve the results. We get one more
|
||||
// results than the user asked for, so that we can say if there are more
|
||||
// results. If there are, we will get rid of that extra result.
|
||||
$results = $query->get();
|
||||
|
||||
if ($areMoreResults = $limit > 0 && $results->count() > $limit) {
|
||||
$results->pop();
|
||||
}
|
||||
|
||||
return new SearchResults($results, $areMoreResults);
|
||||
}
|
||||
}
|
26
src/Filter/FilterInterface.php
Normal file
26
src/Filter/FilterInterface.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* This filter will only be run when a query contains a filter param with this key.
|
||||
*/
|
||||
public function getFilterKey(): string;
|
||||
|
||||
/**
|
||||
* Filters a query.
|
||||
*
|
||||
* @param FilterState $filter
|
||||
* @param string $value The value of the requested filter
|
||||
*/
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate);
|
||||
}
|
85
src/Filter/FilterServiceProvider.php
Normal file
85
src/Filter/FilterServiceProvider.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
use Flarum\Discussion\Filter\AuthorFilterGambit;
|
||||
use Flarum\Discussion\Filter\CreatedFilterGambit;
|
||||
use Flarum\Discussion\Filter\DiscussionFilterer;
|
||||
use Flarum\Discussion\Filter\HiddenFilterGambit;
|
||||
use Flarum\Discussion\Filter\UnreadFilterGambit;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\User\Filter\EmailFilterGambit;
|
||||
use Flarum\User\Filter\GroupFilterGambit;
|
||||
use Flarum\User\Filter\UserFilterer;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class FilterServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('flarum.filter.filters', function () {
|
||||
return [
|
||||
DiscussionFilterer::class => [
|
||||
AuthorFilterGambit::class,
|
||||
CreatedFilterGambit::class,
|
||||
HiddenFilterGambit::class,
|
||||
UnreadFilterGambit::class,
|
||||
],
|
||||
UserFilterer::class => [
|
||||
EmailFilterGambit::class,
|
||||
GroupFilterGambit::class,
|
||||
]
|
||||
];
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.filter.filter_mutators', function () {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
// We can resolve the filter mutators in the when->needs->give callback,
|
||||
// but we need to resolve at least one regardless so we know which
|
||||
// filterers we need to register filters for.
|
||||
$filters = $this->app->make('flarum.filter.filters');
|
||||
|
||||
foreach ($filters as $filterer => $filterClasses) {
|
||||
$this->app
|
||||
->when($filterer)
|
||||
->needs('$filters')
|
||||
->give(function () use ($filterClasses) {
|
||||
$compiled = [];
|
||||
|
||||
foreach ($filterClasses as $filterClass) {
|
||||
$filter = $this->app->make($filterClass);
|
||||
$compiled[$filter->getFilterKey()][] = $filter;
|
||||
}
|
||||
|
||||
return $compiled;
|
||||
});
|
||||
|
||||
$this->app
|
||||
->when($filterer)
|
||||
->needs('$filterMutators')
|
||||
->give(function () use ($filterer) {
|
||||
return array_map(function ($filterMutatorClass) {
|
||||
return ContainerUtil::wrapCallback($filterMutatorClass, $this->app);
|
||||
}, Arr::get($this->app->make('flarum.filter.filter_mutators'), $filterer, []));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
87
src/Filter/FilterState.php
Normal file
87
src/Filter/FilterState.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filter;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class FilterState
|
||||
{
|
||||
/**
|
||||
* @var Builder
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
protected $actor;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $defaultSort = [];
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param User $actor
|
||||
*/
|
||||
public function __construct(Builder $query, User $actor, $defaultSort = [])
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->actor = $actor;
|
||||
$this->defaultSort = $defaultSort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query builder for the search results query.
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who is performing the search.
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getActor()
|
||||
{
|
||||
return $this->actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default sort order for the search.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultSort()
|
||||
{
|
||||
return $this->defaultSort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default sort order for the search. This will only be applied if
|
||||
* a sort order has not been specified in the search criteria.
|
||||
*
|
||||
* @param mixed $defaultSort An array of sort-order pairs, where the column
|
||||
* is the key, and the order is the value. The order may be 'asc',
|
||||
* 'desc', or an array of IDs to order by.
|
||||
* Alternatively, a callable may be used.
|
||||
* @return mixed
|
||||
*/
|
||||
public function setDefaultSort($defaultSort)
|
||||
{
|
||||
$this->defaultSort = $defaultSort;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ use Flarum\Console\ConsoleServiceProvider;
|
|||
use Flarum\Database\DatabaseServiceProvider;
|
||||
use Flarum\Discussion\DiscussionServiceProvider;
|
||||
use Flarum\Extension\ExtensionServiceProvider;
|
||||
use Flarum\Filter\FilterServiceProvider;
|
||||
use Flarum\Formatter\FormatterServiceProvider;
|
||||
use Flarum\Forum\ForumServiceProvider;
|
||||
use Flarum\Frontend\FrontendServiceProvider;
|
||||
|
@ -117,6 +118,7 @@ class InstalledSite implements SiteInterface
|
|||
$laravel->register(ExtensionServiceProvider::class);
|
||||
$laravel->register(ErrorServiceProvider::class);
|
||||
$laravel->register(FilesystemServiceProvider::class);
|
||||
$laravel->register(FilterServiceProvider::class);
|
||||
$laravel->register(FormatterServiceProvider::class);
|
||||
$laravel->register(ForumServiceProvider::class);
|
||||
$laravel->register(FrontendServiceProvider::class);
|
||||
|
|
|
@ -13,15 +13,15 @@ abstract class AbstractRegexGambit implements GambitInterface
|
|||
{
|
||||
/**
|
||||
* The regex pattern to match the bit against.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pattern;
|
||||
protected function getGambitPattern()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $bit)
|
||||
public function apply(SearchState $search, $bit)
|
||||
{
|
||||
if ($matches = $this->match($bit)) {
|
||||
list($negate) = array_splice($matches, 1, 1);
|
||||
|
@ -40,7 +40,8 @@ abstract class AbstractRegexGambit implements GambitInterface
|
|||
*/
|
||||
protected function match($bit)
|
||||
{
|
||||
if (preg_match('/^(-?)'.$this->pattern.'$/i', $bit, $matches)) {
|
||||
// @deprecated, remove use of $this->pattern during beta 17.
|
||||
if (preg_match('/^(-?)'.($this->pattern ?? $this->getGambitPattern()).'$/i', $bit, $matches)) {
|
||||
return $matches;
|
||||
}
|
||||
}
|
||||
|
@ -48,11 +49,12 @@ abstract class AbstractRegexGambit implements GambitInterface
|
|||
/**
|
||||
* Apply conditions to the search, given that the gambit was matched.
|
||||
*
|
||||
* @param AbstractSearch $search The search object.
|
||||
* @param SearchState $search The search object.
|
||||
* @param array $matches An array of matches from the search bit.
|
||||
* @param bool $negate Whether or not the bit was negated, and thus whether
|
||||
* or not the conditions should be negated.
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function conditions(AbstractSearch $search, array $matches, $negate);
|
||||
// Uncomment for beta 17
|
||||
// abstract protected function conditions(SearchState $search, array $matches, $negate);
|
||||
}
|
||||
|
|
|
@ -9,90 +9,19 @@
|
|||
|
||||
namespace Flarum\Search;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Flarum\Filter\FilterState;
|
||||
|
||||
/**
|
||||
* An object which represents the internal state of a generic search:
|
||||
* the search query, the user performing the search, the fallback sort order,
|
||||
* and a log of which gambits have been used.
|
||||
* @deprecated, use SearchState instead.
|
||||
* These methods should be transferred over to SearchState in beta 17.
|
||||
*/
|
||||
abstract class AbstractSearch
|
||||
class AbstractSearch extends FilterState
|
||||
{
|
||||
/**
|
||||
* @var Builder
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
protected $actor;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultSort = [];
|
||||
|
||||
/**
|
||||
* @var GambitInterface[]
|
||||
*/
|
||||
protected $activeGambits = [];
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @param User $actor
|
||||
*/
|
||||
public function __construct(Builder $query, User $actor)
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->actor = $actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query builder for the search results query.
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who is performing the search.
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public function getActor()
|
||||
{
|
||||
return $this->actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default sort order for the search.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultSort()
|
||||
{
|
||||
return $this->defaultSort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default sort order for the search. This will only be applied if
|
||||
* a sort order has not been specified in the search criteria.
|
||||
*
|
||||
* @param mixed $defaultSort An array of sort-order pairs, where the column
|
||||
* is the key, and the order is the value. The order may be 'asc',
|
||||
* 'desc', or an array of IDs to order by.
|
||||
* @return mixed
|
||||
*/
|
||||
public function setDefaultSort($defaultSort)
|
||||
{
|
||||
$this->defaultSort = $defaultSort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the gambits that are active in this search.
|
||||
*
|
||||
|
|
|
@ -34,9 +34,7 @@ abstract class AbstractSearcher
|
|||
|
||||
abstract protected function getQuery(User $actor): Builder;
|
||||
|
||||
abstract protected function getSearch(Builder $query, User $actor): AbstractSearch;
|
||||
|
||||
protected function mutateSearch(AbstractSearch $search, SearchCriteria $criteria)
|
||||
protected function mutateSearch(SearchState $search, SearchCriteria $criteria)
|
||||
{
|
||||
foreach ($this->searchMutators as $mutator) {
|
||||
$mutator($search, $criteria);
|
||||
|
@ -49,16 +47,17 @@ abstract class AbstractSearcher
|
|||
* @param int $offset
|
||||
*
|
||||
* @return SearchResults
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function search(SearchCriteria $criteria, $limit = null, $offset = 0, array $load = [])
|
||||
public function search(SearchCriteria $criteria, $limit = null, $offset = 0): SearchResults
|
||||
{
|
||||
$actor = $criteria->actor;
|
||||
|
||||
$query = $this->getQuery($actor);
|
||||
|
||||
$search = $this->getSearch($query, $actor);
|
||||
$search = new SearchState($query->getQuery(), $actor);
|
||||
|
||||
$this->gambits->apply($search, $criteria->query);
|
||||
$this->gambits->apply($search, $criteria->query['q']);
|
||||
$this->applySort($search, $criteria->sort);
|
||||
$this->applyOffset($search, $offset);
|
||||
$this->applyLimit($search, $limit + 1);
|
||||
|
@ -74,8 +73,6 @@ abstract class AbstractSearcher
|
|||
$results->pop();
|
||||
}
|
||||
|
||||
$results->load($load);
|
||||
|
||||
return new SearchResults($results, $areMoreResults);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
namespace Flarum\Search;
|
||||
|
||||
use Flarum\Filter\FilterState;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait ApplySearchParametersTrait
|
||||
|
@ -16,10 +17,10 @@ trait ApplySearchParametersTrait
|
|||
/**
|
||||
* Apply sort criteria to a discussion search.
|
||||
*
|
||||
* @param AbstractSearch $search
|
||||
* @param FilterState $search
|
||||
* @param array $sort
|
||||
*/
|
||||
protected function applySort(AbstractSearch $search, array $sort = null)
|
||||
protected function applySort(FilterState $search, array $sort = null)
|
||||
{
|
||||
$sort = $sort ?: $search->getDefaultSort();
|
||||
|
||||
|
@ -39,10 +40,10 @@ trait ApplySearchParametersTrait
|
|||
}
|
||||
|
||||
/**
|
||||
* @param AbstractSearch $search
|
||||
* @param FilterState $search
|
||||
* @param int $offset
|
||||
*/
|
||||
protected function applyOffset(AbstractSearch $search, $offset)
|
||||
protected function applyOffset(FilterState $search, $offset)
|
||||
{
|
||||
if ($offset > 0) {
|
||||
$search->getQuery()->skip($offset);
|
||||
|
@ -50,10 +51,10 @@ trait ApplySearchParametersTrait
|
|||
}
|
||||
|
||||
/**
|
||||
* @param AbstractSearch $search
|
||||
* @param FilterState $search
|
||||
* @param int|null $limit
|
||||
*/
|
||||
protected function applyLimit(AbstractSearch $search, $limit)
|
||||
protected function applyLimit(FilterState $search, $limit)
|
||||
{
|
||||
if ($limit > 0) {
|
||||
$search->getQuery()->take($limit);
|
||||
|
|
|
@ -14,9 +14,9 @@ interface GambitInterface
|
|||
/**
|
||||
* Apply conditions to the searcher for a bit of the search string.
|
||||
*
|
||||
* @param AbstractSearch $search
|
||||
* @param SearchState $search
|
||||
* @param string $bit The piece of the search string.
|
||||
* @return bool Whether or not the gambit was active for this bit.
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $bit);
|
||||
public function apply(SearchState $search, $bit);
|
||||
}
|
||||
|
|
|
@ -55,10 +55,10 @@ class GambitManager
|
|||
/**
|
||||
* Apply gambits to a search, given a search query.
|
||||
*
|
||||
* @param AbstractSearch $search
|
||||
* @param SearchState $search
|
||||
* @param string $query
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $query)
|
||||
public function apply(SearchState $search, $query)
|
||||
{
|
||||
$query = $this->applyGambits($search, $query);
|
||||
|
||||
|
@ -89,11 +89,11 @@ class GambitManager
|
|||
}
|
||||
|
||||
/**
|
||||
* @param AbstractSearch $search
|
||||
* @param SearchState $search
|
||||
* @param string $query
|
||||
* @return string
|
||||
*/
|
||||
protected function applyGambits(AbstractSearch $search, $query)
|
||||
protected function applyGambits(SearchState $search, $query)
|
||||
{
|
||||
$bits = $this->explode($query);
|
||||
|
||||
|
@ -121,10 +121,10 @@ class GambitManager
|
|||
}
|
||||
|
||||
/**
|
||||
* @param AbstractSearch $search
|
||||
* @param SearchState $search
|
||||
* @param string $query
|
||||
*/
|
||||
protected function applyFulltext(AbstractSearch $search, $query)
|
||||
protected function applyFulltext(SearchState $search, $query)
|
||||
{
|
||||
if (! $this->fulltextGambit) {
|
||||
return;
|
||||
|
|
|
@ -13,22 +13,22 @@ use Flarum\User\User;
|
|||
|
||||
/**
|
||||
* Represents the criteria that will determine the entire result set of a
|
||||
* search. The limit and offset are not included because they only determine
|
||||
* query. The limit and offset are not included because they only determine
|
||||
* which part of the entire result set will be returned.
|
||||
*/
|
||||
class SearchCriteria
|
||||
{
|
||||
/**
|
||||
* The user performing the search.
|
||||
* The user performing the query.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $actor;
|
||||
|
||||
/**
|
||||
* The search query.
|
||||
* Query params.
|
||||
*
|
||||
* @var string
|
||||
* @var array
|
||||
*/
|
||||
public $query;
|
||||
|
||||
|
@ -42,8 +42,8 @@ class SearchCriteria
|
|||
public $sort;
|
||||
|
||||
/**
|
||||
* @param User $actor The user performing the search.
|
||||
* @param string $query The search query.
|
||||
* @param User $actor The user performing the query.
|
||||
* @param array $query The query params.
|
||||
* @param array $sort An array of sort-order pairs, where the column is the
|
||||
* key, and the order is the value. The order may be 'asc', 'desc', or
|
||||
* an array of IDs to order by.
|
||||
|
|
|
@ -9,19 +9,19 @@
|
|||
|
||||
namespace Flarum\Search;
|
||||
|
||||
use Flarum\Discussion\Filter\AuthorFilterGambit;
|
||||
use Flarum\Discussion\Filter\CreatedFilterGambit;
|
||||
use Flarum\Discussion\Filter\HiddenFilterGambit;
|
||||
use Flarum\Discussion\Filter\UnreadFilterGambit;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Discussion\Search\Gambit\AuthorGambit;
|
||||
use Flarum\Discussion\Search\Gambit\CreatedGambit;
|
||||
use Flarum\Discussion\Search\Gambit\FulltextGambit as DiscussionFulltextGambit;
|
||||
use Flarum\Discussion\Search\Gambit\HiddenGambit;
|
||||
use Flarum\Discussion\Search\Gambit\UnreadGambit;
|
||||
use Flarum\Event\ConfigureDiscussionGambits;
|
||||
use Flarum\Event\ConfigureUserGambits;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\User\Search\Gambit\EmailGambit;
|
||||
use Flarum\User\Filter\EmailFilterGambit;
|
||||
use Flarum\User\Filter\GroupFilterGambit;
|
||||
use Flarum\User\Search\Gambit\FulltextGambit as UserFulltextGambit;
|
||||
use Flarum\User\Search\Gambit\GroupGambit;
|
||||
use Flarum\User\Search\UserSearcher;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
|
@ -42,14 +42,14 @@ class SearchServiceProvider extends AbstractServiceProvider
|
|||
$this->app->singleton('flarum.simple_search.gambits', function () {
|
||||
return [
|
||||
DiscussionSearcher::class => [
|
||||
AuthorGambit::class,
|
||||
CreatedGambit::class,
|
||||
HiddenGambit::class,
|
||||
UnreadGambit::class
|
||||
AuthorFilterGambit::class,
|
||||
CreatedFilterGambit::class,
|
||||
HiddenFilterGambit::class,
|
||||
UnreadFilterGambit::class
|
||||
],
|
||||
UserSearcher::class => [
|
||||
EmailGambit::class,
|
||||
GroupGambit::class
|
||||
EmailFilterGambit::class,
|
||||
GroupFilterGambit::class
|
||||
]
|
||||
];
|
||||
});
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Search;
|
||||
namespace Flarum\Search;
|
||||
|
||||
use Flarum\Search\AbstractSearch;
|
||||
|
||||
class UserSearch extends AbstractSearch
|
||||
class SearchState extends AbstractSearch
|
||||
{
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
namespace Flarum\User\Event;
|
||||
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\User\Search\UserSearch;
|
||||
use Flarum\Search\SearchState;
|
||||
|
||||
/**
|
||||
* @deprecated beta 16, remove beta 17
|
||||
|
@ -18,7 +18,7 @@ use Flarum\User\Search\UserSearch;
|
|||
class Searching
|
||||
{
|
||||
/**
|
||||
* @var \Flarum\User\Search\UserSearch
|
||||
* @var \Flarum\User\Search\SearchState
|
||||
*/
|
||||
public $search;
|
||||
|
||||
|
@ -28,10 +28,10 @@ class Searching
|
|||
public $criteria;
|
||||
|
||||
/**
|
||||
* @param UserSearch $search
|
||||
* @param SearchState $search
|
||||
* @param SearchCriteria $criteria
|
||||
*/
|
||||
public function __construct(UserSearch $search, SearchCriteria $criteria)
|
||||
public function __construct(SearchState $search, SearchCriteria $criteria)
|
||||
{
|
||||
$this->search = $search;
|
||||
$this->criteria = $criteria;
|
||||
|
|
68
src/User/Filter/EmailFilterGambit.php
Normal file
68
src/User/Filter/EmailFilterGambit.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Filter;
|
||||
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\SearchState;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class EmailFilterGambit extends AbstractRegexGambit implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(SearchState $search, $bit)
|
||||
{
|
||||
if (! $search->getActor()->hasPermission('user.edit')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::apply($search, $bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGambitPattern()
|
||||
{
|
||||
return 'email:(.+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(SearchState $search, array $matches, $negate)
|
||||
{
|
||||
$this->constrain($search->getQuery(), $matches[1], $negate);
|
||||
}
|
||||
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'email';
|
||||
}
|
||||
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate)
|
||||
{
|
||||
if (! $filterState->getActor()->hasPermission('user.edit')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->constrain($filterState->getQuery(), $filterValue, $negate);
|
||||
}
|
||||
|
||||
protected function constrain(Builder $query, $rawEmail, bool $negate)
|
||||
{
|
||||
$email = trim($rawEmail, '"');
|
||||
|
||||
$query->where('email', $negate ? '!=' : '=', $email);
|
||||
}
|
||||
}
|
68
src/User/Filter/GroupFilterGambit.php
Normal file
68
src/User/Filter/GroupFilterGambit.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Filter;
|
||||
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class GroupFilterGambit extends AbstractRegexGambit implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGambitPattern()
|
||||
{
|
||||
return 'group:(.+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(SearchState $search, array $matches, $negate)
|
||||
{
|
||||
$this->constrain($search->getQuery(), $search->getActor(), $matches[1], $negate);
|
||||
}
|
||||
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'group';
|
||||
}
|
||||
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate)
|
||||
{
|
||||
$this->constrain($filterState->getQuery(), $filterState->getActor(), $filterValue, $negate);
|
||||
}
|
||||
|
||||
protected function constrain(Builder $query, User $actor, string $rawQuery, bool $negate)
|
||||
{
|
||||
$groupIdentifiers = explode(',', trim($rawQuery, '"'));
|
||||
|
||||
$groupQuery = Group::whereVisibleTo($actor);
|
||||
|
||||
foreach ($groupIdentifiers as $identifier) {
|
||||
if (is_numeric($identifier)) {
|
||||
$groupQuery->orWhere('id', $identifier);
|
||||
} else {
|
||||
$groupQuery->orWhere('name_singular', $identifier)->orWhere('name_plural', $identifier);
|
||||
}
|
||||
}
|
||||
|
||||
$userIds = $groupQuery->join('group_user', 'groups.id', 'group_user.group_id')
|
||||
->pluck('group_user.user_id')
|
||||
->all();
|
||||
|
||||
$query->whereIn('id', $userIds, 'and', $negate);
|
||||
}
|
||||
}
|
40
src/User/Filter/UserFilterer.php
Normal file
40
src/User/Filter/UserFilterer.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Filter;
|
||||
|
||||
use Flarum\Filter\AbstractFilterer;
|
||||
use Flarum\User\User;
|
||||
use Flarum\User\UserRepository;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class UserFilterer extends AbstractFilterer
|
||||
{
|
||||
/**
|
||||
* @var UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param UserRepository $users
|
||||
* @param array $filters
|
||||
* @param array $filterMutators
|
||||
*/
|
||||
public function __construct(UserRepository $users, array $filters, array $filterMutators)
|
||||
{
|
||||
parent::__construct($filters, $filterMutators);
|
||||
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
protected function getQuery(User $actor): Builder
|
||||
{
|
||||
return $this->users->query()->whereVisibleTo($actor);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Search\Gambit;
|
||||
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\User\Search\UserSearch;
|
||||
use Flarum\User\UserRepository;
|
||||
use LogicException;
|
||||
|
||||
class EmailGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'email:(.+)';
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @param \Flarum\User\UserRepository $users
|
||||
*/
|
||||
public function __construct(UserRepository $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $bit)
|
||||
{
|
||||
if (! $search->getActor()->hasPermission('user.edit')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::apply($search, $bit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof UserSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a UserSearch');
|
||||
}
|
||||
|
||||
$email = trim($matches[1], '"');
|
||||
|
||||
$search->getQuery()->where('email', $negate ? '!=' : '=', $email);
|
||||
}
|
||||
}
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
namespace Flarum\User\Search\Gambit;
|
||||
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\Search\GambitInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\User\UserRepository;
|
||||
|
||||
class FulltextGambit implements GambitInterface
|
||||
|
@ -43,7 +43,7 @@ class FulltextGambit implements GambitInterface
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $searchValue)
|
||||
public function apply(SearchState $search, $searchValue)
|
||||
{
|
||||
$search->getQuery()
|
||||
->whereIn(
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Search\Gambit;
|
||||
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Group\GroupRepository;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\User\Search\UserSearch;
|
||||
use LogicException;
|
||||
|
||||
class GroupGambit extends AbstractRegexGambit
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pattern = 'group:(.+)';
|
||||
|
||||
/**
|
||||
* @var GroupRepository
|
||||
*/
|
||||
protected $groups;
|
||||
|
||||
/**
|
||||
* @param \Flarum\Group\GroupRepository $groups
|
||||
*/
|
||||
public function __construct(GroupRepository $groups)
|
||||
{
|
||||
$this->groups = $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
{
|
||||
if (! $search instanceof UserSearch) {
|
||||
throw new LogicException('This gambit can only be applied on a UserSearch');
|
||||
}
|
||||
|
||||
$groupIdentifiers = $this->extractGroupIdentifiers($matches);
|
||||
|
||||
$groupQuery = Group::whereVisibleTo($search->getActor());
|
||||
|
||||
foreach ($groupIdentifiers as $identifier) {
|
||||
if (is_numeric($identifier)) {
|
||||
$groupQuery->orWhere('id', $identifier);
|
||||
} else {
|
||||
$groupQuery->orWhere('name_singular', $identifier)->orWhere('name_plural', $identifier);
|
||||
}
|
||||
}
|
||||
|
||||
$userIds = $groupQuery->join('group_user', 'groups.id', 'group_user.group_id')
|
||||
->pluck('group_user.user_id')
|
||||
->all();
|
||||
|
||||
$search->getQuery()->whereIn('id', $userIds, 'and', $negate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the group names from the pattern match.
|
||||
*
|
||||
* @param array $matches
|
||||
* @return array
|
||||
*/
|
||||
protected function extractGroupIdentifiers(array $matches)
|
||||
{
|
||||
return explode(',', trim($matches[1], '"'));
|
||||
}
|
||||
}
|
|
@ -9,10 +9,10 @@
|
|||
|
||||
namespace Flarum\User\Search;
|
||||
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\Search\AbstractSearcher;
|
||||
use Flarum\Search\GambitManager;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\User\Event\Searching;
|
||||
use Flarum\User\User;
|
||||
use Flarum\User\UserRepository;
|
||||
|
@ -54,15 +54,10 @@ class UserSearcher extends AbstractSearcher
|
|||
return $this->users->query()->whereVisibleTo($actor);
|
||||
}
|
||||
|
||||
protected function getSearch(Builder $query, User $actor): AbstractSearch
|
||||
{
|
||||
return new UserSearch($query->getQuery(), $actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated along with the Searching event, remove in Beta 17.
|
||||
*/
|
||||
protected function mutateSearch(AbstractSearch $search, SearchCriteria $criteria)
|
||||
protected function mutateSearch(SearchState $search, SearchCriteria $criteria)
|
||||
{
|
||||
parent::mutateSearch($search, $criteria);
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace Flarum\Tests\integration\api\discussions;
|
|||
use Carbon\Carbon;
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListTest extends TestCase
|
||||
{
|
||||
|
@ -26,10 +28,16 @@ class ListTest extends TestCase
|
|||
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
['id' => 2, 'title' => 'lightsail in title', 'created_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'not in title', 'created_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1],
|
||||
['id' => 4, 'title' => 'hidden', 'created_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'hidden_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar</p></t>'],
|
||||
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>foo bar</p></t>'],
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>not in text</p></t>'],
|
||||
['id' => 3, 'discussion_id' => 3, 'created_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
|
||||
['id' => 4, 'discussion_id' => 4, 'created_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
|
||||
],
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
|
@ -37,6 +45,16 @@ class ListTest extends TestCase
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark some discussions, but not others, as read to test that filter/gambit.
|
||||
*/
|
||||
protected function read()
|
||||
{
|
||||
$user = User::find(2);
|
||||
$user->marked_all_as_read_at = Carbon::createFromDate(1990, 0, 0)->toDateTimeString();
|
||||
$user->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
|
@ -49,22 +67,398 @@ class ListTest extends TestCase
|
|||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
$this->assertEquals(1, count($data['data']));
|
||||
$this->assertEquals(3, count($data['data']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function can_search_for_author()
|
||||
public function author_filter_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['author' => 'normal'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function author_filter_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['-author' => 'normal'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_filter_works_with_date()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['created' => '1995-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_filter_works_negated_with_date()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['-created' => '1995-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_filter_works_with_range()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['created' => '1980-05-21..2000-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_filter_works_negated_with_range()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['-created' => '1980-05-21..2000-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function hidden_filter_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 1])
|
||||
->withQueryParams([
|
||||
'filter' => ['hidden' => ''],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['4'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function hidden_filter_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 1])
|
||||
->withQueryParams([
|
||||
'filter' => ['-hidden' => ''],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1', '2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function unread_filter_works()
|
||||
{
|
||||
$this->app();
|
||||
$this->read();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 2])
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'author:normal foo'],
|
||||
'filter' => ['unread' => ''],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function unread_filter_works_when_negated()
|
||||
{
|
||||
$this->app();
|
||||
$this->read();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 2])
|
||||
->withQueryParams([
|
||||
'filter' => ['-unread' => ''],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function author_gambit_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'author:normal'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function author_gambit_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '-author:normal'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_gambit_works_with_date()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'created:1995-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_gambit_works_negated_with_date()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '-created:1995-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_gambit_works_with_range()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'created:1980-05-21..2000-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function created_gambit_works_negated_with_range()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '-created:1980-05-21..2000-05-21'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function hidden_gambit_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 1])
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'is:hidden'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['4'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function hidden_gambit_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 1])
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '-is:hidden'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1', '2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function unread_gambit_works()
|
||||
{
|
||||
$this->app();
|
||||
$this->read();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 2])
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => 'is:unread'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function unread_gambit_works_when_negated()
|
||||
{
|
||||
$this->app();
|
||||
$this->read();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', ['authenticatedAs' => 2])
|
||||
->withQueryParams([
|
||||
'filter' => ['q' => '-is:unread'],
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
// Order-independent comparison
|
||||
$this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,26 @@ namespace Flarum\Tests\integration\api\users;
|
|||
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ListTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->prepareDatabase([
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
|
@ -59,4 +74,200 @@ class ListTest extends TestCase
|
|||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function shows_full_results_without_search_or_filter()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['1', '2'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function group_filter_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['group' => '1'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function group_filter_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['-group' => '1'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['2'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function email_filter_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['email' => 'admin@machine.local'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function email_filter_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['-email' => 'admin@machine.local'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['2'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function email_filter_only_works_for_admin()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 2,
|
||||
])->withQueryParams([
|
||||
'filter' => ['email' => 'admin@machine.local'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['1', '2'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function group_gambit_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['q' => 'group:1'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function group_gambit_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['q' => '-group:1'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['2'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function email_gambit_works()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['q' => 'email:admin@machine.local'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['1'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function email_gambit_works_negated()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => ['q' => '-email:admin@machine.local'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals(['2'], Arr::pluck($data, 'id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function email_gambit_only_works_for_admin()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 2,
|
||||
])->withQueryParams([
|
||||
'filter' => ['q' => 'email:admin@machine.local'],
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
$this->assertEquals([], Arr::pluck($data, 'id'));
|
||||
}
|
||||
}
|
||||
|
|
134
tests/integration/extenders/FilterTest.php
Normal file
134
tests/integration/extenders/FilterTest.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Filter\DiscussionFilterer;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Filter\FilterInterface;
|
||||
use Flarum\Filter\FilterState;
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class FilterTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
public function prepDb()
|
||||
{
|
||||
$this->prepareDatabase([
|
||||
'discussions' => [
|
||||
['id' => 1, 'title' => 'DISCUSSION 1', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 1, 'comment_count' => 1],
|
||||
['id' => 2, 'title' => 'DISCUSSION 2', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 2, 'comment_count' => 1],
|
||||
],
|
||||
'posts' => [
|
||||
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar</p></t>'],
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar not the same</p></t>'],
|
||||
],
|
||||
'users' => [
|
||||
$this->normalUser(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function filterDiscussions($filters, $limit = null)
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions', [
|
||||
'authenticatedAs' => 1,
|
||||
])->withQueryParams([
|
||||
'filter' => $filters,
|
||||
'include' => 'mostRelevantPost',
|
||||
])
|
||||
);
|
||||
|
||||
return json_decode($response->getBody()->getContents(), true)['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function works_as_expected_with_no_modifications()
|
||||
{
|
||||
$this->prepDb();
|
||||
|
||||
$searchForAll = json_encode($this->filterDiscussions([], 5));
|
||||
$this->assertContains('DISCUSSION 1', $searchForAll);
|
||||
$this->assertContains('DISCUSSION 2', $searchForAll);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_filter_has_effect_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Filter(DiscussionFilterer::class))->addFilter(NoResultFilter::class));
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$withResultSearch = json_encode($this->filterDiscussions(['noResult' => 0], 5));
|
||||
$this->assertContains('DISCUSSION 1', $withResultSearch);
|
||||
$this->assertContains('DISCUSSION 2', $withResultSearch);
|
||||
$this->assertEquals([], $this->filterDiscussions(['noResult' => 1], 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function filter_mutator_has_effect_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Filter(DiscussionFilterer::class))->addFilterMutator(function ($query, $actor, $filters, $sort) {
|
||||
$query->getQuery()->whereRaw('1=0');
|
||||
}));
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$this->assertEquals([], $this->filterDiscussions([], 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function filter_mutator_has_effect_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Filter(DiscussionFilterer::class))->addFilterMutator(CustomFilterMutator::class));
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$this->assertEquals([], $this->filterDiscussions([], 5));
|
||||
}
|
||||
}
|
||||
|
||||
class NoResultFilter implements FilterInterface
|
||||
{
|
||||
public function getFilterKey(): string
|
||||
{
|
||||
return 'noResult';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function filter(FilterState $filterState, string $filterValue, bool $negate)
|
||||
{
|
||||
if ($filterValue) {
|
||||
$filterState->getQuery()
|
||||
->whereRaw('0=1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomFilterMutator
|
||||
{
|
||||
public function __invoke($query, $actor, $filters, $sort)
|
||||
{
|
||||
$query->getQuery()->whereRaw('1=0');
|
||||
}
|
||||
}
|
|
@ -13,9 +13,9 @@ use Carbon\Carbon;
|
|||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearch;
|
||||
use Flarum\Search\GambitInterface;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
|
@ -64,7 +64,7 @@ class SimpleFlarumSearchTest extends TestCase
|
|||
|
||||
$actor = User::find(1);
|
||||
|
||||
$criteria = new SearchCriteria($actor, $query);
|
||||
$criteria = new SearchCriteria($actor, ['q' => $query]);
|
||||
|
||||
return $this->app()->getContainer()->make(DiscussionSearcher::class)->search($criteria, $limit)->getResults();
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ class NoResultFullTextGambit implements GambitInterface
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(AbstractSearch $search, $searchValue)
|
||||
public function apply(SearchState $search, $searchValue)
|
||||
{
|
||||
$search->getQuery()
|
||||
->whereRaw('0=1');
|
||||
|
@ -151,12 +151,18 @@ class NoResultFullTextGambit implements GambitInterface
|
|||
|
||||
class NoResultFilterGambit extends AbstractRegexGambit
|
||||
{
|
||||
protected $pattern = 'noResult:(.+)';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGambitPattern()
|
||||
{
|
||||
return 'noResult:(.+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function conditions(AbstractSearch $search, array $matches, $negate)
|
||||
public function conditions(SearchState $search, array $matches, $negate)
|
||||
{
|
||||
$noResults = trim($matches[1], ' ');
|
||||
if ($noResults == '1') {
|
||||
|
|
Loading…
Reference in New Issue
Block a user