diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5458c37c283b365bcfa483ff651c5295047f7c72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.idea +.gitlab-ci-local diff --git a/css/views_filters_summary.css b/css/views_filters_summary.css index 45162b2e65ae1baccfac2e967fd384e0d6c3aa63..cff4dc7722c7e8ff702cacad94f064f4e7b64f95 100644 --- a/css/views_filters_summary.css +++ b/css/views_filters_summary.css @@ -1,24 +1,24 @@ .views-filters-summary .value { - display: inline-block; - font-weight: 500; - border-radius: 3px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - margin-right: 0.5em; - margin-bottom: 6px; - padding: 0 1px 0 6px; - border: 1px solid #8C1515; + display: inline-block; + margin-right: 0.5em; + margin-bottom: 6px; + padding: 0 1px 0 6px; + border: 1px solid #8c1515; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + font-weight: 500; } .views-filters-summary a.remove-filter { - border: none; + margin-left: 4px; + padding: 1px 8px 0 8px; + vertical-align: middle; + color: #000; + border: none; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; background-color: #ededed; font-size: 18px; - padding: 1px 8px 0 8px; - margin-left: 4px; - color: #000; - vertical-align: middle; - border-radius: 3px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; } diff --git a/js/views-filters-summary.js b/js/views-filters-summary.js index 00d50812e12198e93dba55cc03b1f0b154a677df..5fd637b8e17576da1487d44bb12523bd9eee59ec 100644 --- a/js/views-filters-summary.js +++ b/js/views-filters-summary.js @@ -1,36 +1,42 @@ -(function ($, Drupal, window) { - 'use strict'; +(($, Drupal, window) => { Drupal.behaviors.viewsFiltersSummary = { - attach: function (context) { - + attach(context) { /** * Reset form input to an empty value/default state. + * @param {string} selector + * The selector of the filters forms to reset. */ function reset(selector) { - $(selector).find(':input').each(function () { - switch (this.type) { - case 'password': - case 'select-multiple': - case 'select-one': - case 'text': - case 'textarea': - $(this).val(''); - break; - case 'checkbox': - case 'radio': - this.checked = false; - break; - } - }); + $(selector) + .find(':input') + .each(function processInput() { + switch (this.type) { + case 'password': + case 'select-multiple': + case 'select-one': + case 'text': + case 'textarea': + this.value = ''; + break; + case 'checkbox': + case 'radio': + this.checked = false; + break; + default: + break; + } + }); } /** * Check if the views exposed form uses AJAX. - * @returns {boolean|jQuery|*} + * @return {boolean|jQuery|*} + * true if Ajax is used, false otherwise. */ function usesAjax() { - return $('.views-filters-summary', context) - .hasClass('views-filters-summary--use-ajax'); + return $('.views-filters-summary', context).hasClass( + 'views-filters-summary--use-ajax', + ); } /** @@ -38,51 +44,60 @@ * filters existed in the URL. */ function clickHandler() { - let uri = window.location.toString(); + const uri = window.location.toString(); if (!usesAjax()) { - let base_url = uri.substring(0, uri.indexOf("?")); - window.history.replaceState({}, document.title, base_url); + const baseUrl = uri.substring(0, uri.indexOf('?')); + window.history.replaceState({}, document.title, baseUrl); window.location.reload(); } else { - $('.views-exposed-form input[type="submit"]:nth-child(1)').trigger('click'); + $('.views-exposed-form input[type="submit"]:nth-child(1)').trigger( + 'click', + ); } } /** * Remove a specific filter from the views exposed filters. */ - $('.views-filters-summary a.remove-filter', context).one('click', function (event) { - event.preventDefault(); - let [selector, value] = $(this).data('removeSelector').split(':'); - let $input = $(`[name^="${selector}"]`); + $('.views-filters-summary a.remove-filter', context).one( + 'click', + function click(event) { + event.preventDefault(); + const [selector, value] = $(this).data('removeSelector').split(':'); + const input = document.querySelector(`[name^="${selector}"]`); - if ($input !== undefined) { - if ($input.is('input')) { - switch ($input.attr('type')) { - case 'radio': - case 'checkbox': - $input.filter(`[value="${value}"]`).prop('checked', false); - break; - default: - $input.val(''); - break; + if (input !== null) { + const $input = $(input); + if (input.matches('input')) { + switch ($input.attr('type')) { + case 'radio': + case 'checkbox': + $input.filter(`[value="${value}"]`).prop('checked', false); + break; + default: + input.value = ''; + break; + } + } else { + $input.children(`[value="${value}"]`).prop('selected', false); } - } else { - $input.children(`[value="${value}"]`).prop('selected', false); + clickHandler(); } - clickHandler() - } - }); + }, + ); /** * Reset all views exposed form filters. */ - $('.views-filters-summary a.reset', context).one('click', function (event) { - event.preventDefault(); - reset('form[class^="views-exposed-form"]'); - clickHandler() - }); + $('.views-filters-summary a.reset', context).one( + 'click', + function click(event) { + event.preventDefault(); + reset('form[class^="views-exposed-form"]'); + clickHandler(); + }, + ); }, }; })(jQuery, Drupal, window); diff --git a/src/Plugin/views/area/ViewsFiltersSummary.php b/src/Plugin/views/area/ViewsFiltersSummary.php index f1689f4f6e0a391f3349d42ca008d9236cad4eaa..868c04db445b5ffe70d561faf1ff2d3767a1b33e 100644 --- a/src/Plugin/views/area/ViewsFiltersSummary.php +++ b/src/Plugin/views/area/ViewsFiltersSummary.php @@ -1,18 +1,19 @@ <?php -declare(strict_types=1); - namespace Drupal\views_filters_summary\Plugin\views\area; -use Drupal\Core\Form\FormStateInterface; -use Drupal\views\Plugin\views\area\Result; use Drupal\Component\Render\MarkupInterface; -use Drupal\views\Plugin\views\style\DefaultSummary; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\views\Plugin\views\filter\FilterPluginBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\Url; +use Drupal\Core\Utility\Error; +use Drupal\user\Entity\User; +use Drupal\views\Plugin\views\area\Result; +use Drupal\views\Plugin\views\filter\FilterPluginBase; +use Drupal\views\Plugin\views\style\DefaultSummary; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -45,6 +46,13 @@ class ViewsFiltersSummary extends Result { */ protected $entityTypeBundleInfo; + /** + * The logger factory. + * + * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected $loggerFactory; + /** * Constructs a ViewsFiltersSummary object. * @@ -60,6 +68,8 @@ class ViewsFiltersSummary extends Result { * The entity type manager. * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info * The bundle info service. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger factory. */ public function __construct( array $configuration, @@ -67,12 +77,14 @@ class ViewsFiltersSummary extends Result { $plugin_definition, TranslationInterface $translation_manager, EntityTypeManagerInterface $entity_type_manager, - EntityTypeBundleInfoInterface $entity_type_bundle_info + EntityTypeBundleInfoInterface $entity_type_bundle_info, + LoggerChannelFactoryInterface $logger_factory, ) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->translationManager = $translation_manager; $this->entityTypeManager = $entity_type_manager; $this->entityTypeBundleInfo = $entity_type_bundle_info; + $this->loggerFactory = $logger_factory; } /** @@ -82,7 +94,7 @@ class ViewsFiltersSummary extends Result { ContainerInterface $container, array $configuration, $plugin_id, - $plugin_definition + $plugin_definition, ) { return new static( $configuration, @@ -90,7 +102,8 @@ class ViewsFiltersSummary extends Result { $plugin_definition, $container->get('string_translation'), $container->get('entity_type.manager'), - $container->get('entity_type.bundle.info') + $container->get('entity_type.bundle.info'), + $container->get('logger.factory') ); } @@ -111,8 +124,8 @@ class ViewsFiltersSummary extends Result { $options['filters_result_label'] = [ 'default' => [ 'plural' => 'results', - 'singular' => 'result' - ] + 'singular' => 'result', + ], ]; $options['content'] = [ 'default' => $this->t('Displaying @total @result_label @exposed_filter_summary'), @@ -126,7 +139,7 @@ class ViewsFiltersSummary extends Result { */ public function buildOptionsForm( &$form, - FormStateInterface $form_state + FormStateInterface $form_state, ): void { parent::buildOptionsForm($form, $form_state); @@ -176,7 +189,7 @@ class ViewsFiltersSummary extends Result { $form['show_reset_link'] = [ '#type' => 'checkbox', '#title' => $this->t('Show reset filter link'), - '#description' => $this->t('If checked, a rest filter link will be shown.'), + '#description' => $this->t('If checked, a reset filter link will be shown.'), '#default_value' => $this->options['show_reset_link'], ]; $form['filters_reset_link_title'] = [ @@ -187,9 +200,9 @@ class ViewsFiltersSummary extends Result { '#required' => TRUE, '#states' => [ 'visible' => [ - ':input[name="options[show_reset_link]"]' => ['checked' => TRUE] - ] - ] + ':input[name="options[show_reset_link]"]' => ['checked' => TRUE], + ], + ], ]; $form['filters_summary_separator'] = [ '#title' => $this->t('Exposed Filter Summary Separator'), @@ -259,8 +272,10 @@ class ViewsFiltersSummary extends Result { ), ]; } - } catch (\Exception $exception) { - watchdog_exception('views_filter_summary', $exception); + } + catch (\Exception $exception) { + $logger = $this->loggerFactory->get('views_filters_summary'); + Error::logException($logger, $exception); } return []; @@ -299,20 +314,22 @@ class ViewsFiltersSummary extends Result { $summary[] = $this->buildFilterSummaryGroupedItem( $id, $label, $value ); - } else { + } + else { foreach ($value as $info) { if (!isset($info['id'], $info['value'])) { - continue; + continue; } $summary[] = $this->buildFilterSummaryItem( $id, $label, $info['value'], $info['raw'] ); } } - } else { + } + else { $summary[] = $this->buildFilterSummaryItem( $id, $label, $value - ); + ); } } @@ -338,7 +355,7 @@ class ViewsFiltersSummary extends Result { string $id, string $label, string $value, - ?string $value_raw = NULL + ?string $value_raw = NULL, ): array { $input = $value_raw ?? $value; return [ @@ -352,8 +369,8 @@ class ViewsFiltersSummary extends Result { '#attributes' => [ 'class' => ['remove-filter'], 'data-remove-selector' => "{$id}:{$input}", - 'aria-label' => "clear {$value}" - ] + 'aria-label' => "clear {$value}", + ], ], ]; } @@ -374,7 +391,7 @@ class ViewsFiltersSummary extends Result { protected function buildFilterSummaryGroupedItem( string $id, string $label, - array $values + array $values, ): array { $item = [ 'id' => $id, @@ -457,16 +474,16 @@ class ViewsFiltersSummary extends Result { 'show_reset_link' => $this->options['show_reset_link'], 'has_group_values' => $this->options['group_values'], 'reset_link' => [ - 'title' => $this->options['filters_reset_link_title'] + 'title' => $this->options['filters_reset_link_title'], ], 'filters_summary' => [ 'prefix' => $this->options['filters_summary_prefix'], - 'separator' => $this->options['filters_summary_separator'] + 'separator' => $this->options['filters_summary_separator'], ], ], '#attached' => [ - 'library' => ['views_filters_summary/views_filters_summary'] - ] + 'library' => ['views_filters_summary/views_filters_summary'], + ], ]; return $this->getRenderer()->render( $element @@ -504,21 +521,41 @@ class ViewsFiltersSummary extends Result { * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ - protected function getFilterDefinitions(): array { + protected function getFilterDefinitions(): array { $definitions = []; foreach ($this->view->filter as $filter) { - if (empty($filter->value) || !$filter->isExposed()) { + if ( + !$filter->isExposed() + || !$this->isSelectedFilter($filter) + || !$this->hasValidFilterValue($filter) + || !$this->hasValidExposedInput($filter) + ) { continue; } - $definitions[] = $this->buildFilterDefinition( - $filter - ); + $definition = $this->buildFilterDefinition($filter); + if (!$this->hasValidDefinitionValue($definition)) { + continue; + } + $definitions[] = $definition; } return $definitions; } + /** + * Check if value is not empty or equal to zero. + * + * @param mixed $value + * The value to check. + * + * @return bool + * True if value is not empty or equal to zero. + */ + protected function isValidValue(mixed $value): bool { + return !empty($value) || is_numeric($value); + } + /** * Build the filter definition. * @@ -535,7 +572,7 @@ class ViewsFiltersSummary extends Result { $info = [ 'id' => $filter->exposedInfo()['value'] ?? NULL, 'label' => $this->getFilterLabel($filter), - 'value' => $filter->value + 'value' => $filter->value['value'] ?? $filter->value, ]; if (is_array($info['value'])) { @@ -556,6 +593,7 @@ class ViewsFiltersSummary extends Result { } $info['value'] = $values; break; + case 'bundle': if ($entity_type = $filter->getEntityType()) { $values = []; @@ -573,17 +611,91 @@ class ViewsFiltersSummary extends Result { $info['value'] = $values; } break; - case 'numeric': - $filter_options = $filter->options['group_info']['group_items']; - $filter_id = reset($filter->value); - foreach ($filter_options as $value) { - if ($filter_id == $value['value']['value']) { - $info['value'] = $value['title']; + + case 'user_name': + $values = []; + foreach ($info['value'] as $index => $value) { + if (!$this->isValidArrayValue($filter, $index, $value)) { + continue; + } + $user = User::load($value); + if ($user) { + $values[] = [ + 'id' => $index, + 'raw' => (string) $value, + 'value' => (string) $user->getDisplayName(), + ]; } } + $info['value'] = $values; + break; + + default: + $values = []; + if ($filter->options['is_grouped']) { + $filter_group_items = $filter->options['group_info']['group_items']; + foreach ($info['value'] as $index => $value) { + if (!$this->isValidArrayValue($filter, $index, $value)) { + continue; + } + foreach ($filter_group_items as $group_item) { + if ($value == $group_item['value']) { + $values[] = [ + 'id' => $index, + 'raw' => $group_item['value'], + 'value' => $group_item['title'], + ]; + break; + } + } + } + } + else { + foreach ($info['value'] as $index => $value) { + if (!$this->isValidArrayValue($filter, $index, $value)) { + continue; + } + $values[] = [ + 'id' => $index, + 'raw' => (string) $value, + 'value' => (string) $value, + ]; + } + } + $info['value'] = $values; break; } } + else { + switch ($filter->getPluginId()) { + case 'todo': + default: + if ($filter->options['is_grouped']) { + $filter_group_items = $filter->options['group_info']['group_items']; + foreach ($filter_group_items as $group_item) { + if ($info['value'] == $group_item['value']) { + $values[] = [ + 'id' => 0, + 'raw' => $group_item['value'], + 'value' => $group_item['title'], + ]; + $info['value'] = $values; + break; + } + } + } + else { + if ($this->isValidValue($info['value'])) { + $values[] = [ + 'id' => 0, + 'raw' => (string) $info['value'], + 'value' => (string) $info['value'], + ]; + $info['value'] = $values; + } + } + } + } // Invoke hook_views_filters_summary_info_alter(). $this->moduleHandler->alter('views_filters_summary_info', $info, $filter); @@ -601,8 +713,13 @@ class ViewsFiltersSummary extends Result { * The views filter label. */ protected function getFilterLabel( - FilterPluginBase $filter + FilterPluginBase $filter, ): ?string { + if ($filter->options['is_grouped']) { + if (!empty($filter->options['group_info']['label'])) { + return $filter->options['group_info']['label']; + } + } return $filter->options['expose']['label'] ?? $filter->definition['title short'] ?? $filter->definition['title'] @@ -616,7 +733,7 @@ class ViewsFiltersSummary extends Result { * An array of the filter options. */ protected function getFilterOptions(): array { - $options = array(); + $options = []; $this->view->initHandlers(); foreach ($this->view->filter as $id => $handler) { @@ -628,4 +745,113 @@ class ViewsFiltersSummary extends Result { return $options; } + /** + * Check that the filter value is equal to zero or not empty. + * + * @param \Drupal\views\Plugin\views\filter\FilterPluginBase $filter + * The filter to validate. + * + * @return bool + * True if the filter has a valid value. + */ + protected function hasValidFilterValue(FilterPluginBase $filter) { + // We want to keep the numerical filter values equal to zero. + return $this->isValidValue($filter->value); + } + + /** + * Check the filter definition value. + * + * @param array $definition + * The filter definition to check value validity from. + * + * @return bool + * True if definition value is valid. + */ + protected function hasValidDefinitionValue(array $definition): bool { + return $this->isValidValue($definition['value']); + } + + /** + * Check that a filter has a valid corresponding exposed input. + * + * @param \Drupal\views\Plugin\views\filter\FilterPluginBase $filter + * The filter to validate. + * + * @return bool + * True if a corresponding valid exposed input has been found. + */ + protected function hasValidExposedInput(FilterPluginBase $filter): bool { + $inputs = $filter->view->getExposedInput(); + if ($filter->options['is_grouped']) { + $identifier = $filter->options['group_info']['identifier']; + $default = $filter->options['group_info']['default_group']; + } + else { + $identifier = $filter->options['expose']['identifier']; + $default = 'All'; + } + return isset($inputs[$identifier]) && $inputs[$identifier] != $default; + } + + /** + * Check that the filter is selected in the configuration. + * + * @param \Drupal\views\Plugin\views\filter\FilterPluginBase $filter + * The filter to check for selection. + * + * @return bool + * True if the filter is present in the configuration selection. + */ + protected function isSelectedFilter(FilterPluginBase $filter): bool { + $filters = $this->options['filters']; + return empty($filters) || in_array($filter->options['id'], $filters); + } + + /** + * Check if value index is valid. + * + * We want to exclude 'type' for some filters like date for example. + * + * @param \Drupal\views\Plugin\views\filter\FilterPluginBase $filter + * The array value filter. + * @param int|string $index + * The index in the array value. + * + * @return bool + * True if the array value index is valid, false otherwise. + */ + protected function isValidIndex(FilterPluginBase $filter, int|string $index): bool { + switch ($filter->pluginId) { + case 'language': + // Language filter array are similar to: ['fr' => 'fr', 'en' => 'en']. + return TRUE; + + default: + // By default, we want all index to be numeric values. + return is_numeric($index); + } + } + + /** + * Check if a filter array value is valid. + * + * @param \Drupal\views\Plugin\views\filter\FilterPluginBase $filter + * The filter. + * @param int|string $index + * The value index in array. + * @param mixed $value + * The array value. + * + * @return bool + * True if valid array value, false otherwise. + */ + protected function isValidArrayValue( + FilterPluginBase $filter, + int|string $index, + mixed $value, + ): bool { + return $this->isValidIndex($filter, $index) && $this->isValidValue($value); + } + } diff --git a/templates/views-filters-summary.html.twig b/templates/views-filters-summary.html.twig index 2646e885ae75c5df24074253b1b352ed0447a436..d13fac9502448ff6205d24363199cc505d9ba74a 100644 --- a/templates/views-filters-summary.html.twig +++ b/templates/views-filters-summary.html.twig @@ -5,13 +5,13 @@ * Default theme implementation to display views exposed filters summary. * * Available variables: - * - show_label: Show the fitler label. + * - show_label: Show the filter label. * - show_remove_link: Show a remove link. * - show_reset_link: Show a reset link. * - reset_link: * - title: The reset link title. * - filters_summary: - * - prefix: The exposed filter summmary prefix. + * - prefix: The exposed filter summary prefix. * - separator: The exposed filter summary separator. * - summary: An array of selected filters. Available keys: * - label: The label of the filter. diff --git a/views_filters_summary.module b/views_filters_summary.module index c2063da3e29eadbfaab587c081b6820e22c4a131..9e65ebbc067b5031dd35562d5296879bdae2295b 100644 --- a/views_filters_summary.module +++ b/views_filters_summary.module @@ -1,9 +1,9 @@ <?php -declare(strict_types=1); - /** * @file + * Hook implementations for views_filters_summary module. + * * Provides the ability to show which filters are used in Views Result. */