Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile selection #631

Closed
wants to merge 17 commits into from
Expand Up @@ -33,20 +33,13 @@ public function buildPaneSummary() {
*/
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
$store = $this->order->getStore();
$billing_profile = $this->order->getBillingProfile();
if (!$billing_profile) {
$profile_storage = $this->entityTypeManager->getStorage('profile');
$billing_profile = $profile_storage->create([
'type' => 'customer',
'uid' => $this->order->getCustomerId(),
]);
}

$pane_form['profile'] = [
'#type' => 'commerce_profile_select',
'#default_value' => $billing_profile,
'#default_value' => $this->order->getBillingProfile(),
'#default_country' => $store->getAddress()->getCountryCode(),
'#available_countries' => $store->getBillingCountries(),
'#profile_type' => 'customer',
'#owner_uid' => $this->order->getCustomerId(),
];

return $pane_form;
Expand All @@ -56,7 +49,8 @@ public function buildPaneForm(array $pane_form, FormStateInterface $form_state,
* {@inheritdoc}
*/
public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
$this->order->setBillingProfile($pane_form['profile']['#profile']);
$values = $form_state->getValue($pane_form['#parents']);
$this->order->setBillingProfile($values['profile']);
}

}
214 changes: 185 additions & 29 deletions modules/order/src/Element/ProfileSelect.php
Expand Up @@ -3,9 +3,11 @@
namespace Drupal\commerce_order\Element;

use Drupal\commerce\Element\CommerceElementTrait;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\RenderElement;
use Drupal\Core\Render\Element\FormElement;
use Drupal\profile\Entity\ProfileInterface;

/**
Expand All @@ -18,15 +20,17 @@
* '#default_value' => $profile,
* '#default_country' => 'FR',
* '#available_countries' => ['US', 'FR'],
* '#profile_type' => 'customer',
* '#owner_uid' => \Drupal::currentUser()->id(),
* ];
* @endcode
* To access the profile in validation or submission callbacks, use
* $form['billing_profile']['#profile']. Due to Drupal core limitations the
* profile can't be accessed via $form_state->getValue('billing_profile').
*
* @RenderElement("commerce_profile_select")
* @FormElement("commerce_profile_select")
*/
class ProfileSelect extends RenderElement {
class ProfileSelect extends FormElement {

use CommerceElementTrait;

Expand All @@ -42,7 +46,10 @@ public function getInfo() {
'#available_countries' => [],

// The profile entity operated on. Required.
'#default_value' => NULL,
'#default_value' => '_new',
'#owner_uid' => 0,
// Provide default to not break contrib which have outdated elements.
'#profile_type' => 'customer',
'#process' => [
[$class, 'attachElementSubmit'],
[$class, 'processForm'],
Expand All @@ -58,6 +65,26 @@ public function getInfo() {
];
}

/**
* {@inheritdoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if (!empty($input['profile_selection'])) {
$value = $input['profile_selection'];
}
elseif ($element['#default_value'] instanceof ProfileInterface) {
$value = $element['#default_value']->id();
}
elseif (!empty($element['#default_value'])) {
$value = $element['#default_value'];
}
else {
$value = '_new';
}

return $value;
}

/**
* Builds the element form.
*
Expand All @@ -76,30 +103,115 @@ public function getInfo() {
* The processed form element.
*/
public static function processForm(array $element, FormStateInterface $form_state, array &$complete_form) {
if (empty($element['#default_value'])) {
throw new \InvalidArgumentException('The commerce_profile_select element requires the #default_value property.');
}
elseif (isset($element['#default_value']) && !($element['#default_value'] instanceof ProfileInterface)) {
throw new \InvalidArgumentException('The commerce_profile_select #default_value property must be a profile entity.');
}
if (!is_array($element['#available_countries'])) {
throw new \InvalidArgumentException('The commerce_profile_select #available_countries property must be an array.');
}
if (empty($element['#profile_type'])) {
throw new \InvalidArgumentException('The commerce_profile_select #profile_type property must be provided.');
}
$entity_type_manager = \Drupal::entityTypeManager();
/** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
$profile_storage = $entity_type_manager->getStorage('profile');
/** @var \Drupal\profile\Entity\ProfileTypeInterface $profile_type */
$profile_type = $entity_type_manager->getStorage('profile_type')->load($element['#profile_type']);

$user_profiles = [];
/** @var \Drupal\user\UserInterface $user */
$user = $entity_type_manager->getStorage('user')->load($element['#owner_uid']);

if (!$user->isAnonymous()) {
// If the user exists, attempt to load other profiles for selection.
foreach ($profile_storage->loadMultipleByUser($user, $profile_type->id(), TRUE) as $existing_profile) {
$user_profiles[$existing_profile->id()] = $existing_profile->label();

$element['#profile'] = $element['#default_value'];
$form_display = EntityFormDisplay::collectRenderDisplay($element['#profile'], 'default');
$form_display->buildForm($element['#profile'], $element, $form_state);
if (!empty($element['address']['widget'][0])) {
$widget_element = &$element['address']['widget'][0];
// Remove the details wrapper from the address widget.
$widget_element['#type'] = 'container';
// Provide a default country.
if (!empty($element['#default_country']) && empty($widget_element['address']['#default_value']['country_code'])) {
$widget_element['address']['#default_value']['country_code'] = $element['#default_country'];
// If this is the first form build, set the element's value based on
// the user's default profile.
if (!$form_state->isProcessingInput() && $existing_profile->isDefault()) {
$element['#value'] = $existing_profile->id();
}
}
// Limit the available countries.
if (!empty($element['#available_countries'])) {
$widget_element['address']['#available_countries'] = $element['#available_countries'];
}

$id_prefix = implode('-', $element['#parents']);
$wrapper_id = Html::getUniqueId($id_prefix . '-ajax-wrapper');
$element = [
'#tree' => TRUE,
'#prefix' => '<div id="' . $wrapper_id . '">',
'#suffix' => '</div>',
// Pass the id along to other methods.
'#wrapper_id' => $wrapper_id,
'#element_mode' => $form_state->get('element_mode') ?: 'view',
] + $element;

if (!empty($user_profiles)) {
$element['profile_selection'] = [
'#title' => t('Select a profile'),
'#options' => $user_profiles + ['_new' => t('+ Create new :label', [':label' => $profile_type->label()])],
'#type' => 'select',
'#weight' => -5,
'#default_value' => $element['#value'],
'#ajax' => [
'callback' => [get_called_class(), 'ajaxRefresh'],
'wrapper' => $wrapper_id,
],
'#element_mode' => 'view',
];
}
else {
$element['profile_selection'] = [
'#type' => 'value',
'#value' => '_new',
'#element_mode' => 'create',
];
}

/** @var \Drupal\profile\Entity\ProfileInterface $element_profile */
if ($element['#value'] == '_new') {
$element_profile = $profile_storage->create([
'type' => $profile_type->id(),
'uid' => $user->id(),
]);
$element['#element_mode'] = 'create';
}
else {
$element_profile = $profile_storage->load($element['#value']);
}

// Viewing a profile.
if (!$element_profile->isNew() && $element['#element_mode'] == 'view') {
$view_builder = $entity_type_manager->getViewBuilder('profile');
$element['rendered_profile'] = $view_builder->view($element_profile, 'default');

$element['edit_button'] = [
'#type' => 'submit',
'#value' => t('Edit'),
'#limit_validation_errors' => [],
'#ajax' => [
'callback' => [get_called_class(), 'ajaxRefresh'],
'wrapper' => $wrapper_id,
],
'#submit' => [[get_called_class(), 'ajaxSubmit']],
'#name' => 'edit_profile',
'#element_mode' => 'edit',
];
}
else {
$form_display = EntityFormDisplay::collectRenderDisplay($element_profile, 'default');
$form_display->buildForm($element_profile, $element, $form_state);

// @todo Loop over all possible address fields.
if (!empty($element['address']['widget'][0])) {
$widget_element = &$element['address']['widget'][0];
// Remove the details wrapper from the address widget.
$widget_element['#type'] = 'container';
// Provide a default country.
if (!empty($element['#default_country']) && empty($widget_element['address']['#default_value']['country_code'])) {
$widget_element['address']['#default_value']['country_code'] = $element['#default_country'];
}
// Limit the available countries.
if (!empty($element['#available_countries'])) {
$widget_element['address']['#available_countries'] = $element['#available_countries'];
}
}
}

Expand All @@ -119,9 +231,29 @@ public static function processForm(array $element, FormStateInterface $form_stat
* form, as a protection against buggy behavior.
*/
public static function validateForm(array &$element, FormStateInterface $form_state) {
$form_display = EntityFormDisplay::collectRenderDisplay($element['#profile'], 'default');
$form_display->extractFormValues($element['#profile'], $element, $form_state);
$form_display->validateFormValues($element['#profile'], $element, $form_state);
$value = $form_state->getValue($element['#parents']);

$entity_type_manager = \Drupal::entityTypeManager();
/** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
$profile_storage = $entity_type_manager->getStorage('profile');
/** @var \Drupal\profile\Entity\ProfileInterface $element_profile */
if ($value['profile_selection'] == '_new') {
$element_profile = $profile_storage->create([
'type' => $element['#profile_type'],
'uid' => $element['#owner_uid'],
]);
}
else {
$element_profile = $profile_storage->load($value['profile_selection']);
}

if ($element['#element_mode'] != 'view' && $form_state->isSubmitted()) {
$form_display = EntityFormDisplay::collectRenderDisplay($element_profile, 'default');
$form_display->extractFormValues($element_profile, $element, $form_state);
$form_display->validateFormValues($element_profile, $element, $form_state);
}

$form_state->setValueForElement($element, $element_profile);
}

/**
Expand All @@ -133,9 +265,33 @@ public static function validateForm(array &$element, FormStateInterface $form_st
* The current state of the form.
*/
public static function submitForm(array &$element, FormStateInterface $form_state) {
$form_display = EntityFormDisplay::collectRenderDisplay($element['#profile'], 'default');
$form_display->extractFormValues($element['#profile'], $element, $form_state);
$element['#profile']->save();
$element_profile = $form_state->getValue($element['#parents']);

if ($element['#element_mode'] != 'view' && $form_state->isSubmitted()) {
$form_display = EntityFormDisplay::collectRenderDisplay($element_profile, 'default');
$form_display->extractFormValues($element_profile, $element, $form_state);
$element_profile->save();
}

$form_state->setValueForElement($element, $element_profile);
}

/**
* Ajax callback.
*/
public static function ajaxRefresh(array &$form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
$element = NestedArray::getValue($form, array_slice($triggering_element['#array_parents'], 0, -1));
return $element;
}

/**
* Ajax submit callback.
*/
public static function ajaxSubmit(array &$form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
$form_state->set('element_mode', $triggering_element['#element_mode']);
$form_state->setRebuild();
}

}
Expand Up @@ -2,7 +2,6 @@

namespace Drupal\commerce_order\Plugin\Field\FieldWidget;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
Expand Down Expand Up @@ -75,22 +74,14 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$order = $items[$delta]->getEntity();
$store = $order->getStore();

if (!$items[$delta]->isEmpty()) {
$profile = $items[$delta]->entity;
}
else {
$profile = $this->entityTypeManager->getStorage('profile')->create([
'type' => 'customer',
'uid' => $order->getCustomerId(),
]);
}

$element['#type'] = 'fieldset';
$element['profile'] = [
'#type' => 'commerce_profile_select',
'#default_value' => $profile,
'#default_value' => $profile = $items[$delta]->entity,
'#default_country' => $store->getAddress()->getCountryCode(),
'#available_countries' => $store->getBillingCountries(),
'#profile_type' => 'customer',
'#owner_uid' => $order->getCustomerId(),
];
// Workaround for massageFormValues() not getting $element.
$element['array_parents'] = [
Expand All @@ -107,8 +98,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
$new_values = [];
foreach ($values as $delta => $value) {
$element = NestedArray::getValue($form, $value['array_parents']);
$new_values[$delta]['entity'] = $element['profile']['#profile'];
$new_values[$delta]['entity'] = $value['profile'];
}
return $new_values;
}
Expand Down
@@ -0,0 +1,9 @@
commerce_order_test.profile_select_form:
path: '/commerce_order_test/profile_select_form'
defaults:
_form: '\Drupal\commerce_order_test\Form\ProfileSelectTestForm'
_title: 'Profile select test form'
requirements:
_access: 'TRUE'
options:
no_cache: TRUE