diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index faf1b6ed..f8d74e37 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -181,14 +181,22 @@ private function buildFilterTree($filterName) ->arrayNode('choices') ->prototype('array') ->beforeNormalization() - ->ifTrue( + ->always( function ($v) { - return empty($v['label']); - } - ) - ->then( - function ($v) { - $v['label'] = $v['field']; + if (empty($v['fields']) && !empty($v['field'])) { + $field = ['field' => $v['field']]; + if (array_key_exists('order', $v)) { + $field['order'] = $v['order']; + } + if (array_key_exists('mode', $v)) { + $field['mode'] = $v['mode']; + } + $v['fields'][] = $field; + } + + if (empty($v['label'])) { + $v['label'] = $v['fields'][0]['field']; + } return $v; } @@ -197,11 +205,21 @@ function ($v) { ->addDefaultsIfNotSet() ->children() ->scalarNode('label')->end() - ->scalarNode('field')->isRequired()->end() + ->scalarNode('field')->end() ->scalarNode('order')->defaultValue('asc')->end() ->scalarNode('mode')->defaultNull()->end() ->scalarNode('key')->info('Custom parameter value')->end() ->booleanNode('default')->defaultFalse()->end() + ->arrayNode('fields') + ->isRequired() + ->requiresAtLeastOneElement() + ->prototype('array') + ->children() + ->scalarNode('field')->isRequired()->end() + ->scalarNode('order')->defaultValue('asc')->end() + ->scalarNode('mode')->defaultNull()->end() + ->end() + ->end() ->end() ->end() ->end() diff --git a/Filters/Widget/Sort/Sort.php b/Filters/Widget/Sort/Sort.php index ee85a4af..bc304d3a 100644 --- a/Filters/Widget/Sort/Sort.php +++ b/Filters/Widget/Sort/Sort.php @@ -37,14 +37,16 @@ class Sort extends AbstractSingleRequestValueFilter implements ViewDataFactoryIn public function modifySearch(Search $search, FilterState $state = null, SearchRequest $request = null) { if ($state && $state->isActive()) { - $search->addSort( - new EsSort( - $this->choices[$state->getValue()]['field'], - $this->choices[$state->getValue()]['order'], - null, - $this->choices[$state->getValue()]['mode'] - ) - ); + $stateValue = $state->getValue(); + + if (!empty($this->choices[$stateValue]['fields'])) { + foreach ($this->choices[$stateValue]['fields'] as $sortField) { + $search->addSort(new EsSort($sortField['field'], $sortField['order'], null, $sortField['mode'])); + } + } else { + $sortField = $this->choices[$stateValue]; + $search->addSort(new EsSort($sortField['field'], $sortField['order'], null, $sortField['mode'])); + } } else { foreach ($this->choices as $choice) { if ($choice['default']) { diff --git a/Resources/doc/filter/sort.rst b/Resources/doc/filter/sort.rst index d3a73374..54042bb6 100644 --- a/Resources/doc/filter/sort.rst +++ b/Resources/doc/filter/sort.rst @@ -63,6 +63,22 @@ After which you can specify multiple sort options/choices: +------------------------+--------------------------------------------------------------------+ | `mode` | For any arrays: `min`, `max`, for numeric arrays `avg`, `sum`. | +------------------------+--------------------------------------------------------------------+ +| `fields` | Array of fields to sort on. For more information see table below. | ++------------------------+--------------------------------------------------------------------+ + +.. note:: `field`, `order`, and `mode` are ignored if at least one of fields is defined. + +Each object in `fields` array specifies sorting condition. Available parameters are defined below: + ++------------------------+--------------------------------------------------------------------+ +| Setting name | Meaning | ++========================+====================================================================+ +| `field` | Specifies the field in repository to sort on. (e.g. `item_color`) | ++------------------------+--------------------------------------------------------------------+ +| `order` | Order to sort by. Default `asc`. Valid values: `asc`, `desc`. | ++------------------------+--------------------------------------------------------------------+ +| `mode` | For any arrays: `min`, `max`, for numeric arrays `avg`, `sum`. | ++------------------------+--------------------------------------------------------------------+ Example: @@ -83,6 +99,7 @@ Example: choices: - { label: Color ascending, field: item_color, default: true } - { label: Color descending, field: item_color, order: desc } + - { label: 'In stock & cheap', fields: [{field: stock, order: desc}, {field: price}] } .. diff --git a/Tests/Unit/DependencyInjection/ConfigurationTest.php b/Tests/Unit/DependencyInjection/ConfigurationTest.php index a3024269..a0090ce6 100644 --- a/Tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/Tests/Unit/DependencyInjection/ConfigurationTest.php @@ -117,6 +117,34 @@ public function getTestConfigurationData() $expectedConfig['filters']['sort']['sorting']['choices'][0]['default'] = false; $expectedConfig['filters']['sort']['sorting']['choices'][0]['order'] = 'asc'; $expectedConfig['filters']['sort']['sorting']['choices'][0]['mode'] = null; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][0]['field'] = 'test'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][0]['order'] = 'asc'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][0]['mode'] = null; + unset($customConfig['filters']['document_field']); + unset($customConfig['filters']['multi_choice']); + unset($customConfig['filters']['fuzzy']); + $cases[] = [ + $customConfig, + $expectedConfig, + ]; + + // Case #3 sorting by multiple fields. + $customConfig = $expectedBaseConfig; + $customConfig['filters']['sort']['sorting']['choices'][0]['fields'][0]['field'] = 'price'; + $customConfig['filters']['sort']['sorting']['choices'][0]['fields'][1]['field'] = 'date'; + $customConfig['filters']['sort']['sorting']['choices'][0]['fields'][1]['order'] = 'desc'; + $expectedConfig = $customConfig; + $expectedConfig['filters']['choice'] = ['single_choice' => ['request_field' => 'choice', 'tags' => ['badged']]]; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['label'] = 'price'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['default'] = false; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['order'] = 'asc'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['mode'] = null; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][0]['field'] = 'price'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][0]['order'] = 'asc'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][0]['mode'] = null; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][1]['field'] = 'date'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][1]['order'] = 'desc'; + $expectedConfig['filters']['sort']['sorting']['choices'][0]['fields'][1]['mode'] = null; unset($customConfig['filters']['document_field']); unset($customConfig['filters']['multi_choice']); unset($customConfig['filters']['fuzzy']); @@ -219,6 +247,14 @@ public function getTestConfigurationExceptionData() '. Permissible values: "_term", "_count"', ]; + // Case #8 Sorting fields are not set. + $config = $this->getBaseConfiguration(); + $config['filters']['sort']['sorting']['choices'][0]['label'] = 'test'; + $cases[] = [ + $config, + 'The child node "fields" at path "ongr_filter_manager.filters.sort.sorting.choices.0" must be configured.', + ]; + return $cases; } diff --git a/Tests/app/config/config_test.yml b/Tests/app/config/config_test.yml index b3d330d9..91b5dc92 100644 --- a/Tests/app/config/config_test.yml +++ b/Tests/app/config/config_test.yml @@ -102,3 +102,4 @@ ongr_filter_manager: request_field: 'sort' choices: - { label: foo, field: price, default: true, order: asc } + - { label: bar, fields: [{field: price, order: asc}, {field: date, order: desc}]}