diff --git a/src/Dictionaries/BasicDictionary.php b/src/Dictionaries/BasicDictionary.php index 4b17dd19d0..52e2f7363c 100644 --- a/src/Dictionaries/BasicDictionary.php +++ b/src/Dictionaries/BasicDictionary.php @@ -16,11 +16,17 @@ public function get(string $key): ?Item } public function options(?string $search = null): array + { + return collect($this->optionItems($search)) + ->mapWithKeys(fn (Item $item) => [$item->value() => $item->label()]) + ->all(); + } + + public function optionItems(?string $search = null): array { return $this ->getFilteredItems() ->when($search, fn ($collection) => $collection->filter(fn ($item) => $this->matchesSearchQuery($search, $item))) - ->mapWithKeys(fn (Item $item) => [$item->value() => $item->label()]) ->all(); } diff --git a/src/Dictionaries/Dictionary.php b/src/Dictionaries/Dictionary.php index c94771a254..dd704bc380 100644 --- a/src/Dictionaries/Dictionary.php +++ b/src/Dictionaries/Dictionary.php @@ -68,4 +68,11 @@ private function getInferredGqlType($value) return GraphQL::string(); } + + public function optionItems(?string $search = null): array + { + return collect($this->options($search)) + ->map(fn ($label, $value) => new Item($value, $label, $this->get($value)->extra())) + ->all(); + } } diff --git a/src/Providers/ExtensionServiceProvider.php b/src/Providers/ExtensionServiceProvider.php index e499359dc5..d70ffa149f 100644 --- a/src/Providers/ExtensionServiceProvider.php +++ b/src/Providers/ExtensionServiceProvider.php @@ -166,6 +166,7 @@ class ExtensionServiceProvider extends ServiceProvider Tags\Collection\Collection::class, Tags\Cookie::class, Tags\Dd::class, + Tags\Dictionary\Dictionary::class, Tags\Dump::class, Tags\GetContent::class, Tags\GetError::class, diff --git a/src/Tags/Dictionary/Dictionary.php b/src/Tags/Dictionary/Dictionary.php new file mode 100644 index 0000000000..8482e0193c --- /dev/null +++ b/src/Tags/Dictionary/Dictionary.php @@ -0,0 +1,62 @@ +loop($tag); + } + + /** + * {{ dictionary handle="" }} ... {{ /dictionary }}. + */ + public function index() + { + return $this->loop($this->params->pull('handle')); + } + + private function loop($handle) + { + if (! $handle) { + return []; + } + + $search = $this->params->pull('search'); + + if (! $dictionary = Dictionaries::find($handle, $this->params->all())) { + throw new DictionaryNotFoundException($handle); + } + + $options = (new DataCollection($dictionary->optionItems($search))) + ->map(fn ($item) => new DictionaryItem($item->toArray())) + ->values(); + + $query = (new ItemQueryBuilder)->withItems($options); + + $this->queryConditions($query); + $this->queryOrderBys($query); + $this->queryScopes($query); + + return $this->output($this->results($query)); + } +} diff --git a/src/Tags/Dictionary/DictionaryItem.php b/src/Tags/Dictionary/DictionaryItem.php new file mode 100644 index 0000000000..f155578e23 --- /dev/null +++ b/src/Tags/Dictionary/DictionaryItem.php @@ -0,0 +1,26 @@ +toArray(), $key, $default); + } + + public function toArray() + { + return array_merge($this->data, $this->supplements ?? []); + } +} diff --git a/tests/Tags/DictionaryTagTest.php b/tests/Tags/DictionaryTagTest.php new file mode 100644 index 0000000000..f717da2792 --- /dev/null +++ b/tests/Tags/DictionaryTagTest.php @@ -0,0 +1,97 @@ +assertEquals('AFG', $this->tag($template)); + } + + #[Test] + public function it_gets_dictionary_by_handle() + { + $template = '{{ dictionary handle="countries" limit="1" }}{{ value }}{{ /dictionary }}'; + + $this->assertEquals('AFG', $this->tag($template)); + } + + #[Test] + public function it_gets_timezones() + { + $template = '{{ dictionary:timezones limit="1" }}{{ value }}{{ /dictionary:timezones }}'; + + $this->assertEquals('Africa/Abidjan', $this->tag($template)); + } + + #[Test] + public function it_can_search() + { + $template = '{{ dictionary:countries search="Alg" }}{{ value }}{{ /dictionary:countries }}'; + + $this->assertEquals('DZA', $this->tag($template)); + } + + #[Test] + public function it_pulls_extra_data_data() + { + $template = '{{ dictionary:countries search="Alg" }}{{ region }} - {{ iso2 }}{{ /dictionary:countries }}'; + + $this->assertEquals('Africa - DZ', $this->tag($template)); + } + + #[Test] + public function it_can_paginate() + { + $template = '{{ dictionary:countries paginate="4" }}{{ options | count }}{{ /dictionary:countries }}'; + + $this->assertEquals('4', $this->tag($template)); + + $template = '{{ dictionary:countries paginate="4" as="countries" }}{{ countries | count }}{{ /dictionary:countries }}'; + + $this->assertEquals('4', $this->tag($template)); + } + + #[Test] + public function it_can_be_filtered_using_conditions() + { + $template = '{{ dictionary:countries iso3:is="AUS" }}{{ name }}{{ /dictionary:countries }}'; + + $this->assertEquals('Australia', $this->tag($template)); + } + + #[Test] + public function it_can_be_filtered_using_a_query_scope() + { + app('statamic.scopes')[TestScope::handle()] = TestScope::class; + + $template = '{{ dictionary:countries query_scope="test_scope" }}{{ name }}{{ /dictionary:countries }}'; + + $this->assertEquals('Australia', $this->tag($template)); + } + + private function tag($tag, $data = []) + { + return (string) Parse::template($tag, $data); + } +} + +class TestScope extends Scope +{ + public function apply($query, $params) + { + $query->where('iso3', 'AUS'); + } +}