Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 32 additions & 53 deletions src/Query/Scopes/Filters/Fields/Entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Statamic\Facades;
use Statamic\Support\Arr;
use Statamic\Support\Str;

use function Statamic\trans as __;

Expand All @@ -13,30 +12,28 @@ class Entries extends FieldtypeFilter
public function fieldItems()
{
return [
'field' => [
'type' => 'select',
'options' => [
'id' => __('ID'),
'title' => __('Title'),
],
'default' => 'title',
],
'operator' => [
'type' => 'select',
'options' => [
'like' => __('Contains'),
'=' => __('Is'),
'!=' => __('Isn\'t'),
'null' => __('Empty'),
'not-null' => __('Not empty'),
],
'default' => 'like',
'default' => '=',
],
'value' => [
'type' => 'text',
'placeholder' => __('Value'),
'if' => [
'operator' => 'contains_any like, =, !=',
'type' => 'entries',
'max_items' => 1,
'mode' => 'typeahead',
'create' => false,
'collections' => $this->fieldtype->config('collections'),
'search_index' => $this->fieldtype->config('search_index'),
'select_across_sites' => $this->fieldtype->config('select_across_sites'),
'blueprints' => $this->fieldtype->config('blueprints'),
'query_scopes' => $this->fieldtype->config('query_scopes'),
'unless' => [
'operator' => 'contains_any null, not-null',
],
'required' => false,
],
Expand All @@ -45,10 +42,7 @@ public function fieldItems()

public function apply($query, $handle, $values)
{
$config = $this->fieldtype->field()->config();
$maxItems = $config['max_items'] ?? 0;
$operator = $values['operator'];
$value = $values['value'];

if (in_array($operator, ['null', 'not-null'])) {
match ($operator) {
Expand All @@ -59,56 +53,41 @@ public function apply($query, $handle, $values)
return;
}

if ($operator === 'like') {
$value = Str::ensureLeft($value, '%');
$value = Str::ensureRight($value, '%');
}

if ($values['field'] == 'id') {
$maxItems === 1
? $query->where($handle, $operator, $value)
: $query->whereJsonContains($handle, $value);

if (! $id = $values['value']) {
return;
}

$ids = Facades\Entry::query()
->when($config['collections'] ?? null, fn ($query) => $query->whereIn('collection', $config['collections']))
->where($values['field'], $operator, $value)
->get(['id'])
->map(fn ($entry) => $entry->id())
->all();
$single = $this->fieldtype->config('max_items') === 1;

if (empty($ids)) {
$maxItems === 1
? $query->where($handle, -1)
: $query->whereJsonContains($handle, [-1]);
if ($operator === '=') {
$single
? $query->where($handle, $id)
: $query->whereJsonContains($handle, [$id]);

return;
}

if ($maxItems === 1) {
$query->whereIn($handle, $ids);

return;
}

$query->where(function ($subquery) use ($handle, $ids) {
foreach ($ids as $count => $id) {
$subquery->{$count == 0 ? 'whereJsonContains' : 'orWhereJsonContains'}($handle, [$id]);
}
});
$single
? $query->where($handle, '!=', $id)
: $query->whereJsonDoesntContain($handle, [$id]);
}

public function badge($values)
{
$field = $this->fieldtype->field()->display();
$selectedField = $values['field'];
$operator = $values['operator'];
$translatedField = Arr::get($this->fieldItems(), "field.options.{$selectedField}");
$translatedOperator = Arr::get($this->fieldItems(), "operator.options.{$operator}");
$value = $values['value'];

return $field.' '.$translatedField.' '.strtolower($translatedOperator).' '.$value;
if (in_array($operator, ['null', 'not-null'])) {
return $field.' '.strtolower($translatedOperator);
}

if (! $id = $values['value']) {
return null;
}

$title = Facades\Entry::find($id)?->value('title') ?? $id;

return $field.' '.strtolower($translatedOperator).' '.$title;
}
}
67 changes: 67 additions & 0 deletions tests/Query/FieldtypeFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Facades\Collection;
use Statamic\Facades\Entry;
use Statamic\Facades\Site;
use Statamic\Facades\Taxonomy;
use Statamic\Facades\Term;
use Statamic\Fields\Field;
use Statamic\Fieldtypes\Entries as EntriesFieldtype;
use Statamic\Fieldtypes\Integer as IntegerFieldtype;
use Statamic\Fieldtypes\Terms as TermsFieldtype;
use Statamic\Fieldtypes\Text;
use Statamic\Query\Scopes\Filters\Fields\Dimensions;
use Tests\Factories\EntryFactory;
use Tests\PreventSavingStacheItemsToDisk;
use Tests\TestCase;

Expand Down Expand Up @@ -90,4 +94,67 @@ public function it_shows_the_terms_filter_badge_in_the_selected_site()
Site::setSelected('en');
$this->assertEquals('Tags: One', $filter->badge(['operator' => 'like', 'term' => 'one']));
}

#[Test]
#[DataProvider('entriesFilterProvider')]
public function it_applies_the_entries_filter($maxItems, $values, $expected)
{
Collection::make('pages')->save();
Collection::make('topics')->save();

(new EntryFactory)->collection('topics')->id('topic-1')->slug('topic-one')->data(['title' => 'Topic One'])->create();
(new EntryFactory)->collection('topics')->id('topic-2')->slug('topic-two')->data(['title' => 'Topic Two'])->create();

if ($maxItems === 1) {
(new EntryFactory)->collection('pages')->id('page-a')->slug('page-a')->data(['related' => 'topic-1'])->create();
(new EntryFactory)->collection('pages')->id('page-b')->slug('page-b')->data(['related' => 'topic-2'])->create();
} else {
(new EntryFactory)->collection('pages')->id('page-a')->slug('page-a')->data(['related' => ['topic-1']])->create();
(new EntryFactory)->collection('pages')->id('page-b')->slug('page-b')->data(['related' => ['topic-1', 'topic-2']])->create();
}

(new EntryFactory)->collection('pages')->id('page-c')->slug('page-c')->create();

$filter = (new EntriesFieldtype)
->setField(new Field('related', ['type' => 'entries', 'max_items' => $maxItems]))
->filter();

$query = Entry::query()->where('collection', 'pages');
$filter->apply($query, 'related', $values);

$this->assertEquals($expected, $query->get()->map->id()->sort()->values()->all());
}

public static function entriesFilterProvider()
{
return [
'single: is' => [1, ['operator' => '=', 'value' => 'topic-1'], ['page-a']],
'single: isnt' => [1, ['operator' => '!=', 'value' => 'topic-1'], ['page-b', 'page-c']],
'single: empty' => [1, ['operator' => 'null', 'value' => null], ['page-c']],
'single: not empty' => [1, ['operator' => 'not-null', 'value' => null], ['page-a', 'page-b']],
'single: no entry selected' => [1, ['operator' => '=', 'value' => null], ['page-a', 'page-b', 'page-c']],
'multiple: is' => [null, ['operator' => '=', 'value' => 'topic-1'], ['page-a', 'page-b']],
'multiple: is, only one match' => [null, ['operator' => '=', 'value' => 'topic-2'], ['page-b']],
'multiple: isnt' => [null, ['operator' => '!=', 'value' => 'topic-1'], ['page-c']],
'multiple: empty' => [null, ['operator' => 'null', 'value' => null], ['page-c']],
'multiple: not empty' => [null, ['operator' => 'not-null', 'value' => null], ['page-a', 'page-b']],
];
}

#[Test]
public function it_shows_the_entries_filter_badge()
{
Collection::make('topics')->save();

(new EntryFactory)->collection('topics')->id('topic-1')->slug('topic-one')->data(['title' => 'Topic One'])->create();

$filter = (new EntriesFieldtype)
->setField(new Field('related', ['type' => 'entries', 'display' => 'Related']))
->filter();

$this->assertEquals('Related is Topic One', $filter->badge(['operator' => '=', 'value' => 'topic-1']));
$this->assertEquals("Related isn't Topic One", $filter->badge(['operator' => '!=', 'value' => 'topic-1']));
$this->assertEquals('Related empty', $filter->badge(['operator' => 'null', 'value' => null]));
$this->assertEquals('Related not empty', $filter->badge(['operator' => 'not-null', 'value' => null]));
}
}
Loading