diff --git a/src/Extend/Filter.php b/src/Extend/Filter.php
new file mode 100644
index 000000000..65c3156ab
--- /dev/null
+++ b/src/Extend/Filter.php
@@ -0,0 +1,65 @@
+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));
+ }
+ }
+}
diff --git a/src/Filter/Filterer.php b/src/Filter/Filterer.php
index cd844fcde..0d77efca3 100644
--- a/src/Filter/Filterer.php
+++ b/src/Filter/Filterer.php
@@ -21,13 +21,17 @@ class Filterer
protected static $filterMutators = [];
- public static function addFilter($resource, $filterKey, $filter)
+ public static function addFilter($resource, FilterInterface $filter)
{
if (!array_key_exists($resource, static::$filters)) {
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)
@@ -52,20 +56,20 @@ class Filterer
$query->whereVisibleTo($actor);
- foreach (Arr::get(static::$filters, $resource, []) as $filterKey => $filterCallback) {
- if (array_key_exists($filterKey, $filters)) {
- $filterCallback($query, $filters[$filterKey]);
+ $wrappedFilter = new WrappedFilter($query->getQuery(), $actor);
+
+ 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->applyOffset($wrappedFilter, $offset);
$this->applyLimit($wrappedFilter, $limit + 1);
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
diff --git a/tests/integration/extenders/FilterTest.php b/tests/integration/extenders/FilterTest.php
new file mode 100644
index 000000000..5ac67ba7d
--- /dev/null
+++ b/tests/integration/extenders/FilterTest.php
@@ -0,0 +1,138 @@
+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' => 'foo bar
'],
+ ['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => 'foo bar not the same
'],
+ ],
+ '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');
+ }
+}