summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordww2017-05-18 12:07:22 (GMT)
committerBojan Zivanovic2017-05-18 12:08:26 (GMT)
commit115bea5eaff536c4f28a83c0142282801feb4ee6 (patch)
tree3743c22a7f23536d5915e0f9f014c2e4eed35b15
parent0db5d0de6f9e2335288b4864ef347a48d8a56fd6 (diff)
Issue #2787255 by dww, bojanz: Provide a views filter for administrative areas8.x-1.0
-rw-r--r--address.views.inc2
-rw-r--r--config/schema/address.schema.yml26
-rw-r--r--src/Plugin/views/filter/AdministrativeArea.php635
-rw-r--r--src/Plugin/views/filter/CountryAwareInOperatorBase.php185
-rw-r--r--src/Plugin/views/filter/CountryCode.php175
-rw-r--r--tests/modules/address_test/address_test.info.yml5
-rw-r--r--tests/modules/address_test/config/install/field.field.node.address_test.field_address_test.yml34
-rw-r--r--tests/modules/address_test/config/install/field.storage.node.field_address_test.yml18
-rw-r--r--tests/modules/address_test/config/install/node.type.address_test.yml9
-rw-r--r--tests/modules/address_test/config/optional/views.view.address_test_filter_administrative_area.yml281
-rw-r--r--tests/src/Functional/Views/AdministrativeAreaFilterTest.php212
11 files changed, 1408 insertions, 174 deletions
diff --git a/address.views.inc b/address.views.inc
index 1fcafae..25c83fb 100644
--- a/address.views.inc
+++ b/address.views.inc
@@ -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') {
diff --git a/config/schema/address.schema.yml b/config/schema/address.schema.yml
index fe93398..7f8cf16 100644
--- a/config/schema/address.schema.yml
+++ b/config/schema/address.schema.yml
@@ -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'
diff --git a/src/Plugin/views/filter/AdministrativeArea.php b/src/Plugin/views/filter/AdministrativeArea.php
new file mode 100644
index 0000000..2670db6
--- /dev/null
+++ b/src/Plugin/views/filter/AdministrativeArea.php
@@ -0,0 +1,635 @@
+<?php
+
+namespace Drupal\address\Plugin\views\filter;
+
+use CommerceGuys\Addressing\AddressFormat\AddressFormatRepositoryInterface;
+use CommerceGuys\Addressing\Country\CountryRepositoryInterface;
+use CommerceGuys\Addressing\Subdivision\SubdivisionRepositoryInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\address\LabelHelper;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Filter by administrative area.
+ *
+ * @todo: Rebuild the exposed filter element via AJAX when the country changes.
+ * @see https://www.drupal.org/node/2840717
+ *
+ * @ingroup views_filter_handlers
+ *
+ * @ViewsFilter("administrative_area")
+ */
+class AdministrativeArea extends CountryAwareInOperatorBase {
+
+ /**
+ * The address format repository.
+ *
+ * @var \CommerceGuys\Addressing\AddressFormat\AddressFormatRepositoryInterface
+ */
+ protected $addressFormatRepository;
+
+ /**
+ * The subdivision repository.
+ *
+ * @var \CommerceGuys\Addressing\Subdivision\SubdivisionRepositoryInterface
+ */
+ protected $subdivisionRepository;
+
+ /**
+ * If we're in the middle of building a form, its current state.
+ *
+ * @var \Drupal\Core\Form\FormStateInterface
+ */
+ protected $formState;
+
+ /**
+ * The currently selected country (if any).
+ *
+ * @var string
+ */
+ protected $currentCountryCode;
+
+ /**
+ * Constructs a new AdministrativeArea 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.
+ * @param \CommerceGuys\Addressing\AddressFormat\AddressFormatRepositoryInterface $address_format_repository
+ * The address format repository.
+ * @param \CommerceGuys\Addressing\Subdivision\SubdivisionRepositoryInterface $subdivision_repository
+ * The subdivision repository.
+ */
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, CountryRepositoryInterface $country_repository, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, AddressFormatRepositoryInterface $address_format_repository, SubdivisionRepositoryInterface $subdivision_repository) {
+ parent::__construct($configuration, $plugin_id, $plugin_definition, $country_repository, $entity_type_manager, $entity_field_manager);
+
+ $this->addressFormatRepository = $address_format_repository;
+ $this->subdivisionRepository = $subdivision_repository;
+ $this->formState = NULL;
+ $this->currentCountryCode = '';
+ }
+
+ /**
+ * {@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'),
+ $container->get('address.address_format_repository'),
+ $container->get('address.subdivision_repository')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function defineOptions() {
+ $options = parent::defineOptions();
+
+ $options['country'] = [
+ 'contains' => [
+ 'country_source' => ['default' => ''],
+ 'country_argument_id' => ['default' => ''],
+ 'country_filter_id' => ['default' => ''],
+ 'country_static_code' => ['default' => ''],
+ ],
+ ];
+ $options['expose']['contains']['label_type']['default'] = 'static';
+
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function canBuildGroup() {
+ // To be able to define a group, you have to be able to select values
+ // while configuring the filter. But this filter doesn't let you select
+ // values until a country is selected, so the group filter functionality
+ // is impossible.
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+ $this->formState = $form_state;
+
+ $form['country'] = [
+ '#type' => 'container',
+ '#weight' => -300,
+ ];
+ $form['country']['country_source'] = [
+ '#type' => 'radios',
+ '#title' => $this->t('Country source'),
+ '#options' => [
+ 'static' => $this->t('A predefined country code'),
+ 'argument' => $this->t('The value of a contextual filter'),
+ 'filter' => $this->t('The value of an exposed filter'),
+ ],
+ '#default_value' => $this->options['country']['country_source'],
+ '#ajax' => [
+ 'callback' => [get_class($this), 'ajaxRefreshCountry'],
+ 'wrapper' => 'admin-area-value-options-ajax-wrapper',
+ ],
+ ];
+
+ $argument_options = [];
+ // Find all the contextual filters on the display to use as options.
+ foreach ($this->view->display_handler->getHandlers('argument') as $name => $argument) {
+ // @todo Limit this to arguments pointing to a country code field.
+ $argument_options[$name] = $argument->adminLabel();
+ }
+ if (!empty($argument_options)) {
+ $form['country']['country_argument_id'] = [
+ '#type' => 'select',
+ '#title' => t('Country contextual filter'),
+ '#options' => $argument_options,
+ '#default_value' => $this->options['country']['country_argument_id'],
+ ];
+ }
+ else {
+ // #states doesn't work on markup elements, so use a container.
+ $form['country']['country_argument_id'] = [
+ '#type' => 'container',
+ ];
+ $form['country']['country_argument_id']['error'] = [
+ '#type' => 'markup',
+ '#markup' => t('You must add a contextual filter for the country code to use this filter for administrative areas.'),
+ ];
+ }
+ $form['country']['country_argument_id']['#states'] = [
+ 'visible' => [
+ ':input[name="options[country][country_source]"]' => ['value' => 'argument'],
+ ],
+ ];
+
+ $filter_options = [];
+ // Find all country_code filters from address.module for the valid choices.
+ foreach ($this->view->display_handler->getHandlers('filter') as $name => $filter) {
+ $definition = $filter->pluginDefinition;
+ if ($definition['id'] == 'country_code' && $definition['provider'] == 'address') {
+ $filter_options[$name] = $filter->adminLabel();
+ }
+ }
+ if (!empty($filter_options)) {
+ $form['country']['country_filter_id'] = [
+ '#type' => 'select',
+ '#title' => t('Exposed country filter to determine values'),
+ '#options' => $filter_options,
+ '#default_value' => $this->options['country']['country_filter_id'],
+ ];
+ }
+ else {
+ // #states doesn't work on markup elements, so we to use a container.
+ $form['country']['country_filter_id'] = [
+ '#type' => 'container',
+ ];
+ $form['country']['country_filter_id']['error'] = [
+ '#type' => 'markup',
+ '#markup' => t('You must add a filter for the country code to use this filter for administrative areas.'),
+ ];
+ }
+ $form['country']['country_filter_id']['#states'] = [
+ 'visible' => [
+ ':input[name="options[country][country_source]"]' => ['value' => 'filter'],
+ ],
+ ];
+
+ $countries = $this->getAdministrativeAreaCountries();
+
+ $form['country']['country_static_code'] = [
+ '#type' => 'select',
+ '#title' => t('Predefined country for administrative areas'),
+ '#options' => $countries,
+ '#empty_value' => '',
+ '#default_value' => $this->options['country']['country_static_code'],
+ '#ajax' => [
+ 'callback' => [get_class($this), 'ajaxRefreshCountry'],
+ 'wrapper' => 'admin-area-value-options-ajax-wrapper',
+ ],
+ '#states' => [
+ 'visible' => [
+ ':input[name="options[country][country_source]"]' => ['value' => 'static'],
+ ],
+ ],
+ ];
+
+ // @todo This should appear directly above $form['expose']['label'].
+ $form['expose']['label_type'] = [
+ '#type' => 'radios',
+ '#title' => $this->t('Label type'),
+ '#options' => [
+ 'static' => $this->t('Static'),
+ 'dynamic' => $this->t('Dynamic (an appropriate label will be set based on the active country)'),
+ ],
+ '#default_value' => $this->options['expose']['label_type'],
+ '#states' => [
+ 'visible' => [
+ ':input[name="options[expose_button][checkbox][checkbox]"]' => ['checked' => TRUE],
+ ],
+ ],
+ ];
+
+ parent::buildOptionsForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateOptionsForm(&$form, FormStateInterface $form_state) {
+ if (empty($form_state)) {
+ return;
+ }
+ $is_exposed = !empty($this->options['exposed']);
+
+ $country_source = $form_state->getValue(['options', 'country', 'country_source']);
+ switch ($country_source) {
+ case 'argument':
+ $country_argument = $form_state->getValue(['options', 'country', 'country_argument_id']);
+ if (empty($country_argument)) {
+ $error = $this->t("The country contextual filter must be defined for this filter to work using 'contextual filter' for the 'Country source'.");
+ $form_state->setError($form['country']['country_source'], $error);
+ }
+ if (empty($is_exposed)) {
+ $error = $this->t('This filter must be exposed to use a contextual filter to specify the country.');
+ $form_state->setError($form['country']['country_source'], $error);
+ }
+ break;
+
+ case 'filter':
+ $country_filter = $form_state->getValue(['options', 'country', 'country_filter_id']);
+ if (empty($country_filter)) {
+ $error = $this->t("The country filter must be defined for this filter to work using 'exposed filter' for the 'Country source'.");
+ $form_state->setError($form['country']['country_source'], $error);
+ }
+ if (empty($is_exposed)) {
+ $error = $this->t('This filter must be exposed to use a filter to specify the country.');
+ $form_state->setError($form['country']['country_source'], $error);
+ }
+ break;
+
+ case 'static':
+ $country_code = $form_state->getValue(['options', 'country', 'country_static_code']);
+ if (empty($country_code)) {
+ $error = $this->t('The predefined country must be set for this filter to work.');
+ $form_state->setError($form['country']['country_static_code'], $error);
+ }
+ break;
+
+ default:
+ $error = $this->t('The source for the country must be defined for this filter to work.');
+ $form_state->setError($form['country']['country_source'], $error);
+
+ break;
+ }
+
+ parent::validateOptionsForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildExposeForm(&$form, FormStateInterface $form_state) {
+ parent::buildExposeForm($form, $form_state);
+ // Only show the label element if we're configured for a static label.
+ $form['expose']['label']['#states'] = [
+ 'visible' => [
+ ':input[name="options[expose][label_type]"]' => ['value' => 'static'],
+ ],
+ ];
+ // Only show the reduce option if we have a static country. If we're
+ // getting values from a filter or argument, there are no fixed values to
+ // reduce to.
+ $form['expose']['reduce']['#states'] = [
+ 'visible' => [
+ ':input[name="options[country][country_source]"]' => ['value' => 'static'],
+ ],
+ ];
+ // Repair the wrapper container on $form['value'] clobbered by
+ // FilterPluginBase::buildExposeForm().
+ $form['value']['#prefix'] = '<div id="admin-area-value-options-ajax-wrapper" class="views-group-box views-right-60">';
+ $form['value']['#suffix'] = '</div>';
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitExposeForm($form, FormStateInterface $form_state) {
+ // If the country source is anything other than static, we have to
+ // ignore/disable the "reduce" option since it doesn't make any sense and
+ // will cause problems if the stale configuration is saved.
+ // Similarly, we clear out any selections for specific administrative areas.
+ $country_source = $form_state->getValue(['options', 'country', 'country_source']);
+ if ($country_source != 'static') {
+ $form_state->setValue(['options', 'expose', 'reduce'], FALSE);
+ $form_state->setValue(['options', 'value'], []);
+ }
+ parent::submitExposeForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function showValueForm(&$form, FormStateInterface $form_state) {
+ $this->valueForm($form, $form_state);
+ $form['value']['#prefix'] = '<div id="admin-area-value-options-ajax-wrapper">';
+ $form['value']['#suffix'] = '</div>';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function valueForm(&$form, FormStateInterface $form_state) {
+ $this->valueOptions = [];
+ $this->formState = $form_state;
+
+ $country_source = $this->getCountrySource();
+
+ if ($country_source == 'static' || $form_state->get('exposed')) {
+ $this->getCurrentCountry();
+ parent::valueForm($form, $form_state);
+ $form['value']['#after_build'][] = [get_class($this), 'clearValues'];
+ }
+ else {
+ $form['value'] = [
+ '#type' => 'container',
+ '#attributes' => ['id' => 'admin-area-value-options-ajax-wrapper'],
+ ];
+ $form['value']['message'] = [
+ '#type' => 'markup',
+ '#markup' => t("You can only select options here if you use a predefined country for the 'Country source'."),
+ ];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function valueSubmit($form, FormStateInterface $form_state) {
+ $this->formState = $form_state;
+ $country_source = $this->getCountrySource();
+ if ($country_source == 'static') {
+ // Only save the values if we've got a static country code.
+ parent::valueSubmit($form, $form_state);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exposedInfo() {
+ $info = parent::exposedInfo();
+ if ($this->options['expose']['label_type'] == 'dynamic') {
+ $current_country = $this->getCurrentCountry();
+ if (!empty($current_country)) {
+ $address_format = $this->addressFormatRepository->get($current_country);
+ $labels = LabelHelper::getFieldLabels($address_format);
+ if (!empty($labels['administrativeArea'])) {
+ $info['label'] = $labels['administrativeArea'];
+ }
+ }
+ }
+ return $info;
+ }
+
+ /**
+ * Gets the current source for the country code.
+ *
+ * If defined in the current values of the configuration form, use
+ * that. Otherwise, fall back to the filter configuration.
+ *
+ * @return string
+ * The country source.
+ */
+ protected function getCountrySource() {
+ // If we're rebuilding via AJAX, we want the country source from the form
+ // state, not the configuration.
+ $country_source = '';
+ if (!empty($this->formState)) {
+ // First, see if there's a legitimate value in the form state.
+ $form_value_country_source = $this->formState->getValue(['options', 'country', 'country_source']);
+ if (!empty($form_value_country_source)) {
+ $country_source = $form_value_country_source;
+ }
+ else {
+ // At various stages of building/validating the form, we might have
+ // user input but not yet have the value saved into the form
+ // state. So, if we have a form state but still don't have a value,
+ // see if it is defined in the user input.
+ $input = $this->formState->getUserInput();
+ if (!empty($input['options']['country']['country_source'])) {
+ $country_source = $input['options']['country']['country_source'];
+ }
+ }
+ }
+ // If we don't have a source via the form state, use our configuration.
+ if (empty($country_source)) {
+ $country_source = $this->options['country']['country_source'];
+ }
+ return $country_source;
+ }
+
+ /**
+ * Gets the currently active country code.
+ *
+ * The country source determines where to look for the country code. It can
+ * either be predefined, in which case we simply return the current value of
+ * the static country code (via form values or configuration). We can look
+ * for the country via a Views argument, in which case we determine the
+ * current value of the argument. Or we can get the country from another
+ * exposed filter, in which case we look in the form values to find the
+ * current country code from the other filter.
+ *
+ * @return string
+ * The 2-letter country code.
+ */
+ protected function getCurrentCountry() {
+ $this->currentCountryCode = '';
+ switch ($this->getCountrySource()) {
+ case 'argument':
+ $country_argument = $this->view->display_handler->getHandler('argument', $this->options['country']['country_argument_id']);
+ if (!empty($country_argument)) {
+ $this->currentCountryCode = $country_argument->getValue();
+ }
+ break;
+
+ case 'filter':
+ $country_filter = $this->view->display_handler->getHandler('filter', $this->options['country']['country_filter_id']);
+ if (!empty($country_filter) && !empty($this->formState)) {
+ $input = $this->formState->getUserInput();
+ $country_filter_identifier = $country_filter->options['expose']['identifier'];
+ if (!empty($input[$country_filter_identifier])) {
+ if (is_array($input[$country_filter_identifier])) {
+ // @todo Maybe the config validation should prevent multi-valued
+ // country filters. For now, we only provide administrative area
+ // options if a single country is selected.
+ if (count($input[$country_filter_identifier]) == 1) {
+ $this->currentCountryCode = array_shift($input[$country_filter_identifier]);
+ }
+ }
+ else {
+ $this->currentCountryCode = $input[$country_filter_identifier];
+ }
+ }
+ }
+ break;
+
+ case 'static':
+ if (!empty($this->formState)) {
+ // During filter configuration validation, we still need to know the
+ // current country code, but the values won't yet be saved into the
+ // ones accessible via FormStateInterface::getValue(). So, directly
+ // inspect the user input instead of the official form values.
+ $input = $this->formState->getUserInput();
+ if (!empty($input['options']['country']['country_static_code'])) {
+ $form_input_country_code = $input['options']['country']['country_static_code'];
+ }
+ }
+ $this->currentCountryCode = !empty($form_input_country_code) ? $form_input_country_code : $this->options['country']['country_static_code'];
+ break;
+
+ }
+
+ // Since the country code can come from all sorts of non-validated user
+ // input (e.g. GET parameters) and since it might be 'All', ensure we've
+ // got a valid country code before we proceed. Other code in this
+ // filter (and especially upstream in the AddressFormatRepository and
+ // others) will explode if passed an invalid country code.
+ if (!empty($this->currentCountryCode)) {
+ $all_countries = $this->countryRepository->getList();
+ if (empty($all_countries[$this->currentCountryCode])) {
+ $this->currentCountryCode = '';
+ }
+ }
+ return $this->currentCountryCode;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValueOptions() {
+ $this->valueOptions = [];
+ if (($country_code = $this->getCurrentCountry())) {
+ $parents[] = $country_code;
+ $locale = \Drupal::languageManager()->getConfigOverrideLanguage()->getId();
+ $subdivisions = $this->subdivisionRepository->getList($parents, $locale);
+ $this->valueOptions = $subdivisions;
+ }
+ return $this->valueOptions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildExposedForm(&$form, FormStateInterface $form_state) {
+ parent::buildExposedForm($form, $form_state);
+ // Hide the form element if we have no options to select.
+ // (e.g. the country isn't set or it doesn't use administrative areas).
+ if (empty($this->valueOptions)) {
+ $identifier = $this->options['expose']['identifier'];
+ $form[$identifier]['#access'] = FALSE;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function adminSummary() {
+ switch ($this->options['country']['country_source']) {
+ case 'argument':
+ return $this->t('exposed: country set via contextual filter');
+
+ case 'filter':
+ return $this->t('exposed: country set via exposed filter');
+
+ case 'static':
+ if (!empty($this->options['exposed'])) {
+ return $this->t('exposed: fixed country: @country', ['@country' => $this->options['country']['country_static_code']]);
+ }
+ return $this->t('fixed country: @country', ['@country' => $this->options['country']['country_static_code']]);
+ }
+ return $this->t('broken configuration');
+ }
+
+ /**
+ * Gets a list of countries that have administrative areas.
+ *
+ * @param array $available_countries
+ * The available countries to filter by.
+ * Defaults to the available countries for this filter.
+ *
+ * @return array
+ * An array of country names, keyed by country code.
+ */
+ public function getAdministrativeAreaCountries(array $available_countries = NULL) {
+ if (!isset($available_countries)) {
+ $available_countries = $this->getAvailableCountries();
+ }
+
+ $countries = [];
+ foreach ($available_countries as $country_code => $country_name) {
+ $address_format = $this->addressFormatRepository->get($country_code);
+ $subdivision_depth = $address_format->getSubdivisionDepth();
+ if ($subdivision_depth > 0) {
+ $countries[$country_code] = $country_name;
+ }
+ }
+
+ return $countries;
+ }
+
+ /**
+ * Ajax callback.
+ */
+ public static function ajaxRefreshCountry(array $form, FormStateInterface $form_state) {
+ return $form['options']['value'];
+ }
+
+ /**
+ * Clears the administrative area form values when the country changes.
+ *
+ * Implemented as an #after_build callback because #after_build runs before
+ * validation, allowing the values to be cleared early enough to prevent the
+ * "Illegal choice" error.
+ */
+ public static function clearValues(array $element, FormStateInterface $form_state) {
+ $triggering_element = $form_state->getTriggeringElement();
+ if (!$triggering_element) {
+ return $element;
+ }
+
+ $triggering_element_name = end($triggering_element['#parents']);
+ if ($triggering_element_name == 'country_static_code' || $triggering_element_name == 'country_source') {
+ foreach ($element['#options'] as $key => $option) {
+ $element[$key]['#value'] = 0;
+ }
+ $element['#value'] = [];
+
+ $input = &$form_state->getUserInput();
+ $input['options']['value'] = [];
+ }
+
+ return $element;
+ }
+
+}
diff --git a/src/Plugin/views/filter/CountryAwareInOperatorBase.php b/src/Plugin/views/filter/CountryAwareInOperatorBase.php
new file mode 100644
index 0000000..2f16da8
--- /dev/null
+++ b/src/Plugin/views/filter/CountryAwareInOperatorBase.php
@@ -0,0 +1,185 @@
+<?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;
+ }
+
+}
diff --git a/src/Plugin/views/filter/CountryCode.php b/src/Plugin/views/filter/CountryCode.php
index 374b619..aa2da72 100644
--- a/src/Plugin/views/filter/CountryCode.php
+++ b/src/Plugin/views/filter/CountryCode.php
@@ -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;
- }
-
}
diff --git a/tests/modules/address_test/address_test.info.yml b/tests/modules/address_test/address_test.info.yml
index 283730a..60fed48 100644
--- a/tests/modules/address_test/address_test.info.yml
+++ b/tests/modules/address_test/address_test.info.yml
@@ -1,5 +1,8 @@
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
diff --git a/tests/modules/address_test/config/install/field.field.node.address_test.field_address_test.yml b/tests/modules/address_test/config/install/field.field.node.address_test.field_address_test.yml
new file mode 100644
index 0000000..bf09342
--- /dev/null
+++ b/tests/modules/address_test/config/install/field.field.node.address_test.field_address_test.yml
@@ -0,0 +1,34 @@
+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
diff --git a/tests/modules/address_test/config/install/field.storage.node.field_address_test.yml b/tests/modules/address_test/config/install/field.storage.node.field_address_test.yml
new file mode 100644
index 0000000..5c6699e
--- /dev/null
+++ b/tests/modules/address_test/config/install/field.storage.node.field_address_test.yml
@@ -0,0 +1,18 @@
+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
diff --git a/tests/modules/address_test/config/install/node.type.address_test.yml b/tests/modules/address_test/config/install/node.type.address_test.yml
new file mode 100644
index 0000000..395d35d
--- /dev/null
+++ b/tests/modules/address_test/config/install/node.type.address_test.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+name: 'Address Test'
+type: address_test
+description: ''
+help: ''
+new_revision: false
+preview_mode: 1
+display_submitted: true
diff --git a/tests/modules/address_test/config/optional/views.view.address_test_filter_administrative_area.yml b/tests/modules/address_test/config/optional/views.view.address_test_filter_administrative_area.yml
new file mode 100644
index 0000000..dc145d0
--- /dev/null
+++ b/tests/modules/address_test/config/optional/views.view.address_test_filter_administrative_area.yml
@@ -0,0 +1,281 @@
+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
+ sorts: { }
+ title: 'address views administrative area test'
+ header: { }
+ footer: { }
+ empty: { }
+ relationships: { }
+ arguments:
+ 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: ''
+ default_action: ignore
+ exception:
+ value: all
+ title_enable: false
+ title: All
+ title_enable: false
+ title: ''
+ default_argument_type: fixed
+ default_argument_options:
+ argument: ''
+ default_argument_skip_url: false
+ summary_options:
+ base_path: ''
+ count: true
+ items_per_page: 25
+ override: false
+ summary:
+ sort_order: asc
+ number_of_records: 0
+ format: default_summary
+ specify_validation: false
+ validate:
+ type: none
+ fail: 'not found'
+ validate_options: { }
+ glossary: false
+ limit: 0
+ case: none
+ path_case: none
+ transform_dash: false
+ break_phrase: false
+ plugin_id: string
+ display_extenders: { }
+ filter_groups:
+ operator: AND
+ groups:
+ 1: AND
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - url
+ - 'user.node_grants:view'
+ - user.permissions
+ tags: { }
+ page_1:
+ display_plugin: page
+ id: page_1
+ display_title: Page
+ position: 1
+ display_options:
+ display_extenders: { }
+ path: 'address-test/views/filter-administrative-area'
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - url
+ - 'user.node_grants:view'
+ - user.permissions
+ tags: { }
diff --git a/tests/src/Functional/Views/AdministrativeAreaFilterTest.php b/tests/src/Functional/Views/AdministrativeAreaFilterTest.php
new file mode 100644
index 0000000..3f77386
--- /dev/null
+++ b/tests/src/Functional/Views/AdministrativeAreaFilterTest.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace Drupal\Tests\address\Functional\Views;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\views\Views;
+
+/**
+ * Tests the administrative area Views filter for Address fields.
+ *
+ * @group address
+ */
+class AdministrativeAreaFilterTest extends BrowserTestBase {
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = [
+ 'node',
+ 'user',
+ 'views',
+ 'address',
+ 'address_test',
+ ];
+
+ /**
+ * A simple user with 'access content' permission.
+ *
+ * @var \Drupal\user\UserInterface
+ */
+ protected $user;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ $this->user = $this->drupalCreateUser(['access content']);
+ $this->drupalLogin($this->user);
+ }
+
+ /**
+ * Test options for administrative area using a static country code.
+ */
+ public function testStaticCountryAdministrativeAreaOptions() {
+ $view = Views::getView('address_test_filter_administrative_area');
+ $filters = $view->getDisplay()->getOption('filters');
+ $filters['field_address_test_administrative_area']['country']['country_source'] = 'static';
+ $filters['field_address_test_administrative_area']['country']['country_static_code'] = 'BR';
+ $view->getDisplay()->overrideOption('filters', $filters);
+ $view->save();
+
+ $this->drupalGet('address-test/views/filter-administrative-area');
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldExists('field_address_test_administrative_area');
+ $this->assertAdministrativeAreaOptions('BR');
+ }
+
+ /**
+ * Test options for administrative area using a contextual country filter.
+ */
+ public function testContextualCountryFilterAdministrativeAreaOptions() {
+ $view = Views::getView('address_test_filter_administrative_area');
+ $filters = $view->getDisplay()->getOption('filters');
+ $filters['field_address_test_administrative_area']['country']['country_source'] = 'argument';
+ $filters['field_address_test_administrative_area']['country']['country_argument_id'] = 'field_address_test_country_code';
+ $view->getDisplay()->overrideOption('filters', $filters);
+ $view->save();
+
+ // With no country selected, the administrative area shouldn't exist.
+ $this->drupalGet('address-test/views/filter-administrative-area');
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldNotExists('field_address_test_administrative_area');
+
+ // For a country without admin areas, the filter still shouldn't exist.
+ $this->drupalGet('address-test/views/filter-administrative-area/CR');
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldNotExists('field_address_test_administrative_area');
+
+ // For countries with administrative areas, validate the options.
+ foreach (['BR', 'EG', 'MX', 'US'] as $country) {
+ $this->drupalGet("address-test/views/filter-administrative-area/$country");
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldExists('field_address_test_administrative_area');
+ $this->assertAdministrativeAreaOptions($country);
+ }
+ }
+
+ /**
+ * Test options for administrative area using an exposed country filter.
+ */
+ public function testExposedCountryFilterAdministrativeAreaOptions() {
+ $view = Views::getView('address_test_filter_administrative_area');
+ $filters = $view->getDisplay()->getOption('filters');
+ $filters['field_address_test_administrative_area']['country']['country_source'] = 'filter';
+ $filters['field_address_test_administrative_area']['country']['country_filter_id'] = 'field_address_test_country_code';
+ $view->getDisplay()->overrideOption('filters', $filters);
+ $view->save();
+
+ // With no country selected, the administrative area shouldn't exist.
+ $this->drupalGet('address-test/views/filter-administrative-area');
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldNotExists('field_address_test_administrative_area');
+
+ // For a country without admin areas, the filter still shouldn't exist.
+ $options = ['query' => ['field_address_test_country_code' => 'CR']];
+ $this->drupalGet('address-test/views/filter-administrative-area', $options);
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldNotExists('field_address_test_administrative_area');
+
+ // For countries with admin areas, validate the options.
+ foreach (['BR', 'EG', 'MX', 'US'] as $country) {
+ $options = ['query' => ['field_address_test_country_code' => $country]];
+ $this->drupalGet('address-test/views/filter-administrative-area', $options);
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldExists('field_address_test_administrative_area');
+ $this->assertAdministrativeAreaOptions($country);
+ }
+ }
+
+ /**
+ * Test that the static vs. dynamic label feature works properly.
+ */
+ public function testAdministrativeAreaLabels() {
+ $static_label = 'Administrative area (static label)';
+ $dynamic_labels = [
+ 'AE' => 'Emirate',
+ 'BR' => 'State',
+ 'CA' => 'Province',
+ 'US' => 'State',
+ ];
+
+ // Force the view into our expected configuration. Use contextual filter
+ // to set the country, and start with static labels.
+ $view = Views::getView('address_test_filter_administrative_area');
+ $filters = $view->getDisplay()->getOption('filters');
+ $filters['field_address_test_administrative_area']['country']['country_source'] = 'argument';
+ $filters['field_address_test_administrative_area']['country']['country_argument_id'] = 'field_address_test_country_code';
+ $filters['field_address_test_administrative_area']['expose']['label_type'] = 'static';
+ $filters['field_address_test_administrative_area']['expose']['label'] = $static_label;
+ $view->getDisplay()->overrideOption('filters', $filters);
+ $view->save();
+
+ foreach ($dynamic_labels as $country => $dynamic_label) {
+ $this->drupalGet("address-test/views/filter-administrative-area/$country");
+ $this->assertSession()->pageTextContains($static_label);
+ $this->assertSession()->pageTextNotContains($dynamic_label);
+ }
+
+ // Configure for dynamic labels and test again.
+ $view = Views::getView('address_test_filter_administrative_area');
+ $filters['field_address_test_administrative_area']['expose']['label_type'] = 'dynamic';
+ $view->getDisplay()->overrideOption('filters', $filters);
+ $view->save();
+
+ foreach ($dynamic_labels as $country => $dynamic_label) {
+ $this->drupalGet("address-test/views/filter-administrative-area/$country");
+ $this->assertSession()->pageTextNotContains($static_label);
+ $this->assertSession()->pageTextContains($dynamic_label);
+ }
+ }
+
+ /**
+ * Assert the right administrative area options for a given country code.
+ *
+ * @param string $active_country
+ * The country code.
+ */
+ protected function assertAdministrativeAreaOptions($active_country) {
+ // These are not exhaustive lists, nor are the keys guaranteed to be unique.
+ $areas = [
+ 'BR' => [
+ 'AM' => 'Amazonas',
+ 'BA' => 'Bahia',
+ 'PE' => 'Pernambuco',
+ 'RJ' => 'Rio de Janeiro',
+ ],
+ 'EG' => [
+ 'Alexandria Governorate' => 'Alexandria Governorate',
+ 'Cairo Governorate' => 'Cairo Governorate',
+ ],
+ 'MX' => [
+ 'CHIS' => 'Chiapas',
+ 'JAL' => 'Jalisco',
+ 'OAX' => 'Oaxaca',
+ 'VER' => 'Veracruz',
+ ],
+ 'US' => [
+ 'LA' => 'Louisiana',
+ 'MA' => 'Massachusetts',
+ 'WI' => 'Wisconsin',
+ ],
+ ];
+ foreach ($areas as $country => $areas) {
+ foreach ($areas as $area_key => $area_value) {
+ // For the active country, ensure both the key and value match.
+ if ($country == $active_country) {
+ $this->assertSession()->optionExists('edit-field-address-test-administrative-area', $area_key);
+ $this->assertSession()->optionExists('edit-field-address-test-administrative-area', $area_value);
+ }
+ // Otherwise, we can't assume the keys are unique (e.g. 'MA' is a
+ // state code in many different countries), so all we can safely
+ // assume is that the state value strings aren't on the page.
+ else {
+ $this->assertSession()->pageTextNotContains($area_value);
+ }
+ }
+ }
+ }
+
+}