1
0
mirror of https://github.com/flarum/core.git synced 2025-08-06 08:27:42 +02:00

feat: revamp search (#3893)

* refactor: move gambits to frontend (#3885)
* refactor: move gambits to frontend
* test: GambitManager
* refactor: merge filterer and searcher concepts (#3892)
* chore: drop remaining backend regex gambits
* refactor: merge filterer & searcher concept
* refactor: adapt extenders
* refactor: no longer need to push gambits to `q`
* refactor: filters to gambits
* refactor: drop shred `Query` namespace
* chore: cleanup
* chore: leftover gambit references on the backend (#3894)
* chore: leftover gambit references on the backend
* chore: namespace
* feat: search driver backend extension API (#3902)
* feat: first iteration of search drivers
* feat: indexer API & tweaks
* feat: changes after POC driver
* fix: properly fire custom observables
* chore: remove debugging code
* fix: phpstan
* fix: custom eloquent events
* chore: drop POC usage
* test: indexer extender API
* fix: extension searcher fails without filters
* fix: phpstan
* fix: frontend created gambit
* feat: advanced page and localized driver settings (#3905)
* feat: allow getting total search results and replacing filters (#3906)
* feat: allow accessing total search results
* feat: allow replacing filters
* chore: phpstan
This commit is contained in:
Sami Mazouz
2023-11-11 19:43:09 +01:00
committed by GitHub
parent 9e04b010d8
commit 4b126d9f4c
161 changed files with 2734 additions and 2197 deletions

View File

@@ -19,9 +19,10 @@ use Flarum\Likes\Event\PostWasUnliked;
use Flarum\Likes\Notification\PostLikedBlueprint;
use Flarum\Likes\Query\LikedByFilter;
use Flarum\Likes\Query\LikedFilter;
use Flarum\Post\Filter\PostFilterer;
use Flarum\Post\Filter\PostSearcher;
use Flarum\Post\Post;
use Flarum\User\Filter\UserFilterer;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\User\Search\UserSearcher;
use Flarum\User\User;
return [
@@ -76,11 +77,9 @@ return [
->listen(PostWasUnliked::class, Listener\SendNotificationWhenPostIsUnliked::class)
->subscribe(Listener\SaveLikesToDatabase::class),
(new Extend\Filter(PostFilterer::class))
->addFilter(LikedByFilter::class),
(new Extend\Filter(UserFilterer::class))
->addFilter(LikedFilter::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(PostSearcher::class, LikedByFilter::class)
->addFilter(UserSearcher::class, LikedFilter::class),
(new Extend\Settings())
->default('flarum-likes.like_own_post', true),

View File

@@ -9,10 +9,14 @@
namespace Flarum\Likes\Query;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Filter\ValidateFilterTrait;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Flarum\Search\ValidateFilterTrait;
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class LikedByFilter implements FilterInterface
{
use ValidateFilterTrait;
@@ -22,11 +26,11 @@ class LikedByFilter implements FilterInterface
return 'likedBy';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$likedId = $this->asInt($filterValue);
$likedId = $this->asInt($value);
$filterState
$state
->getQuery()
->whereIn('id', function ($query) use ($likedId, $negate) {
$query->select('post_id')

View File

@@ -9,10 +9,14 @@
namespace Flarum\Likes\Query;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Filter\ValidateFilterTrait;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Flarum\Search\ValidateFilterTrait;
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class LikedFilter implements FilterInterface
{
use ValidateFilterTrait;
@@ -22,11 +26,11 @@ class LikedFilter implements FilterInterface
return 'liked';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$likedId = $this->asString($filterValue);
$likedId = $this->asString($value);
$filterState
$state
->getQuery()
->whereIn('id', function ($query) use ($likedId) {
$query->select('user_id')

View File

@@ -11,16 +11,16 @@ use Flarum\Api\Serializer\BasicDiscussionSerializer;
use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Discussion\Discussion;
use Flarum\Discussion\Event\Saving;
use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Discussion\Search\DiscussionSearcher;
use Flarum\Extend;
use Flarum\Lock\Access;
use Flarum\Lock\Event\DiscussionWasLocked;
use Flarum\Lock\Event\DiscussionWasUnlocked;
use Flarum\Lock\Filter\LockedFilter;
use Flarum\Lock\Listener;
use Flarum\Lock\Notification\DiscussionLockedBlueprint;
use Flarum\Lock\Post\DiscussionLockedPost;
use Flarum\Lock\Query\LockedFilterGambit;
use Flarum\Search\Database\DatabaseSearchDriver;
return [
(new Extend\Frontend('forum'))
@@ -57,9 +57,6 @@ return [
(new Extend\Policy())
->modelPolicy(Discussion::class, Access\DiscussionPolicy::class),
(new Extend\Filter(DiscussionFilterer::class))
->addFilter(LockedFilterGambit::class),
(new Extend\SimpleFlarumSearch(DiscussionSearcher::class))
->addGambit(LockedFilterGambit::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(DiscussionSearcher::class, LockedFilter::class),
];

View File

@@ -0,0 +1 @@
export { default as default } from '../common/extend';

View File

@@ -1,5 +1,7 @@
import app from 'flarum/admin/app';
export { default as extend } from './extend';
app.initializers.add('lock', () => {
app.extensionData.for('flarum-lock').registerPermission(
{

View File

@@ -0,0 +1,7 @@
import Extend from 'flarum/common/extenders';
import LockedGambit from './query/discussions/LockedGambit';
export default [
new Extend.Search() //
.gambit('discussions', LockedGambit),
];

View File

@@ -0,0 +1,23 @@
import IGambit from 'flarum/common/query/IGambit';
export default class LockedGambit implements IGambit {
pattern(): string {
return 'is:locked';
}
toFilter(_matches: string[], negate: boolean): Record<string, any> {
const key = (negate ? '-' : '') + 'locked';
return {
[key]: true,
};
}
filterKey(): string {
return 'locked';
}
fromFilter(value: string, negate: boolean): string {
return `${negate ? '-' : ''}is:locked`;
}
}

View File

@@ -2,7 +2,11 @@ import Extend from 'flarum/common/extenders';
import Discussion from 'flarum/common/models/Discussion';
import DiscussionLockedPost from './components/DiscussionLockedPost';
import commonExtend from '../common/extend';
export default [
...commonExtend,
new Extend.PostTypes() //
.add('discussionLocked', DiscussionLockedPost),

View File

@@ -0,0 +1,36 @@
<?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\Lock\Filter;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Illuminate\Database\Query\Builder;
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class LockedFilter implements FilterInterface
{
public function getFilterKey(): string
{
return 'locked';
}
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$this->constrain($state->getQuery(), $negate);
}
protected function constrain(Builder $query, bool $negate): void
{
$query->where('is_locked', ! $negate);
}
}

View File

@@ -1,44 +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\Lock\Query;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Search\AbstractRegexGambit;
use Flarum\Search\SearchState;
use Illuminate\Database\Query\Builder;
class LockedFilterGambit extends AbstractRegexGambit implements FilterInterface
{
protected function getGambitPattern(): string
{
return 'is:locked';
}
protected function conditions(SearchState $search, array $matches, bool $negate): void
{
$this->constrain($search->getQuery(), $negate);
}
public function getFilterKey(): string
{
return 'locked';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
{
$this->constrain($filterState->getQuery(), $negate);
}
protected function constrain(Builder $query, bool $negate): void
{
$query->where('is_locked', ! $negate);
}
}

View File

@@ -24,8 +24,9 @@ use Flarum\Post\Event\Hidden;
use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored;
use Flarum\Post\Event\Revised;
use Flarum\Post\Filter\PostFilterer;
use Flarum\Post\Filter\PostSearcher;
use Flarum\Post\Post;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\Tags\Api\Serializer\TagSerializer;
use Flarum\User\User;
@@ -114,9 +115,9 @@ return [
->listen(Hidden::class, Listener\UpdateMentionsMetadataWhenInvisible::class)
->listen(Deleted::class, Listener\UpdateMentionsMetadataWhenInvisible::class),
(new Extend\Filter(PostFilterer::class))
->addFilter(Filter\MentionedFilter::class)
->addFilter(Filter\MentionedPostFilter::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(PostSearcher::class, Filter\MentionedFilter::class)
->addFilter(PostSearcher::class, Filter\MentionedPostFilter::class),
(new Extend\ApiSerializer(CurrentUserSerializer::class))
->attribute('canMentionGroups', function (CurrentUserSerializer $serializer, User $user): bool {

View File

@@ -9,10 +9,14 @@
namespace Flarum\Mentions\Filter;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Filter\ValidateFilterTrait;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Flarum\Search\ValidateFilterTrait;
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class MentionedFilter implements FilterInterface
{
use ValidateFilterTrait;
@@ -22,11 +26,11 @@ class MentionedFilter implements FilterInterface
return 'mentioned';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$mentionedId = $this->asInt($filterValue);
$mentionedId = $this->asInt($value);
$filterState
$state
->getQuery()
->join('post_mentions_user', 'posts.id', '=', 'post_mentions_user.post_id')
->where('post_mentions_user.mentions_user_id', $negate ? '!=' : '=', $mentionedId);

View File

@@ -9,9 +9,13 @@
namespace Flarum\Mentions\Filter;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class MentionedPostFilter implements FilterInterface
{
public function getFilterKey(): string
@@ -19,11 +23,11 @@ class MentionedPostFilter implements FilterInterface
return 'mentionedPost';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$mentionedId = trim($filterValue, '"');
$mentionedId = trim($value, '"');
$filterState
$state
->getQuery()
->join('post_mentions_post', 'posts.id', '=', 'post_mentions_post.post_id')
->where('post_mentions_post.mentions_post_id', $negate ? '!=' : '=', $mentionedId);

View File

@@ -12,6 +12,7 @@ namespace Flarum\Nicknames;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\Extend;
use Flarum\Nicknames\Access\UserPolicy;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\User\Event\Saving;
use Flarum\User\Search\UserSearcher;
use Flarum\User\User;
@@ -52,8 +53,8 @@ return [
(new Extend\Validator(UserValidator::class))
->configure(AddNicknameValidation::class),
(new Extend\SimpleFlarumSearch(UserSearcher::class))
->setFullTextGambit(NicknameFullTextGambit::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->setFulltext(UserSearcher::class, NicknameFullTextFilter::class),
(new Extend\Policy())
->modelPolicy(User::class, UserPolicy::class),

View File

@@ -1,6 +1,9 @@
import app from 'flarum/admin/app';
import Alert from 'flarum/common/components/Alert';
import Link from 'flarum/common/components/Link';
import BasicsPage from 'flarum/admin/components/BasicsPage';
import extractText from 'flarum/common/utils/extractText';
import { extend } from 'flarum/common/extend';
app.initializers.add('flarum/nicknames', () => {
app.extensionData
@@ -55,4 +58,8 @@ app.initializers.add('flarum/nicknames', () => {
},
'start'
);
extend(BasicsPage.prototype, 'driverLocale', function (locale) {
locale.display_name['nickname'] = extractText(app.translator.trans('flarum-nicknames.admin.basics.display_name_driver_options.nickname'));
});
});

View File

@@ -1,5 +1,8 @@
flarum-nicknames:
admin:
basics:
display_name_driver_options:
nickname: Nickname
permissions:
edit_own_nickname_label: Edit own nickname
settings:

View File

@@ -9,19 +9,16 @@
namespace Flarum\Nicknames;
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
use Flarum\Search\GambitInterface;
use Flarum\Search\AbstractFulltextFilter;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\SearchState;
use Flarum\User\UserRepository;
use Illuminate\Database\Eloquent\Builder;
class NicknameFullTextGambit implements GambitInterface
/**
* @extends AbstractFulltextFilter<DatabaseSearchState>
*/
class NicknameFullTextFilter extends AbstractFulltextFilter
{
public function __construct(
protected UserRepository $users
@@ -37,14 +34,12 @@ class NicknameFullTextGambit implements GambitInterface
->orWhere('nickname', 'like', "{$searchValue}%");
}
public function apply(SearchState $search, string $bit): bool
public function search(SearchState $state, string $value): void
{
$search->getQuery()
$state->getQuery()
->whereIn(
'id',
$this->getUserSearchSubQuery($bit)
$this->getUserSearchSubQuery($value)
);
return true;
}
}

View File

@@ -11,16 +11,16 @@ use Flarum\Api\Controller\ListDiscussionsController;
use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Discussion\Discussion;
use Flarum\Discussion\Event\Saving;
use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Discussion\Search\DiscussionSearcher;
use Flarum\Extend;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\Sticky\Event\DiscussionWasStickied;
use Flarum\Sticky\Event\DiscussionWasUnstickied;
use Flarum\Sticky\Listener;
use Flarum\Sticky\Listener\SaveStickyToDatabase;
use Flarum\Sticky\PinStickiedDiscussionsToTop;
use Flarum\Sticky\Post\DiscussionStickiedPost;
use Flarum\Sticky\Query\StickyFilterGambit;
use Flarum\Sticky\Query\StickyFilter;
return [
(new Extend\Frontend('forum'))
@@ -54,10 +54,7 @@ return [
->listen(DiscussionWasStickied::class, [Listener\CreatePostWhenDiscussionIsStickied::class, 'whenDiscussionWasStickied'])
->listen(DiscussionWasUnstickied::class, [Listener\CreatePostWhenDiscussionIsStickied::class, 'whenDiscussionWasUnstickied']),
(new Extend\Filter(DiscussionFilterer::class))
->addFilter(StickyFilterGambit::class)
->addFilterMutator(PinStickiedDiscussionsToTop::class),
(new Extend\SimpleFlarumSearch(DiscussionSearcher::class))
->addGambit(StickyFilterGambit::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(DiscussionSearcher::class, StickyFilter::class)
->addMutator(DiscussionSearcher::class, PinStickiedDiscussionsToTop::class),
];

View File

@@ -0,0 +1 @@
export { default as default } from '../common/extend';

View File

@@ -1,5 +1,7 @@
import app from 'flarum/admin/app';
export { default as extend } from './extend';
app.initializers.add('flarum-sticky', () => {
app.extensionData.for('flarum-sticky').registerPermission(
{

View File

@@ -0,0 +1,7 @@
import Extend from 'flarum/common/extenders';
import StickyGambit from './query/discussions/StickyGambit';
export default [
new Extend.Search() //
.gambit('discussions', StickyGambit),
];

View File

@@ -0,0 +1,23 @@
import IGambit from 'flarum/common/query/IGambit';
export default class StickyGambit implements IGambit {
pattern(): string {
return 'is:sticky';
}
toFilter(_matches: string[], negate: boolean): Record<string, any> {
const key = (negate ? '-' : '') + 'sticky';
return {
[key]: true,
};
}
filterKey(): string {
return 'sticky';
}
fromFilter(value: string, negate: boolean): string {
return `${negate ? '-' : ''}is:sticky`;
}
}

View File

@@ -2,7 +2,11 @@ import Extend from 'flarum/common/extenders';
import Discussion from 'flarum/common/models/Discussion';
import DiscussionStickiedPost from './components/DiscussionStickiedPost';
import commonExtend from '../common/extend';
export default [
...commonExtend,
new Extend.PostTypes() //
.add('discussionStickied', DiscussionStickiedPost),

View File

@@ -9,23 +9,23 @@
namespace Flarum\Sticky;
use Flarum\Filter\FilterState;
use Flarum\Query\QueryCriteria;
use Flarum\Tags\Query\TagFilterGambit;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\SearchCriteria;
use Flarum\Tags\Search\Filter\TagFilter;
class PinStickiedDiscussionsToTop
{
public function __invoke(FilterState $filterState, QueryCriteria $criteria): void
public function __invoke(DatabaseSearchState $state, SearchCriteria $criteria): void
{
if ($criteria->sortIsDefault) {
$query = $filterState->getQuery();
if ($criteria->sortIsDefault && ! $state->isFulltextSearch()) {
$query = $state->getQuery();
// If we are viewing a specific tag, then pin all stickied
// discussions to the top no matter what.
$filters = $filterState->getActiveFilters();
$filters = $state->getActiveFilters();
if ($count = count($filters)) {
if ($count === 1 && $filters[0] instanceof TagFilterGambit) {
if ($count === 1 && $filters[0] instanceof TagFilter) {
if (! is_array($query->orders)) {
$query->orders = [];
}
@@ -51,14 +51,14 @@ class PinStickiedDiscussionsToTop
->selectRaw('1')
->from('discussion_user as sticky')
->whereColumn('sticky.discussion_id', 'id')
->where('sticky.user_id', '=', $filterState->getActor()->id)
->where('sticky.user_id', '=', $state->getActor()->id)
->whereColumn('sticky.last_read_post_number', '>=', 'last_post_number');
// Add the bindings manually (rather than as the second
// argument in orderByRaw) for now due to a bug in Laravel which
// would add the bindings in the wrong order.
$query->orderByRaw('is_sticky and not exists ('.$read->toSql().') and last_posted_at > ? desc')
->addBinding(array_merge($read->getBindings(), [$filterState->getActor()->marked_all_as_read_at ?: 0]), 'union');
->addBinding(array_merge($read->getBindings(), [$state->getActor()->marked_all_as_read_at ?: 0]), 'union');
$query->unionOrders = array_merge($query->unionOrders, $query->orders);
$query->unionLimit = $query->limit;

View File

@@ -0,0 +1,36 @@
<?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\Sticky\Query;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Illuminate\Database\Query\Builder;
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class StickyFilter implements FilterInterface
{
public function getFilterKey(): string
{
return 'sticky';
}
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$this->constrain($state->getQuery(), $negate);
}
protected function constrain(Builder $query, bool $negate): void
{
$query->where('is_sticky', ! $negate);
}
}

View File

@@ -1,44 +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\Sticky\Query;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Search\AbstractRegexGambit;
use Flarum\Search\SearchState;
use Illuminate\Database\Query\Builder;
class StickyFilterGambit extends AbstractRegexGambit implements FilterInterface
{
protected function getGambitPattern(): string
{
return 'is:sticky';
}
protected function conditions(SearchState $search, array $matches, bool $negate): void
{
$this->constrain($search->getQuery(), $negate);
}
public function getFilterKey(): string
{
return 'sticky';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
{
$this->constrain($filterState->getQuery(), $negate);
}
protected function constrain(Builder $query, bool $negate): void
{
$query->where('is_sticky', ! $negate);
}
}

View File

@@ -12,7 +12,6 @@ use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Approval\Event\PostWasApproved;
use Flarum\Discussion\Discussion;
use Flarum\Discussion\Event\Saving;
use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Discussion\Search\DiscussionSearcher;
use Flarum\Discussion\UserState;
use Flarum\Extend;
@@ -20,14 +19,18 @@ use Flarum\Post\Event\Deleted;
use Flarum\Post\Event\Hidden;
use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\Subscriptions\Filter\SubscriptionFilter;
use Flarum\Subscriptions\HideIgnoredFromAllDiscussionsPage;
use Flarum\Subscriptions\Listener;
use Flarum\Subscriptions\Notification\FilterVisiblePostsBeforeSending;
use Flarum\Subscriptions\Notification\NewPostBlueprint;
use Flarum\Subscriptions\Query\SubscriptionFilterGambit;
use Flarum\User\User;
return [
(new Extend\Frontend('admin'))
->js(__DIR__.'/js/dist/admin.js'),
(new Extend\Frontend('forum'))
->js(__DIR__.'/js/dist/forum.js')
->css(__DIR__.'/less/forum.less')
@@ -67,12 +70,9 @@ return [
->listen(Deleted::class, Listener\DeleteNotificationWhenPostIsHiddenOrDeleted::class)
->listen(Posted::class, Listener\FollowAfterReply::class),
(new Extend\Filter(DiscussionFilterer::class))
->addFilter(SubscriptionFilterGambit::class)
->addFilterMutator(HideIgnoredFromAllDiscussionsPage::class),
(new Extend\SimpleFlarumSearch(DiscussionSearcher::class))
->addGambit(SubscriptionFilterGambit::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(DiscussionSearcher::class, SubscriptionFilter::class)
->addMutator(DiscussionSearcher::class, HideIgnoredFromAllDiscussionsPage::class),
(new Extend\User())
->registerPreference('flarum-subscriptions.notify_for_all_posts', 'boolval', false),

View File

@@ -0,0 +1 @@
export * from './src/admin';

View File

@@ -0,0 +1 @@
export { default as default } from '../common/extend';

View File

@@ -0,0 +1 @@
export { default as extend } from './extend';

View File

@@ -0,0 +1,7 @@
import Extend from 'flarum/common/extenders';
import SubscriptionGambit from './query/discussions/SubscriptionGambit';
export default [
new Extend.Search() //
.gambit('discussions', SubscriptionGambit),
];

View File

@@ -0,0 +1,23 @@
import IGambit from 'flarum/common/query/IGambit';
export default class SubscriptionGambit implements IGambit {
pattern(): string {
return 'is:(follow|ignor)(?:ing|ed)';
}
toFilter(matches: string[], negate: boolean): Record<string, any> {
const type = matches[1] === 'follow' ? 'following' : 'ignoring';
return {
subscription: type,
};
}
filterKey(): string {
return 'subscription';
}
fromFilter(value: string, negate: boolean): string {
return `${negate ? '-' : ''}is:${value}`;
}
}

View File

@@ -36,12 +36,7 @@ export default function addSubscriptionFilter() {
extend(DiscussionListState.prototype, 'requestParams', function (params) {
if (this.params.onFollowing) {
params.filter ||= {};
if (params.filter.q) {
params.filter.q += ' is:following';
} else {
params.filter.subscription = 'following';
}
params.filter.subscription = 'following';
}
});
}

View File

@@ -2,7 +2,11 @@ import Extend from 'flarum/common/extenders';
import IndexPage from 'flarum/forum/components/IndexPage';
import Discussion from 'flarum/common/models/Discussion';
import commonExtend from '../common/extend';
export default [
...commonExtend,
new Extend.Routes() //
.add('following', '/following', IndexPage),

View File

@@ -7,42 +7,34 @@
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Subscriptions\Query;
namespace Flarum\Subscriptions\Filter;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Filter\ValidateFilterTrait;
use Flarum\Search\AbstractRegexGambit;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Flarum\Search\ValidateFilterTrait;
use Flarum\User\User;
use Illuminate\Database\Query\Builder;
class SubscriptionFilterGambit extends AbstractRegexGambit implements FilterInterface
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class SubscriptionFilter implements FilterInterface
{
use ValidateFilterTrait;
protected function getGambitPattern(): string
{
return 'is:(follow|ignor)(?:ing|ed)';
}
protected function conditions(SearchState $search, array $matches, bool $negate): void
{
$this->constrain($search->getQuery(), $search->getActor(), $matches[1], $negate);
}
public function getFilterKey(): string
{
return 'subscription';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$filterValue = $this->asString($filterValue);
$value = $this->asString($value);
preg_match('/^'.$this->getGambitPattern().'$/i', 'is:'.$filterValue, $matches);
preg_match('/^(follow|ignor)(?:ing|ed)$/i', $value, $matches);
$this->constrain($filterState->getQuery(), $filterState->getActor(), $matches[1], $negate);
$this->constrain($state->getQuery(), $state->getActor(), $matches[1], $negate);
}
protected function constrain(Builder $query, User $actor, string $subscriptionType, bool $negate): void

View File

@@ -9,18 +9,18 @@
namespace Flarum\Subscriptions;
use Flarum\Filter\FilterState;
use Flarum\Query\QueryCriteria;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\SearchCriteria;
class HideIgnoredFromAllDiscussionsPage
{
public function __invoke(FilterState $filterState, QueryCriteria $criteria): void
public function __invoke(DatabaseSearchState $state, SearchCriteria $criteria): void
{
// We only want to hide on the "all discussions" page.
if (count($filterState->getActiveFilters()) === 0) {
if (count($state->getActiveFilters()) === 0 && ! $state->isFulltextSearch()) {
// TODO: might be better as `id IN (subquery)`?
$actor = $filterState->getActor();
$filterState->getQuery()->whereNotExists(function ($query) use ($actor) {
$actor = $state->getActor();
$state->getQuery()->whereNotExists(function ($query) use ($actor) {
$query->selectRaw(1)
->from('discussion_user')
->whereColumn('discussions.id', 'discussion_id')

View File

@@ -10,6 +10,7 @@
use Flarum\Api\Serializer\BasicUserSerializer;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\Extend;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\Suspend\Access\UserPolicy;
use Flarum\Suspend\AddUserSuspendAttributes;
use Flarum\Suspend\Event\Suspended;
@@ -17,10 +18,9 @@ use Flarum\Suspend\Event\Unsuspended;
use Flarum\Suspend\Listener;
use Flarum\Suspend\Notification\UserSuspendedBlueprint;
use Flarum\Suspend\Notification\UserUnsuspendedBlueprint;
use Flarum\Suspend\Query\SuspendedFilterGambit;
use Flarum\Suspend\Query\SuspendedFilter;
use Flarum\Suspend\RevokeAccessFromSuspendedUsers;
use Flarum\User\Event\Saving;
use Flarum\User\Filter\UserFilterer;
use Flarum\User\Search\UserSearcher;
use Flarum\User\User;
@@ -58,11 +58,8 @@ return [
(new Extend\User())
->permissionGroups(RevokeAccessFromSuspendedUsers::class),
(new Extend\Filter(UserFilterer::class))
->addFilter(SuspendedFilterGambit::class),
(new Extend\SimpleFlarumSearch(UserSearcher::class))
->addGambit(SuspendedFilterGambit::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(UserSearcher::class, SuspendedFilter::class),
(new Extend\View())
->namespace('flarum-suspend', __DIR__.'/views'),

View File

@@ -0,0 +1 @@
export { default as default } from '../common/extend';

View File

@@ -1,5 +1,7 @@
import app from 'flarum/admin/app';
export { default as extend } from './extend';
app.initializers.add('flarum-suspend', () => {
app.extensionData.for('flarum-suspend').registerPermission(
{

View File

@@ -0,0 +1,7 @@
import Extend from 'flarum/common/extenders';
import SuspendedGambit from './query/users/SuspendedGambit';
export default [
new Extend.Search() //
.gambit('users', SuspendedGambit),
];

View File

@@ -0,0 +1,23 @@
import IGambit from 'flarum/common/query/IGambit';
export default class SuspendedGambit implements IGambit {
pattern(): string {
return 'is:suspended';
}
toFilter(_matches: string[], negate: boolean): Record<string, any> {
const key = (negate ? '-' : '') + 'suspended';
return {
[key]: true,
};
}
filterKey(): string {
return 'suspended';
}
fromFilter(value: string, negate: boolean): string {
return `${negate ? '-' : ''}is:suspended`;
}
}

View File

@@ -2,10 +2,14 @@ import Extend from 'flarum/common/extenders';
import User from 'flarum/common/models/User';
import Model from 'flarum/common/Model';
import commonExtend from '../common/extend';
export default [
...commonExtend,
new Extend.Model(User)
.attribute<boolean>('canSuspend')
.attribute<Date, string | null | undefined>('suspendedUntil', Model.transformDate)
.attribute<Date | null | undefined, string | null | undefined>('suspendedUntil', Model.transformDate)
.attribute<string | null | undefined>('suspendReason')
.attribute<string | null | undefined>('suspendMessage'),
];

View File

@@ -10,52 +10,35 @@
namespace Flarum\Suspend\Query;
use Carbon\Carbon;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Search\AbstractRegexGambit;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Flarum\User\Guest;
use Flarum\User\UserRepository;
use Illuminate\Database\Query\Builder;
class SuspendedFilterGambit extends AbstractRegexGambit implements FilterInterface
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class SuspendedFilter implements FilterInterface
{
public function __construct(
protected UserRepository $users
) {
}
protected function getGambitPattern(): string
{
return 'is:suspended';
}
public function apply(SearchState $search, string $bit): bool
{
if (! $search->getActor()->can('suspend', new Guest())) {
return false;
}
return parent::apply($search, $bit);
}
protected function conditions(SearchState $search, array $matches, bool $negate): void
{
$this->constrain($search->getQuery(), $negate);
}
public function getFilterKey(): string
{
return 'suspended';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
if (! $filterState->getActor()->can('suspend', new Guest())) {
if (! $state->getActor()->can('suspend', new Guest())) {
return;
}
$this->constrain($filterState->getQuery(), $negate);
$this->constrain($state->getQuery(), $negate);
}
protected function constrain(Builder $query, bool $negate): void

View File

@@ -13,25 +13,25 @@ use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Api\Serializer\ForumSerializer;
use Flarum\Discussion\Discussion;
use Flarum\Discussion\Event\Saving;
use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Discussion\Search\DiscussionSearcher;
use Flarum\Extend;
use Flarum\Flags\Api\Controller\ListFlagsController;
use Flarum\Http\RequestUtil;
use Flarum\Post\Filter\PostFilterer;
use Flarum\Post\Filter\PostSearcher;
use Flarum\Post\Post;
use Flarum\Search\Database\DatabaseSearchDriver;
use Flarum\Tags\Access;
use Flarum\Tags\Api\Controller;
use Flarum\Tags\Api\Serializer\TagSerializer;
use Flarum\Tags\Content;
use Flarum\Tags\Event\DiscussionWasTagged;
use Flarum\Tags\Filter\HideHiddenTagsFromAllDiscussionsPage;
use Flarum\Tags\Filter\PostTagFilter;
use Flarum\Tags\Listener;
use Flarum\Tags\LoadForumTagsRelationship;
use Flarum\Tags\Post\DiscussionTaggedPost;
use Flarum\Tags\Query\TagFilterGambit;
use Flarum\Tags\Search\Gambit\FulltextGambit;
use Flarum\Tags\Search\Filter\PostTagFilter;
use Flarum\Tags\Search\Filter\TagFilter;
use Flarum\Tags\Search\FulltextFilter;
use Flarum\Tags\Search\HideHiddenTagsFromAllDiscussionsPage;
use Flarum\Tags\Search\TagSearcher;
use Flarum\Tags\Tag;
use Flarum\Tags\Utf8SlugDriver;
@@ -135,18 +135,12 @@ return [
->listen(DiscussionWasTagged::class, Listener\CreatePostWhenTagsAreChanged::class)
->subscribe(Listener\UpdateTagMetadata::class),
(new Extend\Filter(PostFilterer::class))
->addFilter(PostTagFilter::class),
(new Extend\Filter(DiscussionFilterer::class))
->addFilter(TagFilterGambit::class)
->addFilterMutator(HideHiddenTagsFromAllDiscussionsPage::class),
(new Extend\SimpleFlarumSearch(DiscussionSearcher::class))
->addGambit(TagFilterGambit::class),
(new Extend\SimpleFlarumSearch(TagSearcher::class))
->setFullTextGambit(FullTextGambit::class),
(new Extend\SearchDriver(DatabaseSearchDriver::class))
->addFilter(PostSearcher::class, PostTagFilter::class)
->addFilter(DiscussionSearcher::class, TagFilter::class)
->addMutator(DiscussionSearcher::class, HideHiddenTagsFromAllDiscussionsPage::class)
->addSearcher(Tag::class, TagSearcher::class)
->setFulltext(TagSearcher::class, FulltextFilter::class),
(new Extend\ModelUrl(Tag::class))
->addSlugDriver('default', Utf8SlugDriver::class),

View File

@@ -10,6 +10,7 @@ import Form from 'flarum/common/components/Form';
import EditTagModal from './EditTagModal';
import tagIcon from '../../common/helpers/tagIcon';
import sortTags from '../../common/utils/sortTags';
import FormSectionGroup, { FormSection } from '@flarum/core/src/admin/components/FormSectionGroup';
function tagItem(tag) {
return (
@@ -66,17 +67,15 @@ export default class TagsPage extends ExtensionPage {
<div className="TagsContent">
<div className="TagsContent-list">
<div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}>
<div className="SettingsGroups">
<div className="TagGroup">
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
<FormSectionGroup>
<FormSection className="TagGroup" label={app.translator.trans('flarum-tags.admin.tags.primary_heading')}>
<ol className="TagList TagList--primary">{tags.filter((tag) => tag.position() !== null && !tag.isChild()).map(tagItem)}</ol>
<Button className="Button TagList-button" icon="fas fa-plus" onclick={() => app.modal.show(EditTagModal, { primary: true })}>
{app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')}
</Button>
</div>
</FormSection>
<div className="TagGroup TagGroup--secondary">
<label>{app.translator.trans('flarum-tags.admin.tags.secondary_heading')}</label>
<FormSection className="TagGroup TagGroup--secondary" label={app.translator.trans('flarum-tags.admin.tags.secondary_heading')}>
<ul className="TagList">
{tags
.filter((tag) => tag.position() === null)
@@ -86,41 +85,44 @@ export default class TagsPage extends ExtensionPage {
<Button className="Button TagList-button" icon="fas fa-plus" onclick={() => app.modal.show(EditTagModal, { primary: false })}>
{app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')}
</Button>
</div>
<Form label={app.translator.trans('flarum-tags.admin.tags.settings_heading')}>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minPrimaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minPrimaryTags()} bidi={maxPrimaryTags} />
</FormSection>
<FormSection label={app.translator.trans('flarum-tags.admin.tags.settings_heading')}>
<Form>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minPrimaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minPrimaryTags()} bidi={maxPrimaryTags} />
</div>
</div>
</div>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minSecondaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minSecondaryTags()} bidi={maxSecondaryTags} />
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minSecondaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minSecondaryTags()} bidi={maxSecondaryTags} />
</div>
</div>
</div>
<div className="Form-group Form-controls">{this.submitButton()}</div>
</Form>
</div>
<div className="Form-group Form-controls">{this.submitButton()}</div>
</Form>
</FormSection>
</FormSectionGroup>
<div className="TagsContent-footer">
<p>{app.translator.trans('flarum-tags.admin.tags.about_tags_text')}</p>
</div>

View File

@@ -1,7 +1,11 @@
import Extend from 'flarum/common/extenders';
import Tag from './models/Tag';
import TagGambit from './query/discussions/TagGambit';
export default [
new Extend.Store() //
.add('tags', Tag),
new Extend.Search() //
.gambit('discussions', TagGambit),
];

View File

@@ -0,0 +1,23 @@
import IGambit from 'flarum/common/query/IGambit';
export default class TagGambit implements IGambit {
pattern(): string {
return 'tag:(.+)';
}
toFilter(matches: string[], negate: boolean): Record<string, any> {
const key = (negate ? '-' : '') + 'tag';
return {
[key]: matches[1].split(','),
};
}
filterKey(): string {
return 'tag';
}
fromFilter(value: string, negate: boolean): string {
return `${negate ? '-' : ''}tag:${value}`;
}
}

View File

@@ -124,14 +124,8 @@ export default function addTagFilter() {
}
if (this.params.tags) {
const filter = params.filter ?? {};
filter.tag = this.params.tags;
// TODO: replace this with a more robust system.
const q = filter.q;
if (q) {
filter.q = `${q} tag:${this.params.tags}`;
}
params.filter = filter;
params.filter ||= {};
params.filter.tag = this.params.tags;
}
});
}

View File

@@ -13,7 +13,6 @@
.TagsContent-list {
padding: 20px 0 0;
}
.TagList,
@@ -22,6 +21,7 @@
padding: 0;
color: var(--muted-color);
font-size: 13px;
margin-top: 0;
>li {
display: inline-block;
@@ -80,77 +80,35 @@ li:not(.sortable-dragging)>.TagListItem-info:hover>.Button {
height: 34px;
}
.SettingsGroups {
display: flex;
column-count: 3;
column-gap: 30px;
flex-wrap: wrap;
@media (@tablet-up) {
.TagGroup--secondary {
max-width: 250px !important;
}
@media (@tablet-up) {
.TagGroup--secondary {
max-width: 250px !important;
}
}
.Form {
min-width: 300px;
max-height: 500px;
.TagList-button {
background: none;
border: 1px dashed var(--control-bg);
height: 40px;
margin: auto auto 0 0;
}
>label {
margin-bottom: 10px;
}
.TagSettings-rangeInput {
input {
width: 80px;
display: inline;
margin: 0 5px;
.TagSettings-rangeInput {
input {
width: 80px;
display: inline;
margin: 0 5px;
&:first-child {
margin-left: 0;
}
}
}
}
.TagGroup,
.Form {
display: inline-grid;
padding: 10px 20px;
min-height: 20vh;
max-width: 400px;
grid-template-rows: min-content;
border: 1px solid var(--control-bg);
border-radius: var(--border-radius);
flex: 1 1 160px;
@media (max-width: 1209px) {
margin-bottom: 20px;
}
>ol {
>li {
margin-top: 8px;
.Button {
float: right;
visibility: hidden;
margin: -8px -16px -8px 16px;
}
}
}
.TagList-button {
background: none;
border: 1px dashed var(--control-bg);
height: 40px;
margin: auto auto 0 0;
}
>label {
float: left;
font-weight: bold;
color: var(--muted-color);
&:first-child {
margin-left: 0;
}
}
}
.TagGroup {
ol {
> li:not(:first-child) {
margin-top: 8px;
}
}
}

View File

@@ -12,9 +12,10 @@ namespace Flarum\Tags\Api\Controller;
use Flarum\Api\Controller\AbstractListController;
use Flarum\Http\RequestUtil;
use Flarum\Http\UrlGenerator;
use Flarum\Query\QueryCriteria;
use Flarum\Search\SearchCriteria;
use Flarum\Search\SearchManager;
use Flarum\Tags\Api\Serializer\TagSerializer;
use Flarum\Tags\Search\TagSearcher;
use Flarum\Tags\Tag;
use Flarum\Tags\TagRepository;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
@@ -35,7 +36,7 @@ class ListTagsController extends AbstractListController
public function __construct(
protected TagRepository $tags,
protected TagSearcher $searcher,
protected SearchManager $search,
protected UrlGenerator $url
) {
}
@@ -53,7 +54,8 @@ class ListTagsController extends AbstractListController
}
if (array_key_exists('q', $filters)) {
$results = $this->searcher->search(new QueryCriteria($actor, $filters), $limit, $offset);
$results = $this->search->query(Tag::class, new SearchCriteria($actor, $filters, $limit, $offset));
$tags = $results->getResults();
$document->addPaginationLinks(

View File

@@ -7,12 +7,16 @@
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tags\Filter;
namespace Flarum\Tags\Search\Filter;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Filter\ValidateFilterTrait;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Flarum\Search\ValidateFilterTrait;
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class PostTagFilter implements FilterInterface
{
use ValidateFilterTrait;
@@ -22,11 +26,11 @@ class PostTagFilter implements FilterInterface
return 'tag';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$ids = $this->asIntArray($filterValue);
$ids = $this->asIntArray($value);
$filterState->getQuery()
$state->getQuery()
->join('discussion_tag', 'discussion_tag.discussion_id', '=', 'posts.discussion_id')
->whereIn('discussion_tag.tag_id', $ids, 'and', $negate);
}

View File

@@ -7,20 +7,22 @@
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tags\Query;
namespace Flarum\Tags\Search\Filter;
use Flarum\Filter\FilterInterface;
use Flarum\Filter\FilterState;
use Flarum\Filter\ValidateFilterTrait;
use Flarum\Http\SlugManager;
use Flarum\Search\AbstractRegexGambit;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\Filter\FilterInterface;
use Flarum\Search\SearchState;
use Flarum\Search\ValidateFilterTrait;
use Flarum\Tags\Tag;
use Flarum\User\User;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Query\Builder;
class TagFilterGambit extends AbstractRegexGambit implements FilterInterface
/**
* @implements FilterInterface<DatabaseSearchState>
*/
class TagFilter implements FilterInterface
{
use ValidateFilterTrait;
@@ -29,24 +31,14 @@ class TagFilterGambit extends AbstractRegexGambit implements FilterInterface
) {
}
protected function getGambitPattern(): string
{
return 'tag:(.+)';
}
protected function conditions(SearchState $search, array $matches, bool $negate): void
{
$this->constrain($search->getQuery(), $matches[1], $negate, $search->getActor());
}
public function getFilterKey(): string
{
return 'tag';
}
public function filter(FilterState $filterState, string|array $filterValue, bool $negate): void
public function filter(SearchState $state, string|array $value, bool $negate): void
{
$this->constrain($filterState->getQuery(), $filterValue, $negate, $filterState->getActor());
$this->constrain($state->getQuery(), $value, $negate, $state->getActor());
}
protected function constrain(Builder $query, string|array $rawSlugs, bool $negate, User $actor): void

View File

@@ -7,14 +7,18 @@
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tags\Search\Gambit;
namespace Flarum\Tags\Search;
use Flarum\Search\GambitInterface;
use Flarum\Search\AbstractFulltextFilter;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\SearchState;
use Flarum\Tags\TagRepository;
use Illuminate\Database\Eloquent\Builder;
class FulltextGambit implements GambitInterface
/**
* @extends AbstractFulltextFilter<DatabaseSearchState>
*/
class FulltextFilter extends AbstractFulltextFilter
{
public function __construct(
protected TagRepository $tags
@@ -30,14 +34,12 @@ class FulltextGambit implements GambitInterface
->orWhere('slug', 'like', "$searchValue%");
}
public function apply(SearchState $search, string $bit): bool
public function search(SearchState $state, string $value): void
{
$search->getQuery()
$state->getQuery()
->whereIn(
'id',
$this->getTagSearchSubQuery($bit)
$this->getTagSearchSubQuery($value)
);
return true;
}
}

View File

@@ -7,21 +7,21 @@
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tags\Filter;
namespace Flarum\Tags\Search;
use Flarum\Filter\FilterState;
use Flarum\Query\QueryCriteria;
use Flarum\Search\Database\DatabaseSearchState;
use Flarum\Search\SearchCriteria;
use Flarum\Tags\Tag;
class HideHiddenTagsFromAllDiscussionsPage
{
public function __invoke(FilterState $filter, QueryCriteria $queryCriteria): void
public function __invoke(DatabaseSearchState $state, SearchCriteria $queryCriteria): void
{
if (count($filter->getActiveFilters()) > 0) {
if (count($state->getActiveFilters()) > 0 || $state->isFulltextSearch()) {
return;
}
$filter->getQuery()->whereNotIn('discussions.id', function ($query) {
$state->getQuery()->whereNotIn('discussions.id', function ($query) {
return $query->select('discussion_id')
->from('discussion_tag')
->whereIn('tag_id', Tag::where('is_hidden', 1)->pluck('id'));

View File

@@ -9,24 +9,15 @@
namespace Flarum\Tags\Search;
use Flarum\Search\AbstractSearcher;
use Flarum\Search\GambitManager;
use Flarum\Tags\TagRepository;
use Flarum\Search\Database\AbstractSearcher;
use Flarum\Tags\Tag;
use Flarum\User\User;
use Illuminate\Database\Eloquent\Builder;
class TagSearcher extends AbstractSearcher
{
public function __construct(
protected TagRepository $tags,
GambitManager $gambits,
array $searchMutators
) {
parent::__construct($gambits, $searchMutators);
}
protected function getQuery(User $actor): Builder
public function getQuery(User $actor): Builder
{
return $this->tags->query()->whereVisibleTo($actor);
return Tag::whereVisibleTo($actor)->select('tags.*');
}
}

View File

@@ -49,9 +49,9 @@ class ListWithFulltextSearchTest extends TestCase
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
$data = json_decode($contents = $response->getBody()->getContents(), true)['data'] ?? [];
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $contents);
$this->assertEquals($expected, Arr::pluck($data, 'id'));
}