Commit f622064c authored by bojanz's avatar bojanz

Update the Subdivision code for addressing 1.x

parent c6abbb9a
......@@ -9,7 +9,7 @@ services:
address.subdivision_repository:
class: Drupal\address\Repository\SubdivisionRepository
arguments: ['@cache.data', '@language_manager']
arguments: ['@address.address_format_repository', '@cache.data']
address.zone_repository:
class: Drupal\address\Repository\ZoneRepository
......
......@@ -265,19 +265,25 @@ class AddressDefaultFormatter extends FormatterBase implements ContainerFactoryP
$values[$field] = $address->$getter();
}
// Replace the subdivision values with the names of any predefined ones.
foreach ($address_format->getUsedSubdivisionFields() as $field) {
$original_values = [];
$subdivision_fields = $address_format->getUsedSubdivisionFields();
$parents = [];
foreach ($subdivision_fields as $index => $field) {
if (empty($values[$field])) {
// This level is empty, so there can be no sublevels.
break;
}
$subdivision = $this->subdivisionRepository->get($values[$field], $address->getLocale());
$parents[] = $index ? $original_values[$subdivision_fields[$index - 1]] : $address->getCountryCode();
$subdivision = $this->subdivisionRepository->get($values[$field], $parents);
if (!$subdivision) {
// This level has no predefined subdivisions, stop.
break;
}
$values[$field] = $subdivision->getCode();
// Remember the original value so that it can be used for $parents.
$original_values[$field] = $values[$field];
// Replace the value with the expected code.
$use_local_name = LocaleHelper::match($address->getLocale(), $subdivision->getLocale());
$values[$field] = $use_local_name ? $subdivision->getLocalCode() : $subdivision->getCode();
if (!$subdivision->hasChildren()) {
// The current subdivision has no children, stop.
break;
......
......@@ -6,6 +6,7 @@ use CommerceGuys\Addressing\AddressFormat\AddressField;
use CommerceGuys\Addressing\AddressFormat\AddressFormat;
use CommerceGuys\Addressing\AddressFormat\AddressFormatRepositoryInterface;
use CommerceGuys\Addressing\Country\CountryRepositoryInterface;
use CommerceGuys\Addressing\LocaleHelper;
use CommerceGuys\Addressing\Subdivision\SubdivisionRepositoryInterface;
use Drupal\address\AddressInterface;
use Drupal\Core\Field\FormatterBase;
......@@ -175,29 +176,42 @@ class AddressPlainFormatter extends FormatterBase implements ContainerFactoryPlu
$values[$field] = $address->$getter();
}
foreach ($address_format->getUsedSubdivisionFields() as $field) {
$original_values = [];
$subdivision_fields = $address_format->getUsedSubdivisionFields();
$parents = [];
foreach ($subdivision_fields as $index => $field) {
$value = $values[$field];
// The template needs access to both the subdivision code and name.
$values[$field] = [
'code' => '',
'name' => $value,
'code' => $value,
'name' => '',
];
if (empty($value)) {
// This level is empty, so there can be no sublevels.
break;
}
$subdivision = $this->subdivisionRepository->get($value, $address->getLocale());
$parents[] = $index ? $original_values[$subdivision_fields[$index - 1]] : $address->getCountryCode();
$subdivision = $this->subdivisionRepository->get($value, $parents);
if (!$subdivision) {
// This level has no predefined subdivisions, stop.
break;
}
// Replace the subdivision values with the predefined ones.
$values[$field] = [
'code' => $subdivision->getCode(),
'name' => $subdivision->getName(),
];
// Remember the original value so that it can be used for $parents.
$original_values[$field] = $values[$field];
// Replace the value with the expected code.
if (LocaleHelper::match($address->getLocale(), $subdivision->getLocale())) {
$values[$field] = [
'code' => $subdivision->getLocalCode(),
'name' => $subdivision->getLocalName(),
];
}
else {
$values[$field] = [
'code' => $subdivision->getCode(),
'name' => $subdivision->getName(),
];
}
if (!$subdivision->hasChildren()) {
// The current subdivision has no children, stop.
......
......@@ -454,16 +454,17 @@ class AddressDefaultWidget extends WidgetBase implements ContainerFactoryPluginI
}
// Load and insert the subdivisions for each parent id.
$currentDepth = 1;
$parents = [];
foreach ($subdivision_properties as $index => $property) {
if (!isset($element[$property]) || !Element::isVisibleElement($element[$property])) {
break;
}
$parent_property = $index ? $subdivision_properties[$index - 1] : NULL;
$parent_property = $index ? $subdivision_properties[$index - 1] : 'country_code';
if ($parent_property && empty($values[$parent_property])) {
break;
}
$parent_id = $parent_property ? $values[$parent_property] : NULL;
$subdivisions = $this->subdivisionRepository->getList($values['country_code'], $parent_id);
$parents[] = $values[$parent_property];
$subdivisions = $this->subdivisionRepository->getList($parents);
if (empty($subdivisions)) {
break;
}
......
......@@ -167,15 +167,16 @@ class ZoneMemberCountry extends ZoneMemberBase implements ContainerFactoryPlugin
$labels = LabelHelper::getFieldLabels($address_format);
$subdivision_fields = $address_format->getUsedSubdivisionFields();
$current_depth = 1;
$parents = [];
foreach ($subdivision_fields as $index => $field) {
$property = FieldHelper::getPropertyName($field);
$parent_property = $index ? FieldHelper::getPropertyName($subdivision_fields[$index - 1]) : NULL;
$parent_property = $index ? FieldHelper::getPropertyName($subdivision_fields[$index - 1]) : 'country_code';
if ($parent_property && empty($values[$parent_property])) {
// No parent value selected.
break;
}
$parent_id = $parent_property ? $values[$parent_property] : NULL;
$subdivisions = $this->subdivisionRepository->getList($values['country_code'], $parent_id);
$parents[] = $values[$parent_property];
$subdivisions = $this->subdivisionRepository->getList($parents);
if (empty($subdivisions)) {
break;
}
......
......@@ -2,13 +2,14 @@
namespace Drupal\address\Plugin\views\field;
use CommerceGuys\Addressing\LocaleHelper;
use CommerceGuys\Addressing\Repository\SubdivisionRepositoryInterface;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\ResultRow;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Displays the subdivision name instead of the id.
* Displays the subdivision.
*
* @ingroup views_field_handlers
*
......@@ -63,27 +64,37 @@ class Subdivision extends FieldPluginBase {
}
$entity = $this->getEntity($values);
/** @var \Drupal\address\AddressInterface $address */
$address = $entity->{$this->definition['field_name']}->first();
switch ($this->definition['property']) {
case 'administrative_area':
$parent_id = NULL;
$needs_parent = FALSE;
$code = $address->getAdministrativeArea();
$parents = [
$address->getCountryCode(),
];
break;
case 'locality':
$parent_id = $address->administrative_area;
$needs_parent = TRUE;
$code = $address->getLocality();
$parents = [
$address->getCountryCode(),
$address->getAdministrativeArea(),
];
break;
case 'dependent_locality':
$parent_id = $address->locality;
$needs_parent = TRUE;
$code = $address->getDependentLocality();
$parents = [
$address->getCountryCode(),
$address->getAdministrativeArea(),
$address->getLocality(),
];
break;
}
if (!$needs_parent || !empty($parent_id)) {
$subdivisions = $this->subdivisionRepository->getList($address->country_code, $parent_id);
if (isset($subdivisions[$value])) {
$value = $subdivisions[$value];
}
/** @var \CommerceGuys\Addressing\Subdivision\Subdivision $subdivision */
$subdivision = $this->subdivisionRepository->get($code, $parents);
// @todo Allow a choice between subdivision code and name.
if ($subdivision) {
$use_local_name = LocaleHelper::match($address->getLocale(), $subdivision->getLocale());
$value = $use_local_name ? $subdivision->getLocalName() : $subdivision->getName();
}
return $this->sanitizeValue($value);
......
......@@ -2,12 +2,12 @@
namespace Drupal\address\Repository;
use Commerceguys\Addressing\AddressFormat\AddressFormatRepositoryInterface;
use CommerceGuys\Addressing\Subdivision\SubdivisionRepository as ExternalSubdivisionRepository;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Language\LanguageManagerInterface;
/**
* Defines the subdivision repository.
* Provides subdivisions.
*
* Subdivisions are stored on disk in JSON and cached inside Drupal.
*/
......@@ -20,80 +20,45 @@ class SubdivisionRepository extends ExternalSubdivisionRepository {
*/
protected $cache;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Creates a SubdivisionRepository instance.
*
* @param \CommerceGuys\Addressing\AddressFormat\AddressFormatRepositoryInterface $address_format_repository
* The address format repository.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache backend.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
$this->cache = $cache;
$this->languageManager = $language_manager;
public function __construct(AddressFormatRepositoryInterface $address_format_repository, CacheBackendInterface $cache) {
parent::__construct($address_format_repository);
parent::__construct();
}
/**
* {@inheritdoc}
*/
public function getDepth($countryCode) {
if (empty($this->depths)) {
$cache_key = 'address.subdivisions.depths';
if ($cached = $this->cache->get($cache_key)) {
$this->depths = $cached->data;
}
else {
$filename = $this->definitionPath . 'depths.json';
$this->depths = json_decode(file_get_contents($filename), TRUE);
$this->cache->set($cache_key, $this->depths, CacheBackendInterface::CACHE_PERMANENT, ['subdivisions']);
}
}
return isset($this->depths[$countryCode]) ? $this->depths[$countryCode] : 0;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*/
protected function loadDefinitions($countryCode, $parentId = NULL) {
$lookup_id = $parentId ?: $countryCode;
if (isset($this->definitions[$lookup_id])) {
return $this->definitions[$lookup_id];
protected function loadDefinitions(array $parents) {
$group = $this->buildGroup($parents);
if (isset($this->definitions[$group])) {
return $this->definitions[$group];
}
// If there are predefined subdivisions at this level, try to load them.
$this->definitions[$lookup_id] = [];
if ($this->hasData($countryCode, $parentId)) {
$cache_key = 'address.subdivisions.' . $lookup_id;
$filename = $this->definitionPath . $lookup_id . '.json';
$this->definitions[$group] = [];
if ($this->hasData($parents)) {
$cache_key = 'address.subdivisions.' . $group;
$filename = $this->definitionPath . $group . '.json';
if ($cached = $this->cache->get($cache_key)) {
$this->definitions[$lookup_id] = $cached->data;
$this->definitions[$group] = $cached->data;
}
elseif ($raw_definition = @file_get_contents($filename)) {
$this->definitions[$lookup_id] = json_decode($raw_definition, TRUE);
$this->cache->set($cache_key, $this->definitions[$lookup_id], CacheBackendInterface::CACHE_PERMANENT, ['subdivisions']);
$this->definitions[$group] = json_decode($raw_definition, TRUE);
$this->definitions[$group] = $this->processDefinitions($this->definitions[$group]);
$this->cache->set($cache_key, $this->definitions[$group], CacheBackendInterface::CACHE_PERMANENT, ['subdivisions']);
}
}
return $this->definitions[$lookup_id];
}
/**
* {@inheritdoc}
*/
protected function getDefaultLocale() {
// The getCurrentLanguage() fallback is a workaround for core bug #2684873.
$language = $this->languageManager->getConfigOverrideLanguage() ?: $this->languageManager->getCurrentLanguage();
return $language->getId();
return $this->definitions[$group];
}
}
......@@ -185,7 +185,7 @@ class AddressDefaultWidgetTest extends WebTestBase {
'organization' => 'Some Organization',
'address_line1' => '1098 Alta Ave',
'locality' => 'Mountain View',
'administrative_area' => 'US-CA',
'administrative_area' => 'CA',
'postal_code' => '94043',
];
$edit = [];
......@@ -337,7 +337,7 @@ class AddressDefaultWidgetTest extends WebTestBase {
$edit[$field_name . '[0][address_line1]'] = '1098 Alta Ave';
$edit[$field_name . '[0][address_line2]'] = 'Street 2';
$edit[$field_name . '[0][locality]'] = 'Mountain View';
$edit[$field_name . '[0][administrative_area]'] = 'US-CA';
$edit[$field_name . '[0][administrative_area]'] = 'CA';
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertResponse(200);
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
......@@ -351,31 +351,31 @@ class AddressDefaultWidgetTest extends WebTestBase {
$field_name = $this->field->getName();
// Using China since it has predefined subdivisions on all three levels.
$country = 'CN';
$administrativeArea = 'CN-13';
$locality = 'CN-13-2c7460';
$administrativeAreas = $this->subdivisionRepository->getList($country);
$localities = $this->subdivisionRepository->getList($country, $administrativeArea);
$dependentLocalities = $this->subdivisionRepository->getList($country, $locality);
$administrative_area = 'Hebei Sheng';
$locality = 'Chengde Shi';
$administrative_areas = $this->subdivisionRepository->getList([$country]);
$localities = $this->subdivisionRepository->getList([$country, $administrative_area]);
$dependent_localities = $this->subdivisionRepository->getList([$country, $administrative_area, $locality]);
// Confirm the presence and format of the administrative area dropdown.
$edit = [];
$edit[$field_name . '[0][country_code]'] = $country;
$this->drupalPostAjaxForm($this->nodeAddUrl, $edit, $field_name . '[0][country_code]');
$this->assertOptions($field_name . '[0][administrative_area]', array_keys($administrativeAreas), 'All administrative areas for country ' . $country . ' are present.');
$this->assertOptions($field_name . '[0][administrative_area]', array_keys($administrative_areas), 'All administrative areas for country ' . $country . ' are present.');
// Confirm the presence and format of the locality dropdown.
$edit = [];
$edit[$field_name . '[0][administrative_area]'] = $administrativeArea;
$edit[$field_name . '[0][administrative_area]'] = $administrative_area;
$this->drupalPostAjaxForm(NULL, $edit, $field_name . '[0][administrative_area]');
$this->assertResponse(200);
$this->assertOptionSelectedWithDrupalSelector('edit-field-address-0-administrative-area', $administrativeArea, 'Selected administrative area ' . $administrativeAreas[$administrativeArea]);
$this->assertOptions($field_name . '[0][locality]', array_keys($localities), 'All localities for administrative area ' . $administrativeAreas[$administrativeArea] . ' are present.');
$this->assertOptionSelectedWithDrupalSelector('edit-field-address-0-administrative-area', $administrative_area, 'Selected administrative area ' . $administrative_areas[$administrative_area]);
$this->assertOptions($field_name . '[0][locality]', array_keys($localities), 'All localities for administrative area ' . $administrative_areas[$administrative_area] . ' are present.');
// Confirm the presence and format of the dependent locality dropdown.
$edit[$field_name . '[0][locality]'] = $locality;
$this->drupalPostAjaxForm(NULL, $edit, $field_name . '[0][locality]');
$this->assertResponse(200);
$this->assertOptionSelectedWithDrupalSelector('edit-field-address-0-locality', $locality, 'Selected locality ' . $localities[$locality]);
$this->assertOptions($field_name . '[0][dependent_locality]', array_keys($dependentLocalities), 'All dependent localities for locality ' . $localities[$locality] . ' are present.');
$this->assertOptions($field_name . '[0][dependent_locality]', array_keys($dependent_localities), 'All dependent localities for locality ' . $localities[$locality] . ' are present.');
}
/**
......@@ -393,7 +393,7 @@ class AddressDefaultWidgetTest extends WebTestBase {
$edit[$field_name . '[0][address_line1]'] = '1098 Alta Ave';
$edit[$field_name . '[0][address_line2]'] = 'Street 2';
$edit[$field_name . '[0][locality]'] = 'Mountain View';
$edit[$field_name . '[0][administrative_area]'] = 'US-CA';
$edit[$field_name . '[0][administrative_area]'] = 'CA';
$edit[$field_name . '[0][postal_code]'] = '94043';
$this->drupalPostForm($this->nodeAddUrl, $edit, t('Save'));
$this->assertResponse(200);
......@@ -401,7 +401,7 @@ class AddressDefaultWidgetTest extends WebTestBase {
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertFieldByName($field_name . '[0][country_code]', 'US');
$this->assertFieldByName($field_name . '[0][administrative_area]', 'US-CA');
$this->assertFieldByName($field_name . '[0][administrative_area]', 'CA');
$this->assertFieldByName($field_name . '[0][locality]', 'Mountain View');
$this->assertFieldByName($field_name . '[0][postal_code]', '94043');
......@@ -436,13 +436,13 @@ class AddressDefaultWidgetTest extends WebTestBase {
*/
protected function assertOptions($id, $options, $message) {
$elements = $this->xpath('//select[@name="' . $id . '"]/option/@value');
$foundOptions = [];
$found_options = [];
foreach ($elements as $key => $element) {
if ($option = $element->__toString()) {
$foundOptions[] = $option;
$found_options[] = $option;
}
}
$this->assertFieldValues($foundOptions, $options, $message);
$this->assertFieldValues($found_options, $options, $message);
}
/**
......
......@@ -26,8 +26,8 @@
* - country.name: Country name.
*
* if a subdivision (dependent_locality, locality, administrative_area) was
* entered, the array will always have a name. If it's a predefined subdivision,
* it will also have a code (e.g. 'CA' for California), which is preferred.
* entered, the array will always have a code. If it's a predefined subdivision,
* it will also have a name. The code is always prefered.
*
* @ingroup themeable
*/
......@@ -45,11 +45,11 @@
{% if address_line2 %}
{{ address_line2 }} <br>
{% endif %}
{% if dependent_locality.name %}
{{ dependent_locality.code ?: dependent_locality.name }} <br>
{% if dependent_locality.code %}
{{ dependent_locality.code }} <br>
{% endif %}
{% if locality.name or postal_code or administrative_area.name %}
{{ locality.code ?: locality.name }} {{ postal_code }} {{ administrative_area.code ?: administrative_area.name }} <br>
{% if locality.code or postal_code or administrative_area.code %}
{{ locality.code }} {{ postal_code }} {{ administrative_area.code }} <br>
{% endif %}
{{ country.name }}
</p>
......@@ -35,7 +35,7 @@ class AddressTestEventSubscriber implements EventSubscriberInterface {
public function getInitialValues() {
return [
'country_code' => 'AU',
'administrative_area' => 'AU-NSW',
'administrative_area' => 'NSW',
'locality' => 'Sydney',
'dependent_locality' => '',
'postal_code' => '2000',
......
......@@ -84,7 +84,7 @@ class ZoneTest extends JavascriptTestBase {
'name' => 'Test zone',
'scope' => $this->randomMachineName(6),
'members[0][form][name]' => 'California',
'members[0][form][administrative_area]' => 'US-CA',
'members[0][form][administrative_area]' => 'CA',
'members[0][form][included_postal_codes]' => '123',
'members[0][form][excluded_postal_codes]' => '456',
'members[1][form][name]' => 'European Union',
......
......@@ -96,7 +96,7 @@ class DefaultFormatterTest extends KernelTestBase {
$entity = EntityTest::create([]);
$entity->{$this->fieldName} = [
'country_code' => 'AD',
'locality' => 'AD-07',
'locality' => 'Canillo',
'postal_code' => 'AD500',
'address_line1' => 'C. Prat de la Creu, 62-64',
];
......@@ -107,7 +107,7 @@ class DefaultFormatterTest extends KernelTestBase {
$expected = implode('', [
'line1' => '<p class="address" translate="no">',
'line2' => '<span class="address-line1">C. Prat de la Creu, 62-64</span><br>' . "\n",
'line3' => '<span class="postal-code">AD500</span> <span class="locality">Parròquia d&#039;Andorra la Vella</span><br>' . "\n",
'line3' => '<span class="postal-code">AD500</span> <span class="locality">Canillo</span><br>' . "\n",
'line4' => '<span class="country">Andorra</span>',
'line5' => '</p>',
]);
......@@ -160,8 +160,8 @@ class DefaultFormatterTest extends KernelTestBase {
$entity->{$this->fieldName} = [
'langcode' => 'zh-hant',
'country_code' => 'TW',
'administrative_area' => 'TW-TPE',
'locality' => 'TW-TPE-e3cc33',
'administrative_area' => 'Taipei City',
'locality' => "Da'an District",
'address_line1' => 'Sec. 3 Hsin-yi Rd.',
'postal_code' => '106',
// Any HTML in the fields is supposed to be escaped.
......@@ -178,7 +178,7 @@ class DefaultFormatterTest extends KernelTestBase {
'line4' => '<span class="administrative-area">台北市</span><span class="locality">大安區</span><br>' . "\n",
'line5' => '<span class="address-line1">Sec. 3 Hsin-yi Rd.</span><br>' . "\n",
'line6' => '<span class="organization">Giant &lt;h2&gt;Bike&lt;/h2&gt; Store</span><br>' . "\n",
'line7' => '<span class="family-name">Chen</span><span class="given-name">Wu</span>',
'line7' => '<span class="family-name">Chen</span> <span class="given-name">Wu</span>',
'line8' => '</p>',
]);
$this->assertRaw($expected, 'The TW address has been properly formatted.');
......@@ -191,7 +191,7 @@ class DefaultFormatterTest extends KernelTestBase {
$entity = EntityTest::create([]);
$entity->{$this->fieldName} = [
'country_code' => 'US',
'administrative_area' => 'US-CA',
'administrative_area' => 'CA',
'address_line1' => '1098 Alta Ave',
'postal_code' => '94043',
];
......
......@@ -98,7 +98,7 @@ class PlainFormatterTest extends KernelTestBase {
$entity = EntityTest::create([]);
$entity->{$this->fieldName} = [
'country_code' => 'AD',
'locality' => 'AD-07',
'locality' => 'Canillo',
'postal_code' => 'AD500',
'address_line1' => 'C. Prat de la Creu, 62-64',
];
......@@ -109,7 +109,7 @@ class PlainFormatterTest extends KernelTestBase {
$expected_elements = [
'C. Prat de la Creu, 62-64',
'AD500',
'Parròquia d&#039;Andorra la Vella',
'Canillo',
'Andorra',
];
foreach ($expected_elements as $expected_element) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment