Commit 115bea5e authored by dww's avatar dww Committed by bojanz

Issue #2787255 by dww, bojanz: Provide a views filter for administrative areas

parent 0db5d0de
......@@ -43,6 +43,8 @@ function address_field_views_data(FieldStorageConfigInterface $field) {
}
// Add the custom country_code filter.
$data[$table_name][$field_name . '_country_code']['filter']['id'] = 'country_code';
// Add the custom administrative_area filter.
$data[$table_name][$field_name . '_administrative_area']['filter']['id'] = 'administrative_area';
}
}
elseif ($field_type == 'address_country') {
......
......@@ -150,3 +150,29 @@ field.widget.settings.address_zone_default:
views.filter.country_code:
type: views.filter.in_operator
label: 'Country'
views.filter.administrative_area:
type: views.filter.in_operator
label: 'Administrative area'
mapping:
country:
type: mapping
mapping:
country_source:
type: string
label: 'Country source'
country_argument_id:
type: string
label: 'Country contextual filter ID'
country_filter_id:
type: string
label: 'Exposed country filter ID'
country_static_code:
type: string
label: 'Predefined country for administrative areas'
expose:
type: mapping
mapping:
label_type:
type: string
label: 'Label type'
This diff is collapsed.
<?php
namespace Drupal\address\Plugin\views\filter;
use CommerceGuys\Addressing\Country\CountryRepositoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\views\Plugin\views\filter\InOperator;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Abstract base class for country-aware InOperator views filters.
*/
abstract class CountryAwareInOperatorBase extends InOperator {
/**
* The country repository.
*
* @var \CommerceGuys\Addressing\Country\CountryRepositoryInterface
*/
protected $countryRepository;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* Constructs a new CountryAwareInOperatorBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \CommerceGuys\Addressing\Country\CountryRepositoryInterface $country_repository
* The country repository.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, CountryRepositoryInterface $country_repository, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->countryRepository = $country_repository;
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('address.country_repository'),
$container->get('entity_type.manager'),
$container->get('entity_field.manager')
);
}
/**
* Gets the name of the entity field on which this filter operates.
*
* @return string
* The field name.
*/
protected function getFieldName() {
if (isset($this->configuration['field_name'])) {
// Configurable field.
$field_name = $this->configuration['field_name'];
}
else {
// Base field.
$field_name = $this->configuration['entity field'];
}
return $field_name;
}
/**
* Gets the list of available countries for the current entity field.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type, defaults to the current type for this filter.
* @param string $field_name
* The field name, defaults to the current field name for this filter.
*
* @return array
* An array of available country codes, including the full list when unrestricted.
*/
protected function getAvailableCountries(EntityTypeInterface $entity_type = NULL, $field_name = NULL) {
if (!isset($entity_type)) {
$entity_type_id = $this->getEntityType();
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
}
if (!isset($field_name)) {
$field_name = $this->getFieldName();
}
$bundles = $this->getBundles($entity_type, $field_name);
$storage = $this->entityTypeManager->getStorage($entity_type->id());
$countries_by_bundle = [];
foreach ($bundles as $bundle) {
$values = [];
if ($bundle_key = $entity_type->getKey('bundle')) {
$values[$bundle_key] = $bundle;
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $storage->create($values);
if ($entity->hasField($field_name)) {
$countries_by_bundle[$bundle] = $entity->get($field_name)->appendItem()->getAvailableCountries();
}
}
// Create the unified list, valid across bundles.
// Start by filtering out lists that are empty cause no restrictions apply.
$countries = [];
$countries_by_bundle = array_filter($countries_by_bundle);
if (count($countries_by_bundle) === 1) {
$countries = reset($countries_by_bundle);
}
elseif (count($countries_by_bundle) > 1) {
// Leave only the country codes that are common to all lists.
$countries = array_pop($countries_by_bundle);
foreach ($countries_by_bundle as $list) {
$countries = array_intersect_key($countries, $list);
}
}
$available_countries = $this->countryRepository->getList();
if (!empty($countries)) {
$available_countries = array_intersect_key($available_countries, $countries);
}
return $available_countries;
}
/**
* Gets the bundles for the current entity field.
*
* If the view has a non-exposed bundle filter, the bundles are taken from
* there. Otherwise, the field's bundles are used.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The current entity type.
* @param string $field_name
* The current field name.
*
* @return string[]
* The bundles.
*/
protected function getBundles(EntityTypeInterface $entity_type, $field_name) {
$bundles = [];
$bundle_key = $entity_type->getKey('bundle');
if ($bundle_key && isset($this->view->filter[$bundle_key])) {
$filter = $this->view->filter[$bundle_key];
if (!$filter->isExposed() && !empty($filter->value)) {
// 'all' is added by Views and isn't a bundle.
$bundles = array_diff($filter->value, ['all']);
}
}
// Fallback to the list of bundles the field is attached to.
if (empty($bundles)) {
$map = $this->entityFieldManager->getFieldMap();
$bundles = $map[$entity_type->id()][$field_name]['bundles'];
}
return $bundles;
}
}
......@@ -2,13 +2,6 @@
namespace Drupal\address\Plugin\views\filter;
use CommerceGuys\Addressing\Country\CountryRepositoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\views\Plugin\views\filter\InOperator;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Filter by country.
*
......@@ -16,181 +9,17 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* @ViewsFilter("country_code")
*/
class CountryCode extends InOperator {
/**
* The country repository.
*
* @var \CommerceGuys\Addressing\Country\CountryRepositoryInterface
*/
protected $countryRepository;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* Constructs a new CountryCode object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \CommerceGuys\Addressing\Country\CountryRepositoryInterface $country_repository
* The country repository.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, CountryRepositoryInterface $country_repository, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->countryRepository = $country_repository;
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('address.country_repository'),
$container->get('entity_type.manager'),
$container->get('entity_field.manager')
);
}
class CountryCode extends CountryAwareInOperatorBase {
/**
* {@inheritdoc}
*/
public function getValueOptions() {
if (!isset($this->valueOptions)) {
$entity_type_id = $this->getEntityType();
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
$field_name = $this->getFieldName();
$available_countries = $this->getAvailableCountries($entity_type, $field_name);
$countries = $this->countryRepository->getList();
if (!empty($available_countries)) {
$countries = array_intersect_key($countries, $available_countries);
}
$this->valueOptions = $countries;
$this->valueOptions = $this->getAvailableCountries();
}
return $this->valueOptions;
}
/**
* Gets the name of the entity field on which this filter operates.
*
* @return string
* The field name.
*/
protected function getFieldName() {
if (isset($this->configuration['field_name'])) {
// Configurable field.
$field_name = $this->configuration['field_name'];
}
else {
// Base field.
$field_name = $this->configuration['entity field'];
}
return $field_name;
}
/**
* Gets the list of available countries for the current entity field.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The current entity type.
* @param string $field_name
* The current field name.
*
* @return array
* An array of country codes. Empty if the country list isn't restricted.
*/
protected function getAvailableCountries(EntityTypeInterface $entity_type, $field_name) {
$bundles = $this->getBundles($entity_type, $field_name);
$storage = $this->entityTypeManager->getStorage($entity_type->id());
$countries_by_bundle = [];
foreach ($bundles as $bundle) {
$values = [];
if ($bundle_key = $entity_type->getKey('bundle')) {
$values[$bundle_key] = $bundle;
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $storage->create($values);
if ($entity->hasField($field_name)) {
$countries_by_bundle[$bundle] = $entity->get($field_name)->appendItem()->getAvailableCountries();
}
}
// Create the unified list, valid across bundles.
// Start by filtering out lists that are empty cause no restrictions apply.
$countries = [];
$countries_by_bundle = array_filter($countries_by_bundle);
if (count($countries_by_bundle) === 1) {
$countries = reset($countries_by_bundle);
}
elseif (count($countries_by_bundle) > 1) {
// Leave only the country codes that are common to all lists.
$countries = array_pop($countries_by_bundle);
foreach ($countries_by_bundle as $list) {
$countries = array_intersect_key($countries, $list);
}
}
return $countries;
}
/**
* Gets the bundles for the current entity field.
*
* If the view has a non-exposed bundle filter, the bundles are taken from
* there. Otherwise, the field's bundles are used.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The current entity type.
* @param string $field_name
* The current field name.
*
* @return string[]
* The bundles.
*/
protected function getBundles(EntityTypeInterface $entity_type, $field_name) {
$bundles = [];
$bundle_key = $entity_type->getKey('bundle');
if ($bundle_key && isset($this->view->filter[$bundle_key])) {
$filter = $this->view->filter[$bundle_key];
if (!$filter->isExposed() && !empty($filter->value)) {
// 'all' is added by Views and isn't a bundle.
$bundles = array_diff($filter->value, ['all']);
}
}
// Fallback to the list of bundles the field is attached to.
if (empty($bundles)) {
$map = $this->entityFieldManager->getFieldMap();
$bundles = $map[$entity_type->id()][$field_name]['bundles'];
}
return $bundles;
}
}
name: Address test
type: module
description: 'Provides functionality for testing the events in address module.'
description: 'Provides functionality for testing the address module.'
package: Testing
core: 8.x
dependencies:
- address
- views
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_address_test
- node.type.address_test
module:
- address
id: node.address_test.field_address_test
field_name: field_address_test
entity_type: node
bundle: address_test
label: Address
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
available_countries: { }
fields:
administrativeArea: administrativeArea
locality: locality
dependentLocality: dependentLocality
postalCode: postalCode
sortingCode: sortingCode
addressLine1: addressLine1
addressLine2: addressLine2
organization: organization
givenName: givenName
additionalName: additionalName
familyName: familyName
langcode_override: ''
field_type: address
langcode: en
status: true
dependencies:
module:
- address
- node
id: node.field_address_test
field_name: field_address_test
entity_type: node
type: address
settings: { }
module: address
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
langcode: en
status: true
name: 'Address Test'
type: address_test
description: ''
help: ''
new_revision: false
preview_mode: 1
display_submitted: true
langcode: en
status: true
dependencies:
module:
- address
- node
- user
id: address_test_filter_administrative_area
label: 'address test filter administrative area'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: none
options:
offset: 0
style:
type: default
row:
type: fields
options:
default_field_elements: true
inline: { }
separator: ''
hide_empty: false
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
entity_field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
filters:
status:
value: '1'
table: node_field_data
field: status
plugin_id: boolean
entity_type: node
entity_field: status
id: status
expose:
operator: ''
group: 1
field_address_test_country_code:
id: field_address_test_country_code
table: node__field_address_test
field: field_address_test_country_code
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: field_address_test_country_code_op
label: Country
description: ''
use_operator: false
operator: field_address_test_country_code_op
identifier: field_address_test_country_code
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: country_code
field_address_test_administrative_area:
id: field_address_test_administrative_area
table: node__field_address_test
field: field_address_test_administrative_area
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: field_address_test_administrative_area_op
label: 'Administrative area (static label)'
description: ''
use_operator: false
operator: field_address_test_administrative_area_op
identifier: field_address_test_administrative_area
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
label_type: static
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
country:
country_source: static
country_argument_id: ''
country_filter_id: field_address_test_country_code
country_static_code: AR
plugin_id: administrative_area