Skip to content

Commit

Permalink
[4.x] Fix entries on the same date being ignored by collection previo…
Browse files Browse the repository at this point in the history
…us/next tags (#8921)
  • Loading branch information
duncanmcclean authored Nov 3, 2023
1 parent 9a63a3b commit d125380
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 10 deletions.
56 changes: 46 additions & 10 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 @@ -66,18 +68,22 @@ public function next($currentEntry)
throw_if($this->params->has('offset'), new \Exception('collection:next is not compatible with [offset] parameter'));
throw_if($this->collections->count() > 1, new \Exception('collection:next is not compatible with multiple collections'));

if ($this->orderBys->count() === 1) {
$this->orderBys[] = new OrderBy('title', 'asc');
}

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

if ($primaryOrderBy->direction === 'desc') {
$operator = '<';
}
$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:next requires ordered or dated collection');
}
Expand All @@ -91,18 +97,22 @@ public function previous($currentEntry)
throw_if($this->params->has('offset'), new \Exception('collection:previous is not compatible with [offset] parameter'));
throw_if($this->collections->count() > 1, new \Exception('collection:previous is not compatible with multiple collections'));

if ($this->orderBys->count() === 1) {
$this->orderBys[] = new OrderBy('title', 'asc');
}

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

if ($primaryOrderBy->direction === 'desc') {
$operator = '>';
}
$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($secondaryOrderBy->sort, $secondaryOperator, $currentEntry->value($secondaryOrderBy->sort))
->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

0 comments on commit d125380

Please sign in to comment.