diff --git a/src/Entries/EntryQueryBuilder.php b/src/Entries/EntryQueryBuilder.php index bbabf306..54d78a49 100644 --- a/src/Entries/EntryQueryBuilder.php +++ b/src/Entries/EntryQueryBuilder.php @@ -5,6 +5,7 @@ use Illuminate\Support\Str; use Statamic\Contracts\Entries\QueryBuilder; use Statamic\Entries\EntryCollection; +use Statamic\Facades\Collection; use Statamic\Facades\Entry; use Statamic\Query\EloquentQueryBuilder; use Statamic\Stache\Query\QueriesTaxonomizedEntries; @@ -18,6 +19,63 @@ class EntryQueryBuilder extends EloquentQueryBuilder implements QueryBuilder 'date', 'collection', 'created_at', 'updated_at', 'order', 'blueprint', ]; + public function orderBy($column, $direction = 'asc') + { + $actualColumn = $this->column($column); + + if (Str::contains($actualColumn, 'data->')) { + $wheres = collect($this->builder->getQuery()->wheres); + + if ($wheres->where('column', 'collection')->count() == 1) { + if ($collection = Collection::find($wheres->firstWhere('column', 'collection')['value'])) { + $blueprintField = $collection->entryBlueprints() + ->flatMap(function ($blueprint) { + return $blueprint->fields() + ->all() + ->filter(function ($field) { + return in_array($field->type(), ['float', 'integer', 'date']); + }); + }) + ->filter() + ->get($column); + + if ($blueprintField) { + $castType = ''; + $fieldType = $blueprintField->type(); + + $grammar = $this->builder->getConnection()->getQueryGrammar(); + $actualColumn = $grammar->wrap($actualColumn); + + if (in_array($fieldType, ['float'])) { + $castType = 'float'; + } elseif (in_array($fieldType, ['integer'])) { + $castType = 'float'; // bit sneaky but mysql doesnt support casting as integer, it wants unsigned + } elseif (in_array($fieldType, ['date'])) { + $castType = 'date'; + + // sqlite casts dates to year, which is pretty unhelpful + if (str_contains(get_class($grammar), 'SQLiteGrammar')) { + $this->builder->orderByRaw("datetime({$actualColumn}) {$direction}"); + + return $this; + } + } + + if ($castType) { + $this->builder->orderByRaw("cast({$actualColumn} as {$castType}) {$direction}"); + + return $this; + } + } + } + } + } + + parent::orderBy($column, $direction); + + return $this; + } + protected function transform($items, $columns = []) { $items = EntryCollection::make($items)->map(function ($model) use ($columns) { diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index c8ab112a..02b0c518 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -726,4 +726,55 @@ public function entries_can_be_retrieved_on_join_table_conditions() // successfully retrieved 2 results $this->assertCount(2, $entries); } + + /** @test */ + public function entries_can_be_ordered_by_an_integer_json_field() + { + $blueprint = Blueprint::makeFromFields(['integer' => ['type' => 'integer']]); + Blueprint::shouldReceive('in')->with('collections/posts')->andReturn(collect(['posts' => $blueprint])); + + Collection::make('posts')->save(); + EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'integer' => 3])->create(); + EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'integer' => 5])->create(); + EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'integer' => 1])->create(); + + $entries = Entry::query()->where('collection', 'posts')->orderBy('integer', 'asc')->get(); + + $this->assertCount(3, $entries); + $this->assertEquals(['Post 3', 'Post 1', 'Post 2'], $entries->map->title->all()); + } + + /** @test */ + public function entries_can_be_ordered_by_an_float_json_field() + { + $blueprint = Blueprint::makeFromFields(['float' => ['type' => 'float']]); + Blueprint::shouldReceive('in')->with('collections/posts')->andReturn(collect(['posts' => $blueprint])); + + Collection::make('posts')->save(); + EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'float' => 3.3])->create(); + EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'float' => 5.5])->create(); + EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'float' => 1.1])->create(); + + $entries = Entry::query()->where('collection', 'posts')->orderBy('float', 'asc')->get(); + + $this->assertCount(3, $entries); + $this->assertEquals(['Post 3', 'Post 1', 'Post 2'], $entries->map->title->all()); + } + + /** @test */ + public function entries_can_be_ordered_by_an_date_json_field() + { + $blueprint = Blueprint::makeFromFields(['date_field' => ['type' => 'date', 'time_enabled' => true]]); + Blueprint::shouldReceive('in')->with('collections/posts')->andReturn(collect(['posts' => $blueprint])); + + Collection::make('posts')->save(); + EntryFactory::id('1')->slug('post-1')->collection('posts')->data(['title' => 'Post 1', 'date_field' => '2021-06-15 20:31:04'])->create(); + EntryFactory::id('2')->slug('post-2')->collection('posts')->data(['title' => 'Post 2', 'date_field' => '2021-01-13 20:31:04'])->create(); + EntryFactory::id('3')->slug('post-3')->collection('posts')->data(['title' => 'Post 3', 'date_field' => '2021-11-17 20:31:04'])->create(); + + $entries = Entry::query()->where('collection', 'posts')->orderBy('date_field', 'asc')->get(); + + $this->assertCount(3, $entries); + $this->assertEquals(['Post 2', 'Post 1', 'Post 3'], $entries->map->title->all()); + } }