Skip to content

Instantly share code, notes, and snippets.

@bojanz
Created December 21, 2018 01:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bojanz/4cdaf73577d3e2b3979ddf347ff16834 to your computer and use it in GitHub Desktop.
Save bojanz/4cdaf73577d3e2b3979ddf347ff16834 to your computer and use it in GitHub Desktop.
Addressbook 2.x UX
<?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