Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.x] Fix entries on the same date being ignored by collection previous/next tags #8921

Merged
52 changes: 44 additions & 8 deletions src/Tags/Collection/Entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
use Closure;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection as IlluminateCollection;
use Statamic\Contracts\Entries\QueryBuilder;
use Statamic\Contracts\Taxonomies\Term;
use Statamic\Entries\EntryCollection;
use Statamic\Facades\Collection;
use Statamic\Facades\Compare;
use Statamic\Facades\Entry;
use Statamic\Facades\Site;
use Statamic\Query\OrderBy;
use Statamic\Support\Arr;
use Statamic\Support\Str;
use Statamic\Tags\Concerns;
Expand Down Expand Up @@ -68,16 +70,20 @@ public function next($currentEntry)

$collection = $this->collections->first();
$primaryOrderBy = $this->orderBys->first();
$secondaryOrderBy = $this->orderBys->get(1);

if ($primaryOrderBy->direction === 'desc') {
$operator = '<';
if (! $secondaryOrderBy) {
$this->orderBys[] = new OrderBy('title', 'asc');
}

$primaryOperator = $primaryOrderBy->direction === 'desc' ? '<' : '>';
$secondaryOperator = $secondaryOrderBy?->direction === 'desc' ? '<' : '>';
duncanmcclean marked this conversation as resolved.
Show resolved Hide resolved

if ($primaryOrderBy->sort === 'order') {
throw_if(! $currentOrder = $currentEntry->order(), new \Exception('Current entry does not have an order'));
$query = $this->query()->where('order', $operator ?? '>', $currentOrder);
$query = $this->query()->where('order', $primaryOperator, $currentOrder);
} elseif ($collection->dated() && $primaryOrderBy->sort === 'date') {
$query = $this->query()->where('date', $operator ?? '>', $currentEntry->date());
$query = $this->queryPreviousNextByDate($currentEntry, $primaryOperator, $secondaryOperator);
} else {
throw new \Exception('collection:next requires ordered or dated collection');
}
Expand All @@ -93,16 +99,20 @@ public function previous($currentEntry)

$collection = $this->collections->first();
$primaryOrderBy = $this->orderBys->first();
$secondaryOrderBy = $this->orderBys->get(1);

if ($primaryOrderBy->direction === 'desc') {
$operator = '>';
if (! $secondaryOrderBy) {
$this->orderBys[] = new OrderBy('title', 'asc');
}

$primaryOperator = $primaryOrderBy->direction === 'desc' ? '>' : '<';
$secondaryOperator = $secondaryOrderBy?->direction === 'desc' ? '>' : '<';

if ($primaryOrderBy->sort === 'order') {
throw_if(! $currentOrder = $currentEntry->order(), new \Exception('Current entry does not have an order'));
$query = $this->query()->where('order', $operator ?? '<', $currentOrder);
$query = $this->query()->where('order', $primaryOperator, $currentOrder);
} elseif ($collection->dated() && $primaryOrderBy->sort === 'date') {
$query = $this->query()->where('date', $operator ?? '<', $currentEntry->date());
$query = $this->queryPreviousNextByDate($currentEntry, $primaryOperator, $secondaryOperator);
} else {
throw new \Exception('collection:previous requires ordered or dated collection');
}
Expand Down Expand Up @@ -141,6 +151,32 @@ public function newer($currentEntry)
: $this->previous($currentEntry);
}

protected function queryPreviousNextByDate($currentEntry, string $primaryOperator, string $secondaryOperator): QueryBuilder
{
$primaryOrderBy = $this->orderBys->first();
$secondaryOrderBy = $this->orderBys->get(1);

$currentEntryDate = $currentEntry->date();

// Get the IDs of any items that have the same date as the current entry,
// but come before/after the current entry sorted by the second column.
$previousOfSame = $this->query()
->where('date', $currentEntryDate)
->orderBy($secondaryOrderBy->sort, $secondaryOrderBy->direction)
->where('title', $secondaryOperator, $currentEntry->get('title'))
duncanmcclean marked this conversation as resolved.
Show resolved Hide resolved
->get()
->pluck('id')
->toArray();

return $this->query()
->where(fn ($query) => $query
->where('date', $primaryOperator, $currentEntryDate)
->orWhereIn('id', $previousOfSame)
)
->orderBy('date', $primaryOrderBy->direction)
->orderBy($secondaryOrderBy->sort, $secondaryOrderBy->direction);
}

protected function query()
{
$query = Entry::query()
Expand Down
52 changes: 52 additions & 0 deletions tests/Tags/Collection/CollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,32 @@ public function it_can_get_previous_and_next_entries_in_a_dated_desc_collection(
$this->assertEquals(['Grape', 'Hummus', 'Fig'], $this->runTagAndGetTitles('newer')); // Alias of prev when date:desc
}

/**
* @test
* https://github.com/statamic/cms/issues/1831
*/
public function it_can_get_previous_and_next_entries_in_a_dated_desc_collection_when_multiple_entries_share_the_same_date()
{
$this->foods->dated(true)->save();

$this->makeEntry($this->foods, 'a')->date('2023-01-01')->set('title', 'Apple')->save();
$this->makeEntry($this->foods, 'b')->date('2023-02-05')->set('title', 'Banana')->save();
$this->makeEntry($this->foods, 'c')->date('2023-02-05')->set('title', 'Carrot')->save();
$this->makeEntry($this->foods, 'd')->date('2023-03-07')->set('title', 'Danish')->save();

$this->setTagParameters([
'in' => 'foods',
'current' => $this->findEntryByTitle('Carrot')->id(),
'order_by' => 'date:desc|title:desc',
'limit' => 1,
]);

$this->assertEquals(['Danish'], $this->runTagAndGetTitles('previous'));
$this->assertEquals(['Danish'], $this->runTagAndGetTitles('newer')); // Alias of prev when date:desc
$this->assertEquals(['Banana'], $this->runTagAndGetTitles('next'));
$this->assertEquals(['Banana'], $this->runTagAndGetTitles('older')); // Alias of next when date:desc
}

/** @test */
public function it_can_get_previous_and_next_entries_in_a_dated_asc_collection()
{
Expand Down Expand Up @@ -384,6 +410,32 @@ public function it_can_get_previous_and_next_entries_in_a_dated_asc_collection()
$this->assertEquals(['Carrot', 'Banana', 'Danish'], $this->runTagAndGetTitles('older')); // Alias of prev when date:desc
}

/**
* @test
* https://github.com/statamic/cms/issues/1831
*/
public function it_can_get_previous_and_next_entries_in_a_dated_asc_collection_when_multiple_entries_share_the_same_date()
{
$this->foods->dated(true)->save();

$this->makeEntry($this->foods, 'a')->date('2023-01-01')->set('title', 'Apple')->save();
$this->makeEntry($this->foods, 'b')->date('2023-02-05')->set('title', 'Banana')->save();
$this->makeEntry($this->foods, 'c')->date('2023-02-05')->set('title', 'Carrot')->save();
$this->makeEntry($this->foods, 'd')->date('2023-03-07')->set('title', 'Danish')->save();

$this->setTagParameters([
'in' => 'foods',
'current' => $this->findEntryByTitle('Carrot')->id(),
'order_by' => 'date:asc|title:asc',
'limit' => 1,
]);

$this->assertEquals(['Banana'], $this->runTagAndGetTitles('previous'));
$this->assertEquals(['Banana'], $this->runTagAndGetTitles('older')); // Alias of previous when date:desc
$this->assertEquals(['Danish'], $this->runTagAndGetTitles('next'));
$this->assertEquals(['Danish'], $this->runTagAndGetTitles('newer')); // Alias of next when date:asc
}

/** @test */
public function it_can_get_previous_and_next_entries_in_an_orderable_asc_collection()
{
Expand Down
Loading