mirror of
https://github.com/flarum/core.git
synced 2025-08-06 00:17:31 +02:00
Add extender and tests for filter
This commit is contained in:
65
src/Extend/Filter.php
Normal file
65
src/Extend/Filter.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?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 Flarum\Filter\Filterer;
|
||||||
|
use Flarum\Filter\FilterInterface;
|
||||||
|
use Flarum\Foundation\ContainerUtil;
|
||||||
|
use Illuminate\Contracts\Container\Container;
|
||||||
|
|
||||||
|
class Filter implements ExtenderInterface
|
||||||
|
{
|
||||||
|
private $resource;
|
||||||
|
private $filters = [];
|
||||||
|
private $filterMutators = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $resource: The ::class attribute of the resource this applies to, which is typically an Eloquent model.
|
||||||
|
*/
|
||||||
|
public function __construct($resource)
|
||||||
|
{
|
||||||
|
$this->resource = $resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a filter to run when the resource 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.
|
||||||
|
*/
|
||||||
|
public function addFilterMutator($callback)
|
||||||
|
{
|
||||||
|
$this->filterMutators[] = $callback;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extend(Container $container, Extension $extension = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
foreach ($this->filters as $filter) {
|
||||||
|
Filterer::addFilter($this->resource, $container->make($filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->filterMutators as $mutator) {
|
||||||
|
Filterer::addFilterMutator($this->resource, ContainerUtil::wrapCallback($mutator, $container));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -21,13 +21,17 @@ class Filterer
|
|||||||
|
|
||||||
protected static $filterMutators = [];
|
protected static $filterMutators = [];
|
||||||
|
|
||||||
public static function addFilter($resource, $filterKey, $filter)
|
public static function addFilter($resource, FilterInterface $filter)
|
||||||
{
|
{
|
||||||
if (!array_key_exists($resource, static::$filters)) {
|
if (!array_key_exists($resource, static::$filters)) {
|
||||||
static::$filters[$resource] = [];
|
static::$filters[$resource] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static::$filters[$resource][$filterKey] = $filter;
|
if (!array_key_exists($filter->getKey(), static::$filters[$resource])) {
|
||||||
|
static::$filters[$resource][$filter->getKey()] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static::$filters[$resource][$filter->getKey()][] = $filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function addFilterMutator($resource, $mutator)
|
public static function addFilterMutator($resource, $mutator)
|
||||||
@@ -52,20 +56,20 @@ class Filterer
|
|||||||
|
|
||||||
$query->whereVisibleTo($actor);
|
$query->whereVisibleTo($actor);
|
||||||
|
|
||||||
foreach (Arr::get(static::$filters, $resource, []) as $filterKey => $filterCallback) {
|
$wrappedFilter = new WrappedFilter($query->getQuery(), $actor);
|
||||||
if (array_key_exists($filterKey, $filters)) {
|
|
||||||
$filterCallback($query, $filters[$filterKey]);
|
foreach ($filters as $filterKey => $filterValue) {
|
||||||
|
foreach (Arr::get(static::$filters, "$resource.$filterKey", []) as $filter) {
|
||||||
|
$filter->apply($wrappedFilter, $filterValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$wrappedFilter = new WrappedFilter($query->getQuery(), $actor);
|
|
||||||
|
|
||||||
$this->applySort($wrappedFilter, $sort);
|
$this->applySort($wrappedFilter, $sort);
|
||||||
$this->applyOffset($wrappedFilter, $offset);
|
$this->applyOffset($wrappedFilter, $offset);
|
||||||
$this->applyLimit($wrappedFilter, $limit + 1);
|
$this->applyLimit($wrappedFilter, $limit + 1);
|
||||||
|
|
||||||
foreach (Arr::get(static::$filterMutators, $resource, []) as $mutator) {
|
foreach (Arr::get(static::$filterMutators, $resource, []) as $mutator) {
|
||||||
$mutator($wrappedFilter, $filters, $sort);
|
$mutator($query, $actor, $filters, $sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the filter query and retrieve the results. We get one more
|
// Execute the filter query and retrieve the results. We get one more
|
||||||
|
138
tests/integration/extenders/FilterTest.php
Normal file
138
tests/integration/extenders/FilterTest.php
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<?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\Discussion;
|
||||||
|
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||||
|
use Flarum\Extend;
|
||||||
|
use Flarum\Filter\FilterInterface;
|
||||||
|
use Flarum\Filter\WrappedFilter;
|
||||||
|
use Flarum\Search\AbstractSearch;
|
||||||
|
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||||
|
use Flarum\Tests\integration\TestCase;
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
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->adminUser(),
|
||||||
|
$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_gambit_has_effect_if_added()
|
||||||
|
{
|
||||||
|
$this->extend((new Extend\Filter(Discussion::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(Discussion::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(Discussion::class))->addFilterMutator(CustomFilterMutator::class));
|
||||||
|
|
||||||
|
$this->prepDb();
|
||||||
|
|
||||||
|
$this->assertEquals([], $this->filterDiscussions([], 5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoResultFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
public function getKey(): string
|
||||||
|
{
|
||||||
|
return 'noResult';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function apply(WrappedFilter $wrappedFilter, $filterValue)
|
||||||
|
{
|
||||||
|
if ($filterValue) {
|
||||||
|
$wrappedFilter->getQuery()
|
||||||
|
->whereRaw('0=1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomFilterMutator
|
||||||
|
{
|
||||||
|
public function __invoke($query, $actor, $filters, $sort)
|
||||||
|
{
|
||||||
|
$query->getQuery()->whereRaw('1=0');
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user