Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
address
Commits
c6448794
Commit
c6448794
authored
May 13, 2017
by
bojanz
Browse files
Issue
#2727777
by DuneBL, bojanz: Add a country field type
parent
21d629b5
Changes
17
Hide whitespace changes
Inline
Side-by-side
address.views.inc
View file @
c6448794
...
...
@@ -15,34 +15,45 @@ use Drupal\field\FieldStorageConfigInterface;
* Views integration for address fields.
*/
function
address_field_views_data
(
FieldStorageConfigInterface
$field
)
{
// Provide a field handler for each individual column.
$columns
=
[
'country_code'
=>
'country_code'
,
'administrative_area'
=>
'subdivision'
,
'locality'
=>
'subdivision'
,
'dependent_locality'
=>
'subdivision'
,
'postal_code'
=>
'standard'
,
'sorting_code'
=>
'standard'
,
'address_line1'
=>
'standard'
,
'address_line2'
=>
'standard'
,
'organization'
=>
'standard'
,
'given_name'
=>
'standard'
,
'additional_name'
=>
'standard'
,
'family_name'
=>
'standard'
,
];
$data
=
views_field_default_views_data
(
$field
);
$field_type
=
$field
->
getType
();
$field_name
=
$field
->
getName
();
foreach
(
$data
as
$table_name
=>
$table_data
)
{
foreach
(
$columns
as
$column
=>
$plugin_id
)
{
$data
[
$table_name
][
$field_name
.
'_'
.
$column
][
'field'
]
=
[
'id'
=>
$plugin_id
,
if
(
$field_type
==
'address'
)
{
$columns
=
[
'country_code'
=>
'country_code'
,
'administrative_area'
=>
'subdivision'
,
'locality'
=>
'subdivision'
,
'dependent_locality'
=>
'subdivision'
,
'postal_code'
=>
'standard'
,
'sorting_code'
=>
'standard'
,
'address_line1'
=>
'standard'
,
'address_line2'
=>
'standard'
,
'organization'
=>
'standard'
,
'given_name'
=>
'standard'
,
'additional_name'
=>
'standard'
,
'family_name'
=>
'standard'
,
];
foreach
(
$data
as
$table_name
=>
$table_data
)
{
foreach
(
$columns
as
$column
=>
$plugin_id
)
{
$data
[
$table_name
][
$field_name
.
'_'
.
$column
][
'field'
]
=
[
'id'
=>
$plugin_id
,
'field_name'
=>
$field_name
,
'property'
=>
$column
,
];
}
// Add the custom country_code filter.
$data
[
$table_name
][
$field_name
.
'_country_code'
][
'filter'
][
'id'
]
=
'country_code'
;
}
}
elseif
(
$field_type
==
'address_country'
)
{
foreach
(
$data
as
$table_name
=>
$table_data
)
{
$data
[
$table_name
][
$field_name
.
'_value'
][
'field'
]
=
[
'id'
=>
'country_code'
,
'field_name'
=>
$field_name
,
'property'
=>
$column
,
'property'
=>
'value'
,
];
$data
[
$table_name
][
$field_name
.
'_value'
][
'filter'
][
'id'
]
=
'country_code'
;
}
// Add the custom country_code filter.
$data
[
$table_name
][
$field_name
.
'_country_code'
][
'filter'
][
'id'
]
=
'country_code'
;
}
return
$data
;
...
...
config/schema/address.schema.yml
View file @
c6448794
...
...
@@ -91,6 +91,24 @@ field.widget.settings.address_default:
type
:
string
label
:
'
Default
country'
field.value.address_country
:
type
:
mapping
label
:
'
Default
value'
mapping
:
value
:
type
:
string
label
:
'
Country
code'
field.field_settings.address_country
:
type
:
mapping
label
:
'
Country
field
settings'
mapping
:
available_countries
:
type
:
sequence
label
:
'
Available
countries'
sequence
:
-
type
:
string
views.filter.country_code
:
type
:
views.filter.in_operator
label
:
'
Country'
src/Element/Address.php
View file @
c6448794
...
...
@@ -78,27 +78,28 @@ class Address extends FormElement {
*/
public
static
function
valueCallback
(
&
$element
,
$input
,
FormStateInterface
$form_state
)
{
if
(
is_array
(
$input
))
{
return
$input
;
$value
=
$input
;
}
else
{
if
(
!
is_array
(
$element
[
'#default_value'
]))
{
$element
[
'#default_value'
]
=
[];
}
// Initialize properties.
$properties
=
[
'given_name'
,
'additional_name'
,
'family_name'
,
'organization'
,
'address_line1'
,
'address_line2'
,
'postal_code'
,
'sorting_code'
,
'dependent_locality'
,
'locality'
,
'administrative_area'
,
'country_code'
,
'langcode'
,
];
foreach
(
$properties
as
$property
)
{
if
(
!
isset
(
$element
[
'#default_value'
][
$property
]))
{
$element
[
'#default_value'
][
$property
]
=
NULL
;
}
$value
=
$element
[
'#default_value'
];
}
// Initialize default keys.
$properties
=
[
'given_name'
,
'additional_name'
,
'family_name'
,
'organization'
,
'address_line1'
,
'address_line2'
,
'postal_code'
,
'sorting_code'
,
'dependent_locality'
,
'locality'
,
'administrative_area'
,
'country_code'
,
'langcode'
,
];
foreach
(
$properties
as
$property
)
{
if
(
!
isset
(
$value
[
$property
]))
{
$value
[
$property
]
=
NULL
;
}
return
$element
[
'#default_value'
];
}
return
$value
;
}
/**
...
...
@@ -115,36 +116,16 @@ class Address extends FormElement {
* The processed element.
*
* @throws \InvalidArgumentException
* Thrown when
#available_countries or
#used_fields is malformed.
* Thrown when #used_fields is malformed.
*/
public
static
function
processAddress
(
array
&
$element
,
FormStateInterface
$form_state
,
array
&
$complete_form
)
{
if
(
isset
(
$element
[
'#available_countries'
])
&&
!
is_array
(
$element
[
'#available_countries'
]))
{
throw
new
\
InvalidArgumentException
(
'The #available_countries property must be an array.'
);
}
if
(
isset
(
$element
[
'#used_fields'
])
&&
!
is_array
(
$element
[
'#used_fields'
]))
{
throw
new
\
InvalidArgumentException
(
'The #used_fields property must be an array.'
);
}
$id_prefix
=
implode
(
'-'
,
$element
[
'#parents'
]);
$wrapper_id
=
Html
::
getUniqueId
(
$id_prefix
.
'-ajax-wrapper'
);
$full_country_list
=
\
Drupal
::
service
(
'address.country_repository'
)
->
getList
();
$country_list
=
$full_country_list
;
if
(
!
empty
(
$element
[
'#available_countries'
]))
{
$available_countries
=
$element
[
'#available_countries'
];
if
(
!
empty
(
$element
[
'#default_value'
][
'country_code'
]))
{
// The current country should always be available.
$available_countries
[]
=
$element
[
'#default_value'
][
'country_code'
];
}
$available_countries
=
array_combine
(
$available_countries
,
$available_countries
);
$country_list
=
array_intersect_key
(
$country_list
,
$available_countries
);
}
$value
=
$element
[
'#value'
];
if
(
empty
(
$value
[
'country_code'
])
&&
$element
[
'#required'
])
{
// Fallback to the first country in the list if the default country
// is empty even though the field is required.
$value
[
'country_code'
]
=
key
(
$country_list
);
}
$element
=
[
'#tree'
=>
TRUE
,
...
...
@@ -157,35 +138,19 @@ class Address extends FormElement {
'#type'
=>
'hidden'
,
'#value'
=>
$value
[
'langcode'
],
];
// Hide the country dropdown when there is only one possible value.
if
(
count
(
$country_list
)
==
1
&&
$element
[
'#required'
])
{
$element
[
'country_code'
]
=
[
'#type'
=>
'hidden'
,
'#value'
=>
key
(
$available_countries
),
];
}
else
{
$element
[
'country_code'
]
=
[
'#type'
=>
'select'
,
'#title'
=>
t
(
'Country'
),
'#options'
=>
$country_list
,
'#default_value'
=>
$value
[
'country_code'
],
'#required'
=>
$element
[
'#required'
],
'#limit_validation_errors'
=>
[],
'#ajax'
=>
[
'callback'
=>
[
get_called_class
(),
'ajaxRefresh'
],
'wrapper'
=>
$wrapper_id
,
],
'#attributes'
=>
[
'class'
=>
[
'country'
],
'autocomplete'
=>
'country'
,
],
'#weight'
=>
-
100
,
];
if
(
!
$element
[
'#required'
])
{
$element
[
'country_code'
][
'#empty_value'
]
=
''
;
}
}
$element
[
'country_code'
]
=
[
'#type'
=>
'address_country'
,
'#title'
=>
t
(
'Country'
),
'#available_countries'
=>
$element
[
'#available_countries'
],
'#default_value'
=>
$value
[
'country_code'
],
'#required'
=>
$element
[
'#required'
],
'#limit_validation_errors'
=>
[],
'#ajax'
=>
[
'callback'
=>
[
get_called_class
(),
'ajaxRefresh'
],
'wrapper'
=>
$wrapper_id
,
],
'#weight'
=>
-
100
,
];
if
(
!
empty
(
$value
[
'country_code'
]))
{
$element
=
static
::
addressElements
(
$element
,
$value
);
}
...
...
@@ -366,7 +331,7 @@ class Address extends FormElement {
*/
public
static
function
ajaxRefresh
(
array
$form
,
FormStateInterface
$form_state
)
{
$country_element
=
$form_state
->
getTriggeringElement
();
$address_element
=
NestedArray
::
getValue
(
$form
,
array_slice
(
$country_element
[
'#array_parents'
],
0
,
-
1
));
$address_element
=
NestedArray
::
getValue
(
$form
,
array_slice
(
$country_element
[
'#array_parents'
],
0
,
-
2
));
return
$address_element
;
}
...
...
src/Element/Country.php
0 → 100644
View file @
c6448794
<?php
namespace
Drupal\address\Element
;
use
Drupal\Core\Form\FormStateInterface
;
use
Drupal\Core\Render\Element\FormElement
;
/**
* Provides a country form element.
*
* Usage example:
* @code
* $form['country'] = [
* '#type' => 'address_country',
* '#default_value' => 'DE',
* '#available_countries' => ['DE', 'FR'],
* ];
* @endcode
*
* @FormElement("address_country")
*/
class
Country
extends
FormElement
{
/**
* {@inheritdoc}
*/
public
function
getInfo
()
{
$class
=
get_class
(
$this
);
return
[
// List of country codes. If empty, all countries will be available.
'#available_countries'
=>
[],
'#input'
=>
TRUE
,
'#multiple'
=>
FALSE
,
'#default_value'
=>
NULL
,
'#process'
=>
[
[
$class
,
'processCountry'
],
[
$class
,
'processGroup'
],
],
'#theme_wrappers'
=>
[
'container'
],
];
}
/**
* Processes the address_country form element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*
* @throws \InvalidArgumentException
* Thrown when #available_countries is malformed.
*/
public
static
function
processCountry
(
array
&
$element
,
FormStateInterface
$form_state
,
array
&
$complete_form
)
{
if
(
isset
(
$element
[
'#available_countries'
])
&&
!
is_array
(
$element
[
'#available_countries'
]))
{
throw
new
\
InvalidArgumentException
(
'The #available_countries property must be an array.'
);
}
$full_country_list
=
\
Drupal
::
service
(
'address.country_repository'
)
->
getList
();
$country_list
=
$full_country_list
;
if
(
!
empty
(
$element
[
'#available_countries'
]))
{
$available_countries
=
$element
[
'#available_countries'
];
if
(
!
empty
(
$element
[
'#default_value'
]))
{
// The current country should always be available.
$available_countries
[]
=
$element
[
'#default_value'
];
}
$available_countries
=
array_combine
(
$available_countries
,
$available_countries
);
$country_list
=
array_intersect_key
(
$country_list
,
$available_countries
);
}
$value
=
$element
[
'#value'
];
if
(
empty
(
$value
)
&&
$element
[
'#required'
])
{
// Fallback to the first country in the list if the default country
// is empty even though the field is required.
$value
=
key
(
$country_list
);
}
$element
[
'#tree'
]
=
TRUE
;
// Hide the dropdown when there is only one possible value.
if
(
count
(
$country_list
)
==
1
&&
$element
[
'#required'
])
{
$element
[
'country_code'
]
=
[
'#type'
=>
'hidden'
,
'#value'
=>
key
(
$available_countries
),
];
}
else
{
$element
[
'country_code'
]
=
[
'#type'
=>
'select'
,
'#title'
=>
$element
[
'#title'
],
'#options'
=>
$country_list
,
'#default_value'
=>
$value
,
'#required'
=>
$element
[
'#required'
],
'#limit_validation_errors'
=>
[],
'#attributes'
=>
[
'class'
=>
[
'country'
],
'autocomplete'
=>
'country'
,
],
'#weight'
=>
-
100
,
];
if
(
!
$element
[
'#required'
])
{
$element
[
'country_code'
][
'#empty_value'
]
=
''
;
}
if
(
!
empty
(
$element
[
'#ajax'
]))
{
$element
[
'country_code'
][
'#ajax'
]
=
$element
[
'#ajax'
];
unset
(
$element
[
'#ajax'
]);
}
}
// Remove the 'country_code' level from form state values.
$element
[
'country_code'
][
'#parents'
]
=
$element
[
'#parents'
];
return
$element
;
}
}
src/Plugin/Field/FieldFormatter/CountryDefaultFormatter.php
0 → 100644
View file @
c6448794
<?php
namespace
Drupal\address\Plugin\Field\FieldFormatter
;
use
CommerceGuys\Addressing\Country\CountryRepositoryInterface
;
use
Drupal\Core\Field\FormatterBase
;
use
Drupal\Core\Field\FieldDefinitionInterface
;
use
Drupal\Core\Field\FieldItemListInterface
;
use
Drupal\Core\Language\LanguageInterface
;
use
Drupal\Core\Plugin\ContainerFactoryPluginInterface
;
use
Symfony\Component\DependencyInjection\ContainerInterface
;
/**
* Plugin implementation of the 'country_default' formatter.
*
* @FieldFormatter(
* id = "address_country_default",
* label = @Translation("Default"),
* field_types = {
* "address_country",
* },
* )
*/
class
CountryDefaultFormatter
extends
FormatterBase
implements
ContainerFactoryPluginInterface
{
/**
* The country repository.
*
* @var \CommerceGuys\Addressing\Country\CountryRepositoryInterface
*/
protected
$countryRepository
;
/**
* Constructs an CountryDefaultFormatter object.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Any third party settings.
* @param \CommerceGuys\Addressing\Country\CountryRepositoryInterface $country_repository
* The country repository.
*/
public
function
__construct
(
$plugin_id
,
$plugin_definition
,
FieldDefinitionInterface
$field_definition
,
array
$settings
,
$label
,
$view_mode
,
array
$third_party_settings
,
CountryRepositoryInterface
$country_repository
)
{
parent
::
__construct
(
$plugin_id
,
$plugin_definition
,
$field_definition
,
$settings
,
$label
,
$view_mode
,
$third_party_settings
);
$this
->
countryRepository
=
$country_repository
;
}
/**
* {@inheritdoc}
*/
public
static
function
create
(
ContainerInterface
$container
,
array
$configuration
,
$plugin_id
,
$plugin_definition
)
{
return
new
static
(
$plugin_id
,
$plugin_definition
,
$configuration
[
'field_definition'
],
$configuration
[
'settings'
],
$configuration
[
'label'
],
$configuration
[
'view_mode'
],
$configuration
[
'third_party_settings'
],
$container
->
get
(
'address.country_repository'
)
);
}
/**
* {@inheritdoc}
*/
public
function
viewElements
(
FieldItemListInterface
$items
,
$langcode
)
{
$countries
=
$this
->
countryRepository
->
getList
();
$elements
=
[];
foreach
(
$items
as
$delta
=>
$item
)
{
$elements
[
$delta
]
=
[
'#plain_text'
=>
isset
(
$countries
[
$item
->
value
])
?
$countries
[
$item
->
value
]
:
$item
->
value
,
'#cache'
=>
[
'contexts'
=>
[
'languages:'
.
LanguageInterface
::
TYPE_INTERFACE
,
],
],
];
}
return
$elements
;
}
}
src/Plugin/Field/FieldType/AddressItem.php
View file @
c6448794
...
...
@@ -3,8 +3,6 @@
namespace
Drupal\address\Plugin\Field\FieldType
;
use
CommerceGuys\Addressing\AddressFormat\AddressField
;
use
Drupal\address\Event\AddressEvents
;
use
Drupal\address\Event\AvailableCountriesEvent
;
use
Drupal\address\AddressInterface
;
use
Drupal\address\LabelHelper
;
use
Drupal\Core\Field\FieldItemBase
;
...
...
@@ -20,18 +18,14 @@ use Drupal\Core\TypedData\DataDefinition;
* id = "address",
* label = @Translation("Address"),
* description = @Translation("An entity field containing a postal address"),
* category = @Translation("Address"),
* default_widget = "address_default",
* default_formatter = "address_default"
* )
*/
class
AddressItem
extends
FieldItemBase
implements
AddressInterface
{
/**
* An altered list of available countries.
*
* @var array
*/
protected
static
$availableCountries
=
[];
use
AvailableCountriesTrait
;
/**
* {@inheritdoc}
...
...
@@ -141,11 +135,10 @@ class AddressItem extends FieldItemBase implements AddressInterface {
* {@inheritdoc}
*/
public
static
function
defaultFieldSettings
()
{
return
[
'available_countries'
=>
[],
return
self
::
defaultCountrySettings
()
+
[
'fields'
=>
array_values
(
AddressField
::
getAll
()),
'langcode_override'
=>
''
,
]
+
parent
::
defaultFieldSettings
()
;
];
}
/**
...
...
@@ -161,16 +154,7 @@ class AddressItem extends FieldItemBase implements AddressInterface {
}
}
$element
=
[];
$element
[
'available_countries'
]
=
[
'#type'
=>
'select'
,
'#title'
=>
$this
->
t
(
'Available countries'
),
'#description'
=>
$this
->
t
(
'If no countries are selected, all countries will be available.'
),
'#options'
=>
\
Drupal
::
service
(
'address.country_repository'
)
->
getList
(),
'#default_value'
=>
$this
->
getSetting
(
'available_countries'
),
'#multiple'
=>
TRUE
,
'#size'
=>
10
,
];
$element
=
$this
->
countrySettingsForm
(
$form
,
$form_state
);
$element
[
'fields'
]
=
[
'#type'
=>
'checkboxes'
,
'#title'
=>
$this
->
t
(
'Used fields'
),
...
...
@@ -192,27 +176,6 @@ class AddressItem extends FieldItemBase implements AddressInterface {
return
$element
;
}
/**
* Gets the available countries for the current field.
*
* @return array
* A list of country codes.
*/
public
function
getAvailableCountries
()
{
// Alter the list once per field, instead of once per field delta.
$field_definition
=
$this
->
getFieldDefinition
();
$definition_id
=
spl_object_hash
(
$field_definition
);
if
(
!
isset
(
static
::
$availableCountries
[
$definition_id
]))
{
$available_countries
=
array_filter
(
$this
->
getSetting
(
'available_countries'
));
$event_dispatcher
=
\
Drupal
::
service
(
'event_dispatcher'
);
$event
=
new
AvailableCountriesEvent
(
$available_countries
,
$field_definition
);
$event_dispatcher
->
dispatch
(
AddressEvents
::
AVAILABLE_COUNTRIES
,
$event
);
static
::
$availableCountries
[
$definition_id
]
=
$event
->
getAvailableCountries
();
}
return
static
::
$availableCountries
[
$definition_id
];
}
/**
* Initializes and returns the langcode property for the current field.
*
...
...
@@ -262,11 +225,16 @@ class AddressItem extends FieldItemBase implements AddressInterface {
*/
public
function
getConstraints
()
{
$constraints
=
parent
::
getConstraints
();
$manager
=
\
Drupal
::
typedDataManager
()
->
getValidationConstraintManager
();
$available_countries
=
$this
->
getAvailableCountries
();
$constraint_manager
=
\
Drupal
::
typedDataManager
()
->
getValidationConstraintManager
();
$enabled_fields
=
array_filter
(
$this
->
getSetting
(
'fields'
));
$constraints
[]
=
$manager
->
create
(
'Country'
,
[
'availableCountries'
=>
$available_countries
]);
$constraints
[]
=
$manager
->
create
(
'AddressFormat'
,
[
'fields'
=>
$enabled_fields
]);
$constraints
[]
=
$constraint_manager
->
create
(
'ComplexData'
,
[
'country_code'
=>
[
'Country'
=>
[
'availableCountries'
=>
$this
->
getAvailableCountries
(),
],
],
]);
$constraints
[]
=
$constraint_manager
->
create
(
'AddressFormat'
,
[
'fields'
=>
$enabled_fields
]);
return
$constraints
;
}
...
...
src/Plugin/Field/FieldType/AvailableCountriesTrait.php
0 → 100644
View file @
c6448794
<?php
namespace
Drupal\address\Plugin\Field\FieldType
;
use
Drupal\address\Event\AddressEvents
;
use
Drupal\address\Event\AvailableCountriesEvent
;
use
Drupal\Core\Form\FormStateInterface
;
/**
* Allows field types to limit the available countries.
*/
trait
AvailableCountriesTrait
{
/**
* An altered list of available countries.
*
* @var array
*/
protected
static
$availableCountries
=
[];
/**
* Defines the default field-level settings.
*
* @return array
* A list of default settings, keyed by the setting name.
*/
public
static
function
defaultCountrySettings
()
{
return
[
'available_countries'
=>
[],
];
}
/**
* Builds the field settings form.
*
* @param array $form
* The form where the settings form is being included in.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the (entire) configuration form.
*
* @return array
* The modified form.
*/
public
function
countrySettingsForm
(
array
$form
,
FormStateInterface
$form_state
)
{
$element
=
[];
$element
[
'available_countries'
]
=
[
'#type'
=>
'select'
,
'#title'
=>
$this
->
t
(
'Available countries'
),
'#description'
=>
$this
->
t
(
'If no countries are selected, all countries will be available.'
),
'#options'
=>
\
Drupal
::
service
(
'address.country_repository'
)
->
getList
(),
'#default_value'
=>
$this
->
getSetting
(
'available_countries'
),
'#multiple'
=>
TRUE
,
'#size'
=>
10
,
];