Created
December 21, 2018 01:05
-
-
Save bojanz/4cdaf73577d3e2b3979ddf347ff16834 to your computer and use it in GitHub Desktop.
Addressbook 2.x UX
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Drupal\commerce_order\Plugin\Commerce\InlineForm; | |
use Drupal\commerce\EntityHelper; | |
use Drupal\commerce\Plugin\Commerce\InlineForm\EntityInlineFormBase; | |
use Drupal\Component\Utility\Html; | |
use Drupal\Component\Utility\NestedArray; | |
use Drupal\Core\Entity\Entity\EntityFormDisplay; | |
use Drupal\Core\Entity\EntityTypeManagerInterface; | |
use Drupal\Core\Field\BaseFieldDefinition; | |
use Drupal\Core\Form\FormStateInterface; | |
use Drupal\Core\Session\AccountInterface; | |
use Drupal\profile\Entity\ProfileInterface; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
/** | |
* Provides an inline form for managing a customer profile. | |
* | |
* @CommerceInlineForm( | |
* id = "customer_profile", | |
* label = @Translation("Customer profile"), | |
* ) | |
*/ | |
class CustomerProfile extends EntityInlineFormBase { | |
/** | |
* The profile storage. | |
* | |
* @var \Drupal\profile\ProfileStorageInterface | |
*/ | |
protected $profileStorage; | |
/** | |
* Constructs a new CustomerProfile 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 \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager | |
* The entity type manager. | |
*/ | |
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) { | |
parent::__construct($configuration, $plugin_id, $plugin_definition); | |
$this->profileStorage = $entity_type_manager->getStorage('profile'); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { | |
return new static( | |
$configuration, | |
$plugin_id, | |
$plugin_definition, | |
$container->get('entity_type.manager') | |
); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function defaultConfiguration() { | |
return [ | |
'allow_reuse' => TRUE, | |
// The country to select if the address widget doesn't have a default. | |
'default_country' => NULL, | |
// If empty, all countries will be available. | |
'available_countries' => [], | |
]; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
protected function validateConfiguration() { | |
parent::validateConfiguration(); | |
if (!is_array($this->configuration['available_countries'])) { | |
throw new \RuntimeException('The available_countries configuration value must be an array.'); | |
} | |
// Make sure that the specified default country is available. | |
if (!empty($this->configuration['default_country']) && !empty($this->configuration['available_countries'])) { | |
if (!in_array($this->configuration['default_country'], $this->configuration['available_countries'])) { | |
$this->configuration['default_country'] = NULL; | |
} | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function buildInlineForm(array $inline_form, FormStateInterface $form_state) { | |
$inline_form = parent::buildInlineForm($inline_form, $form_state); | |
assert($this->entity instanceof ProfileInterface); | |
$profile_options = NULL; | |
$selected_profile = NULL; | |
$selected_option = NULL; | |
if ($this->configuration['allow_reuse']) { | |
$owner = $this->entity->getOwner(); | |
$profile_type = $this->entity->bundle(); | |
$available_profiles = $this->getAvailableProfiles($owner, $profile_type); | |
$profile_options = EntityHelper::extractLabels($available_profiles); | |
if ($available_profiles) { | |
if ($this->allowNew($owner, $profile_type)) { | |
$profile_options['_new'] = $this->t('+ Enter a new address'); | |
} | |
$selected_profile = $this->getSelectedProfile($inline_form, $form_state, $available_profiles); | |
$selected_option = $selected_profile->isNew() ? '_new' : $selected_profile->id(); | |
} | |
} | |
$id_prefix = implode('-', $inline_form['#parents']); | |
$wrapper_id = Html::getId($id_prefix . '-ajax-wrapper'); | |
$inline_form['#prefix'] = '<div id="' . $wrapper_id . '">'; | |
$inline_form['#suffix'] = '</div>'; | |
$inline_form['available_profiles'] = [ | |
'#type' => 'select', | |
'#title' => $this->t('Select an address'), | |
'#options' => $profile_options, | |
'#default_value' => $selected_option, | |
'#access' => !empty($profile_options), | |
'#ajax' => [ | |
'callback' => [get_called_class(), 'ajaxRefresh'], | |
'wrapper' => $wrapper_id, | |
], | |
'#attributes' => [ | |
'class' => ['available-profiles'], | |
], | |
]; | |
if (!empty($selected_profile)) { | |
// Copy field values from the selected profile to the actual profile. | |
$selected_profile_values = $this->getFieldValues($selected_profile); | |
foreach ($selected_profile_values as $field_name => $value) { | |
$this->entity->set($field_name, $value); | |
} | |
} | |
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, 'default'); | |
$form_display->buildForm($this->entity, $inline_form, $form_state); | |
$inline_form = $this->prepareProfileForm($inline_form, $form_state); | |
return $inline_form; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function validateInlineForm(array &$inline_form, FormStateInterface $form_state) { | |
parent::validateInlineForm($inline_form, $form_state); | |
$triggering_element = $form_state->getTriggeringElement(); | |
$triggering_element_name = end($triggering_element['#array_parents']); | |
if ($triggering_element_name == 'available_profiles') { | |
// Clear user input to allow the updated default values to be shown. | |
$user_input = $form_state->getUserInput(); | |
NestedArray::setValue($user_input, $inline_form['#parents'], NULL); | |
$form_state->setUserInput($user_input); | |
} | |
assert($this->entity instanceof ProfileInterface); | |
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, 'default'); | |
$form_display->extractFormValues($this->entity, $inline_form, $form_state); | |
$form_display->validateFormValues($this->entity, $inline_form, $form_state); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function submitInlineForm(array &$inline_form, FormStateInterface $form_state) { | |
parent::submitInlineForm($inline_form, $form_state); | |
assert($this->entity instanceof ProfileInterface); | |
$form_display = EntityFormDisplay::collectRenderDisplay($this->entity, 'default'); | |
$form_display->extractFormValues($this->entity, $inline_form, $form_state); | |
$this->entity->save(); | |
} | |
/** | |
* Ajax callback. | |
*/ | |
public static function ajaxRefresh(array &$form, FormStateInterface $form_state) { | |
$triggering_element = $form_state->getTriggeringElement(); | |
$inline_form = NestedArray::getValue($form, array_slice($triggering_element['#array_parents'], 0, -1)); | |
return $inline_form; | |
} | |
/** | |
* Prepares the profile form. | |
* | |
* @param array $profile_form | |
* The profile form. | |
* @param \Drupal\Core\Form\FormStateInterface $form_state | |
* The current state of the form. | |
* | |
* @return array | |
* The prepared profile form. | |
*/ | |
protected function prepareProfileForm(array $profile_form, FormStateInterface $form_state) { | |
if (!empty($profile_form['address']['widget'][0])) { | |
$address_widget = &$profile_form['address']['widget'][0]; | |
// Remove the details wrapper from the address widget. | |
$address_widget['#type'] = 'container'; | |
// Provide a default country. | |
$default_country = $this->configuration['default_country']; | |
if ($default_country && empty($address_widget['address']['#default_value']['country_code'])) { | |
$address_widget['address']['#default_value']['country_code'] = $default_country; | |
} | |
// Limit the available countries. | |
$available_countries = $this->configuration['available_countries']; | |
if ($available_countries) { | |
$address_widget['address']['#available_countries'] = $available_countries; | |
} | |
} | |
return $profile_form; | |
} | |
/** | |
* Gets the available profiles for the given user. | |
* | |
* @param \Drupal\Core\Session\AccountInterface $account | |
* The user. | |
* @param string $profile_type | |
* The profile type. | |
* | |
* @return \Drupal\profile\Entity\ProfileInterface[] | |
* The available profiles. | |
*/ | |
protected function getAvailableProfiles(AccountInterface $account, $profile_type) { | |
$available_profiles = []; | |
if ($account->isAuthenticated()) { | |
$available_profiles = $this->profileStorage->loadMultipleByUser($account, $profile_type, TRUE); | |
$available_profiles = $this->filterAvailableProfiles($available_profiles); | |
// Sort the profiles newest-first. | |
krsort($available_profiles); | |
} | |
return $available_profiles; | |
} | |
/** | |
* Filters available profiles. | |
* | |
* @param \Drupal\profile\Entity\ProfileInterface[] $available_profiles | |
* The available profiles. | |
* | |
* @return \Drupal\profile\Entity\ProfileInterface[] | |
* The filtered available profiles. | |
*/ | |
protected function filterAvailableProfiles(array $available_profiles) { | |
$available_countries = $this->configuration['available_countries']; | |
// Filter out profiles with unavailable countries. | |
if ($available_countries) { | |
foreach ($available_profiles as $profile_id => $profile) { | |
/** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */ | |
$address = $profile->get('address')->first(); | |
if (!in_array($address->getCountryCode(), $available_countries)) { | |
unset($available_profiles[$profile_id]); | |
} | |
} | |
} | |
return $available_profiles; | |
} | |
/** | |
* Gets the selected profile. | |
* | |
* @param array $inline_form | |
* The form element. | |
* @param \Drupal\Core\Form\FormStateInterface $form_state | |
* The current state of the form. | |
* @param \Drupal\profile\Entity\ProfileInterface[] $available_profiles | |
* The available profiles. | |
* | |
* @return \Drupal\profile\Entity\ProfileInterface | |
* The selected profile. | |
*/ | |
protected function getSelectedProfile(array $inline_form, FormStateInterface $form_state, array $available_profiles) { | |
$selected_profile = $form_state->getValue(array_merge($inline_form['#parents'], ['available_profiles'])); | |
if (empty($selected_profile)) { | |
$profile = $this->getDefaultProfile($available_profiles); | |
} | |
elseif ($selected_profile == '_new') { | |
assert($this->entity instanceof ProfileInterface); | |
$profile = $this->profileStorage->create([ | |
'type' => $this->entity->bundle(), | |
'uid' => $this->entity->getOwnerId(), | |
]); | |
} | |
else { | |
/** @var \Drupal\profile\Entity\ProfileInterface $profile */ | |
$profile = $this->profileStorage->load($selected_profile); | |
} | |
return $profile; | |
} | |
/** | |
* Gets a default profile from the list of available profiles. | |
* | |
* @param \Drupal\profile\Entity\ProfileInterface[] $available_profiles | |
* The available profiles. | |
* | |
* @return \Drupal\profile\Entity\ProfileInterface | |
* The default profile. | |
*/ | |
protected function getDefaultProfile(array $available_profiles) { | |
$default_profile = reset($available_profiles); | |
foreach ($available_profiles as $available_profile) { | |
if ($available_profile->isDefault()) { | |
$default_profile = $available_profile; | |
break; | |
} | |
} | |
return $default_profile; | |
} | |
/** | |
* Gets whether the given user is allowed to enter new profile information. | |
* | |
* @param \Drupal\Core\Session\AccountInterface $account | |
* The user. | |
* @param string $profile_type | |
* The profile type. | |
* | |
* @return bool | |
* TRUE if the user has access, FALSE otherwise. | |
*/ | |
protected function allowNew(AccountInterface $account, $profile_type) { | |
// Allowed by default. Child classes can add permission or role checks here. | |
return TRUE; | |
} | |
/** | |
* Gets the field values of the given profile. | |
* | |
* Doesn't include base field values, since they are not supposed to change | |
* when copying values between profiles. | |
* | |
* @param \Drupal\profile\Entity\ProfileInterface $profile | |
* The profile. | |
* | |
* @return array | |
* The field values. | |
*/ | |
protected function getFieldValues(ProfileInterface $profile) { | |
$values = $profile->toArray(); | |
foreach ($profile->getFieldDefinitions() as $field_name => $definition) { | |
if ($definition instanceof BaseFieldDefinition) { | |
unset($values[$field_name]); | |
} | |
} | |
return $values; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment