diff --git a/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
index e53b0f92a..0d7a71852 100644
--- a/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
+++ b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
@@ -33,20 +33,14 @@ 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,
+ '#title' => $this->t('Select an address'),
+ '#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;
@@ -56,7 +50,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']);
}
}
diff --git a/modules/order/src/Element/ProfileSelect.php b/modules/order/src/Element/ProfileSelect.php
index 6aacf1c64..1d4d261aa 100644
--- a/modules/order/src/Element/ProfileSelect.php
+++ b/modules/order/src/Element/ProfileSelect.php
@@ -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;
/**
@@ -18,15 +20,14 @@
* '#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;
@@ -40,9 +41,14 @@ public function getInfo() {
'#default_country' => NULL,
// A list of country codes. If empty, all countries will be available.
'#available_countries' => [],
+ '#title' => t('Select a profile'),
+ '#create_title' => t('+ Enter a new profile'),
// 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'],
@@ -59,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.
*
* @param array $element
@@ -76,36 +102,116 @@ 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.');
}
- // Make sure that the specified default country is available.
- if (!empty($element['#default_country']) && !empty($element['#available_countries'])) {
- if (!in_array($element['#default_country'], $element['#available_countries'])) {
- $element['#default_country'] = NULL;
- }
+
+ 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']);
- $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 (!$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();
+
+ // 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' => '
',
+ '#suffix' => '
',
+ // 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' => $element['#title'],
+ '#options' => $user_profiles + ['_new' => $element['#create_title']],
+ '#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'];
+ }
}
}
@@ -125,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);
}
/**
@@ -139,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();
}
}
diff --git a/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php b/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php
index 2d0987c94..32e00a0c7 100644
--- a/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php
+++ b/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php
@@ -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;
@@ -75,22 +74,16 @@ 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,
+ '#title' => $this->t('Select an address'),
+ '#create_title' => t('+ Enter a new address'),
+ '#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'] = [
@@ -107,8 +100,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;
}
diff --git a/modules/order/tests/modules/commerce_order_test/commerce_order_test.routing.yml b/modules/order/tests/modules/commerce_order_test/commerce_order_test.routing.yml
new file mode 100644
index 000000000..7d62a7662
--- /dev/null
+++ b/modules/order/tests/modules/commerce_order_test/commerce_order_test.routing.yml
@@ -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
diff --git a/modules/order/tests/modules/commerce_order_test/src/Form/ProfileSelectTestForm.php b/modules/order/tests/modules/commerce_order_test/src/Form/ProfileSelectTestForm.php
new file mode 100644
index 000000000..30b0555f8
--- /dev/null
+++ b/modules/order/tests/modules/commerce_order_test/src/Form/ProfileSelectTestForm.php
@@ -0,0 +1,45 @@
+ 'commerce_profile_select',
+ '#title' => $this->t('Profile'),
+ '#default_value' => NULL,
+ '#profile_type' => 'customer',
+ '#owner_uid' => \Drupal::currentUser()->id(),
+ '#available_countries' => ['HU', 'FR', 'US', 'RS', 'DE'],
+ ];
+ $form['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Submit'),
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $profile = $form_state->getValue('profile');
+ drupal_set_message($this->t('Profile selected: :label', [':label' => $profile->label()]));
+ }
+
+}
diff --git a/modules/order/tests/src/FunctionalJavascript/ProfileSelectTest.php b/modules/order/tests/src/FunctionalJavascript/ProfileSelectTest.php
new file mode 100644
index 000000000..63bfeff6d
--- /dev/null
+++ b/modules/order/tests/src/FunctionalJavascript/ProfileSelectTest.php
@@ -0,0 +1,303 @@
+ 'HU',
+ 'given_name' => 'Gustav',
+ 'family_name' => 'Mahler',
+ 'address_line1' => 'Teréz körút 7',
+ 'locality' => 'Budapest',
+ 'postal_code' => '1067',
+ ];
+
+ /**
+ * Profile address values.
+ *
+ * @var array
+ */
+ protected $address2 = [
+ 'country_code' => 'DE',
+ 'given_name' => 'Johann Sebastian',
+ 'family_name' => 'Bach',
+ 'address_line1' => 'Thomaskirchhof 15',
+ 'locality' => 'Leipzig',
+ 'postal_code' => '04109',
+ ];
+
+ /**
+ * @var \Drupal\profile\ProfileStorageInterface
+ */
+ protected $profileStorage;
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = [
+ 'commerce_order_test',
+ ];
+
+ /**
+ * @inheritDoc
+ */
+ protected function setUp() {
+ parent::setUp();
+ $this->profileStorage = $this->container->get('entity_type.manager')->getStorage('profile');
+ }
+
+ /**
+ * Tests the profile select form element for anonymous user.
+ */
+ public function testAnonymous() {
+ $this->drupalLogout();
+ $address_fields = $this->address1;
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $this->assertSession()->statusCodeEquals(200);
+
+ $this->assertSession()->fieldNotExists('Select a profile');
+ $this->getSession()->getPage()->fillField('Country', $address_fields['country_code']);
+ $this->waitForAjaxToFinish();
+
+ $edit = [];
+ foreach ($address_fields as $key => $value) {
+ if ($key == 'country_code') {
+ continue;
+ }
+ $edit['profile[address][0][address][' . $key . ']'] = $value;
+ }
+
+ $this->submitForm($edit, 'Submit');
+
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile */
+ $profile = $this->profileStorage->load(1);
+
+ $this->assertSession()->responseContains(new FormattableMarkup('Profile selected: :label', [':label' => $profile->label()]));
+
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $profile->get('address')->first();
+ $this->assertEquals($address_fields['country_code'], $address->getCountryCode());
+ $this->assertEquals($address_fields['given_name'], $address->getGivenName());
+ $this->assertEquals($address_fields['family_name'], $address->getFamilyName());
+ $this->assertEquals($address_fields['address_line1'], $address->getAddressLine1());
+ $this->assertEquals($address_fields['locality'], $address->getLocality());
+ $this->assertEquals($address_fields['postal_code'], $address->getPostalCode());
+ }
+
+ /**
+ * Tests the profile select form element for anonymous user.
+ */
+ public function testAuthenticatedNoExistingProfiles() {
+ $account = $this->createUser();
+ $this->drupalLogin($account);
+
+ $address_fields = $this->address1;
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $this->assertSession()->statusCodeEquals(200);
+
+ $this->assertSession()->fieldNotExists('Select a profile');
+ $this->getSession()->getPage()->fillField('Country', $address_fields['country_code']);
+ $this->waitForAjaxToFinish();
+
+ $edit = [];
+ foreach ($address_fields as $key => $value) {
+ if ($key == 'country_code') {
+ continue;
+ }
+ $edit['profile[address][0][address][' . $key . ']'] = $value;
+ }
+
+ $this->submitForm($edit, 'Submit');
+
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile */
+ $profile = $this->profileStorage->load(1);
+
+ $this->assertSession()->responseContains(new FormattableMarkup('Profile selected: :label', [':label' => $profile->label()]));
+
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $profile->get('address')->first();
+ $this->assertEquals($address_fields['country_code'], $address->getCountryCode());
+ $this->assertEquals($address_fields['given_name'], $address->getGivenName());
+ $this->assertEquals($address_fields['family_name'], $address->getFamilyName());
+ $this->assertEquals($address_fields['address_line1'], $address->getAddressLine1());
+ $this->assertEquals($address_fields['locality'], $address->getLocality());
+ $this->assertEquals($address_fields['postal_code'], $address->getPostalCode());
+ }
+
+ /**
+ * Tests the profile select form element for authenticated user.
+ */
+ public function testProfileSelectAuthenticated() {
+ $account = $this->createUser();
+
+ $profile_storage = $this->container->get('entity_type.manager')->getStorage('profile');
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile_address1 */
+ $profile_address1 = $profile_storage->create([
+ 'type' => 'customer',
+ 'uid' => $account->id(),
+ 'address' => $this->address1,
+ ]);
+ $profile_address1->save();
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile_address2 */
+ $profile_address2 = $profile_storage->create([
+ 'type' => 'customer',
+ 'uid' => $account->id(),
+ 'address' => $this->address2,
+ ]);
+ $profile_address2->setDefault(TRUE);
+ $profile_address2->save();
+
+ $this->drupalLogin($account);
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldExists('Select a profile');
+ // The last created profile should be selected by default.
+ $this->assertSession()->pageTextContains($this->address2['locality']);
+
+ $this->getSession()->getPage()->fillField('Select a profile', $profile_address1->id());
+ $this->waitForAjaxToFinish();
+ $this->assertSession()->pageTextContains($this->address1['locality']);
+ $this->submitForm([], 'Submit');
+ $this->assertSession()->responseContains(new FormattableMarkup('Profile selected: :label', [':label' => $profile_address1->label()]));
+
+ $profile_storage->resetCache([$profile_address1->id()]);
+ $profile_address1 = $profile_storage->load($profile_address1->id());
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $profile_address1->get('address')->first();
+ // Assert that field values have not changed.
+ $this->assertEquals($this->address1['country_code'], $address->getCountryCode());
+ $this->assertEquals($this->address1['given_name'], $address->getGivenName());
+ $this->assertEquals($this->address1['family_name'], $address->getFamilyName());
+ $this->assertEquals($this->address1['address_line1'], $address->getAddressLine1());
+ $this->assertEquals($this->address1['locality'], $address->getLocality());
+ $this->assertEquals($this->address1['postal_code'], $address->getPostalCode());
+ }
+
+ /**
+ * Tests the profile select form element for authenticated user.
+ */
+ public function testProfileSelectAuthenticatedCreateNew() {
+ $account = $this->createUser();
+ $address_fields = $this->address2;
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile_address1 */
+ $profile_address1 = $this->profileStorage->create([
+ 'type' => 'customer',
+ 'uid' => $account->id(),
+ 'address' => $this->address1,
+ ]);
+ $profile_address1->save();
+
+ $this->drupalLogin($account);
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldExists('Select a profile');
+ // The last created profile should be selected by default.
+ $this->assertSession()->pageTextContains($this->address1['locality']);
+
+ $this->getSession()->getPage()->fillField('Select a profile', '_new');
+ $this->waitForAjaxToFinish();
+ $this->getSession()->getPage()->fillField('Country', $address_fields['country_code']);
+ $this->waitForAjaxToFinish();
+ $edit = [];
+ foreach ($address_fields as $key => $value) {
+ if ($key == 'country_code') {
+ continue;
+ }
+ $edit['profile[address][0][address][' . $key . ']'] = $value;
+ }
+
+ $this->submitForm($edit, 'Submit');
+
+ $new_profile = $this->profileStorage->load(2);
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $new_profile->get('address')->first();
+
+ $this->assertSession()->responseContains(new FormattableMarkup('Profile selected: :label', [':label' => $new_profile->label()]));
+ // Assert that field values have not changed.
+ $this->assertEquals($this->address2['country_code'], $address->getCountryCode());
+ $this->assertEquals($this->address2['given_name'], $address->getGivenName());
+ $this->assertEquals($this->address2['family_name'], $address->getFamilyName());
+ $this->assertEquals($this->address2['address_line1'], $address->getAddressLine1());
+ $this->assertEquals($this->address2['locality'], $address->getLocality());
+ $this->assertEquals($this->address2['postal_code'], $address->getPostalCode());
+ }
+
+ /**
+ * Tests the profile select form element for authenticated user.
+ *
+ * @group debug
+ */
+ public function testProfileSelectAuthenticatedEdit() {
+ $account = $this->createUser();
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile_address1 */
+ $profile_address1 = $this->profileStorage->create([
+ 'type' => 'customer',
+ 'uid' => $account->id(),
+ 'address' => $this->address1,
+ ]);
+ $profile_address1->save();
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile_address2 */
+ $profile_address2 = $this->profileStorage->create([
+ 'type' => 'customer',
+ 'uid' => $account->id(),
+ 'address' => $this->address2,
+ ]);
+ $profile_address2->setDefault(TRUE);
+ $profile_address2->save();
+
+ $this->drupalLogin($account);
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $this->assertSession()->statusCodeEquals(200);
+
+ // Edit a profile.
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $this->assertSession()->statusCodeEquals(200);
+ $this->assertSession()->fieldExists('Select a profile');
+ // The last created profile should be selected by default.
+ $this->assertSession()->pageTextContains($this->address2['locality']);
+ $this->getSession()->getPage()->pressButton('Edit');
+ $this->waitForAjaxToFinish();
+
+ foreach ($this->address2 as $key => $value) {
+ $this->assertSession()->fieldValueEquals('profile[address][0][address][' . $key . ']', $value);
+ }
+ $this->getSession()->getPage()->fillField('Street address', 'Andrássy út 22');
+ $this->submitForm([], 'Submit');
+
+ $this->profileStorage->resetCache([$profile_address2->id()]);
+ $profile_address2 = $this->profileStorage->load($profile_address2->id());
+
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $profile_address2->get('address')->first();
+
+ $this->assertSession()->responseContains(new FormattableMarkup('Profile selected: :label', [':label' => $profile_address2->label()]));
+ // Assert that field values have not changed.
+ $this->assertEquals($this->address2['country_code'], $address->getCountryCode());
+ $this->assertEquals($this->address2['given_name'], $address->getGivenName());
+ $this->assertEquals($this->address2['family_name'], $address->getFamilyName());
+ $this->assertEquals('Andrássy út 22', $address->getAddressLine1());
+ $this->assertEquals($this->address2['locality'], $address->getLocality());
+ $this->assertEquals($this->address2['postal_code'], $address->getPostalCode());
+ }
+
+}
diff --git a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
index 2b2e1b0eb..67e2d5eb7 100644
--- a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
+++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
@@ -135,19 +135,15 @@ public function buildPaneForm(array $pane_form, FormStateInterface $form_state,
}
else {
$store = $this->order->getStore();
- $billing_profile = $this->order->getBillingProfile();
- if (!$billing_profile) {
- $billing_profile = $this->entityTypeManager->getStorage('profile')->create([
- 'uid' => $this->order->getCustomerId(),
- 'type' => 'customer',
- ]);
- }
-
$pane_form['billing_information'] = [
'#type' => 'commerce_profile_select',
- '#default_value' => $billing_profile,
+ '#title' => $this->t('Select an address'),
+ '#create_title' => t('+ Enter a new address'),
+ '#default_value' => $this->order->getBillingProfile(),
'#default_country' => $store->getAddress()->getCountryCode(),
'#available_countries' => $store->getBillingCountries(),
+ '#profile_type' => 'customer',
+ '#owner_uid' => $this->order->getCustomerId(),
];
}
@@ -347,7 +343,7 @@ public function submitPaneForm(array &$pane_form, FormStateInterface $form_state
else {
$this->order->set('payment_gateway', $payment_gateway);
$this->order->set('payment_method', NULL);
- $this->order->setBillingProfile($pane_form['billing_information']['#profile']);
+ $this->order->setBillingProfile($values['billing_information']);
}
}
diff --git a/modules/payment/src/PluginForm/PaymentMethodAddForm.php b/modules/payment/src/PluginForm/PaymentMethodAddForm.php
index 0a3ece9eb..566b8bafc 100644
--- a/modules/payment/src/PluginForm/PaymentMethodAddForm.php
+++ b/modules/payment/src/PluginForm/PaymentMethodAddForm.php
@@ -6,7 +6,6 @@
use Drupal\commerce_payment\Exception\DeclineException;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\profile\Entity\Profile;
class PaymentMethodAddForm extends PaymentGatewayFormBase {
@@ -54,11 +53,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
$payment_method = $this->entity;
- /** @var \Drupal\profile\Entity\ProfileInterface $billing_profile */
- $billing_profile = Profile::create([
- 'type' => 'customer',
- 'uid' => $payment_method->getOwnerId(),
- ]);
if ($order = $this->routeMatch->getParameter('commerce_order')) {
$store = $order->getStore();
}
@@ -71,9 +65,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
$form['billing_information'] = [
'#parents' => array_merge($form['#parents'], ['billing_information']),
'#type' => 'commerce_profile_select',
- '#default_value' => $billing_profile,
+ '#title' => t('Select an address'),
+ '#create_title' => t('+ Enter a new address'),
'#default_country' => $store ? $store->getAddress()->getCountryCode() : NULL,
'#available_countries' => $store ? $store->getBillingCountries() : [],
+ '#profile_type' => 'customer',
+ '#owner_uid' => $payment_method->getOwnerId(),
];
return $form;
@@ -109,9 +106,9 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
}
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
$payment_method = $this->entity;
- $payment_method->setBillingProfile($form['billing_information']['#profile']);
$values = $form_state->getValue($form['#parents']);
+ $payment_method->setBillingProfile($values['billing_information']);
/** @var \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface $payment_gateway_plugin */
$payment_gateway_plugin = $this->plugin;
// The payment method form is customer facing. For security reasons
diff --git a/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php b/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php
index 31221e0d9..90cd64556 100644
--- a/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php
+++ b/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php
@@ -246,7 +246,8 @@ public function testPaymentInformation() {
$this->drupalGet('checkout/1');
$radio_button = $page->findField('Example');
$this->assertNull($radio_button);
- $this->assertSession()->fieldExists('payment_information[billing_information][address][0][address][postal_code]');
+ $this->assertSession()->fieldExists('Select a profile');
+ $this->assertSession()->pageTextContains('Pabst Blue Ribbon Dr');
}
/**
@@ -379,7 +380,8 @@ public function testCheckoutWithOffsiteRedirectPost() {
$radio_button = $this->getSession()->getPage()->findField('Example');
$radio_button->click();
$this->waitForAjaxToFinish();
-
+ $this->getSession()->getPage()->selectFieldOption('Select a profile', '_new');
+ $this->waitForAjaxToFinish();
$this->submitForm([
'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
@@ -426,7 +428,8 @@ public function testCheckoutWithOffsiteRedirectGet() {
$this->drupalGet($this->product->toUrl()->toString());
$this->submitForm([], 'Add to cart');
$this->drupalGet('checkout/1');
-
+ $this->getSession()->getPage()->selectFieldOption('Select a profile', '_new');
+ $this->waitForAjaxToFinish();
$this->submitForm([
'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
diff --git a/tests/src/FunctionalJavascript/JavascriptTestTrait.php b/tests/src/FunctionalJavascript/JavascriptTestTrait.php
index 5f12b9231..ffca7de27 100644
--- a/tests/src/FunctionalJavascript/JavascriptTestTrait.php
+++ b/tests/src/FunctionalJavascript/JavascriptTestTrait.php
@@ -89,4 +89,27 @@ protected function createScreenshot($set_background_color = TRUE) {
$this->htmlOutputCounter++;
}
+ /**
+ * Creates a screenshot.
+ *
+ * @param bool $set_background_color
+ * (optional) By default this method will set the background color to white.
+ * Set to FALSE to override this behaviour.
+ *
+ * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
+ * When operation not supported by the driver.
+ * @throws \Behat\Mink\Exception\DriverException
+ * When the operation cannot be done.
+ */
+ protected function createScreenshot($set_background_color = TRUE) {
+ $jpg_output_filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.jpg';
+ $session = $this->getSession();
+ if ($set_background_color) {
+ $session->executeScript("document.body.style.backgroundColor = 'white';");
+ }
+ $image = $session->getScreenshot();
+ file_put_contents($this->htmlOutputDirectory . '/' . $jpg_output_filename, $image);
+ $this->htmlOutputCounter++;
+ }
+
}