diff --git a/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
index 0d7a7185..d7350a94 100644
--- a/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
+++ b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
@@ -14,7 +14,7 @@ use Drupal\Core\Form\FormStateInterface;
* wrapper_element = "fieldset",
* )
*/
-class BillingInformation extends CheckoutPaneBase implements CheckoutPaneInterface {
+class BillingInformation extends BillingInformationPaneBase {
/**
* {@inheritdoc}
@@ -41,7 +41,7 @@ class BillingInformation extends CheckoutPaneBase implements CheckoutPaneInterfa
'#available_countries' => $store->getBillingCountries(),
'#profile_type' => 'customer',
'#owner_uid' => $this->order->getCustomerId(),
- ];
+ ] + $this->getProfileSelectOptions();
return $pane_form;
}
diff --git a/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformationPaneBase.php b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformationPaneBase.php
new file mode 100644
index 0000000..9c558d4
--- /dev/null
+++ b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformationPaneBase.php
@@ -0,0 +1,111 @@
+ FALSE,
+ 'reuse_profile_label' => 'My shipping address is the same as my billing address.',
+ 'reuse_profile_default' => FALSE,
+ ] + parent::defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationSummary() {
+ if (!empty($this->configuration['reuse_profile'])) {
+ $summary = $this->t('Allow reuse of shipping profile: Yes') . '
';
+ $summary .= $this->t('Reuse shipping profile label: @label', [
+ '@label' => $this->configuration['reuse_profile_label']
+ ]) . '
';
+ $summary .= $this->t('Reuse shipping profile by default: @default', [
+ '@default' => ($this->configuration['reuse_profile_default'])
+ ? $this->t('Yes')
+ : $this->t('No')
+ ]);
+ }
+ else {
+ $summary = $this->t('Allow reuse of shipping profile: No');
+ }
+
+ return $summary;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form = parent::buildConfigurationForm($form, $form_state);
+ $visible_state = [['.js-reuse-shipping-profile' => ['checked' => TRUE]]];
+
+ $form['reuse_profile'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Allow reuse of shipping profile for billing'),
+ '#default_value' => $this->configuration['reuse_profile'],
+ ];
+ $form['reuse_profile']['#attributes']['class'][] = 'js-reuse-shipping-profile';
+ $form['reuse_profile_label'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Reuse shipping profile label'),
+ '#default_value' => $this->configuration['reuse_profile_label'],
+ '#states' => [
+ 'visible' => $visible_state,
+ ]
+ ];
+ $form['reuse_profile_default'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Reuse shipping profile by default'),
+ '#default_value' => $this->configuration['reuse_profile_default'],
+ '#states' => [
+ 'visible' => $visible_state,
+ ]
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ parent::submitConfigurationForm($form, $form_state);
+
+ if (!$form_state->getErrors()) {
+ $values = $form_state->getValue($form['#parents']);
+ $this->configuration['reuse_profile'] = !empty($values['reuse_profile']);
+ $this->configuration['reuse_profile_label'] = $values['reuse_profile_label'];
+ $this->configuration['reuse_profile_default'] = !empty($values['reuse_profile_default']);
+ }
+ }
+
+ protected function getProfileSelectOptions() {
+ $options = [];
+
+ if (!empty($this->configuration['reuse_profile'])) {
+ $reuse_label = !empty($this->configuration['reuse_profile_label'])
+ ? $this->configuration['reuse_profile_label']
+ : NULL;
+ $reuse_default = isset($this->configuration['reuse_profile_default'])
+ ? $this->configuration['reuse_profile_default']
+ : FALSE;
+
+ $options = [
+ '#reuse_profile_label' => $reuse_label,
+ '#reuse_profile_source' => 'commerce_order_get_shipping_profile',
+ '#reuse_profile_default' => $reuse_default
+ ];
+ }
+
+ return $options;
+ }
+}
diff --git a/modules/order/commerce_order.module b/modules/order/commerce_order.module
index 195092db..6ff6505a 100644
--- a/modules/order/commerce_order.module
+++ b/modules/order/commerce_order.module
@@ -194,3 +194,41 @@ function commerce_order_mail($key, &$message, $params) {
$message['subject'] = $params['subject'];
$message['body'][] = $params['body'];
}
+
+/**
+ * Determine the current shipping profile for reuse as the billing profile.
+ *
+ * @param array $element
+ * The element reusing the shipping profile.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The form state.
+ * @param array $complete_form
+ * The complete checkout form.
+ *
+ * @return \Drupal\profile\Entity\ProfileInterface|NULL
+ * The shipping profile, or NULL if none can be found.
+ */
+function commerce_order_get_shipping_profile(array $element, FormStateInterface $form_state, array $complete_form) {
+ $profile = NULL;
+
+ $storage = $form_state->getStorage();
+ if (isset($storage['pane_shipping_information[shipping_profile]']['profile'])) {
+ $profile = $storage['pane_shipping_information[shipping_profile]']['profile'];
+ } elseif (isset($complete_form['shipping_information']['shipping_profile']['#profile'])) {
+ $profile = $complete_form['shipping_information']['shipping_profile']['#profile'];
+ } else {
+ /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
+ $order = \Drupal::routeMatch()->getParameter('commerce_order');
+
+ if ($order instanceof \Drupal\commerce_order\Entity\OrderInterface) {
+ if (!$order->get('shipments')->isEmpty()) {
+ /** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
+ $shipment = $order->get('shipments')->entity;
+ $profile = $shipment->getShippingProfile();
+ }
+ }
+ }
+
+ return $profile;
+}
+
diff --git a/modules/order/src/Element/ProfileSelect.php b/modules/order/src/Element/ProfileSelect.php
index 2ed0ff81..ba7c4d59 100644
--- a/modules/order/src/Element/ProfileSelect.php
+++ b/modules/order/src/Element/ProfileSelect.php
@@ -7,8 +7,10 @@ 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;
use Drupal\Core\Render\Element\FormElement;
use Drupal\profile\Entity\ProfileInterface;
+use Symfony\Component\HttpFoundation\Request;
/**
* Provides a form element for selecting a customer profile.
@@ -22,6 +24,9 @@ use Drupal\profile\Entity\ProfileInterface;
* '#available_countries' => ['US', 'FR'],
* '#profile_type' => 'customer',
* '#owner_uid' => \Drupal::currentUser()->id(),
+ * '#reuse_profile_label' => $this->t('My billing address is the same as my shipping address.'),
+ * '#reuse_profile_source' => 'commerce_shipping_get_shipping_profile',
+ * '#reuse_profile_default' => FALSE,
* ];
* @endcode
*
@@ -41,6 +46,13 @@ class ProfileSelect extends FormElement {
'#default_country' => NULL,
// A list of country codes. If empty, all countries will be available.
'#available_countries' => [],
+ // The label for the reuse profile checkbox. If empty, checkbox is hidden.
+ '#reuse_profile_label' => NULL,
+ // The function to call to return the profile to reuse.
+ '#reuse_profile_source' => NULL,
+ // Whether the reuse checkbox should be checked by default.
+ '#reuse_profile_default' => FALSE,
+
'#title' => t('Select a profile'),
'#create_title' => t('+ Enter a new profile'),
@@ -167,6 +179,18 @@ class ProfileSelect extends FormElement {
];
}
+ if (empty($element['#name'])) {
+ list($name) = explode('--', $element['#id']);
+ $element['#name'] = 'profile-select--' . $name;
+ }
+
+ $storage = $form_state->getStorage();
+ $reuse_profile = (isset($storage['pane_' . $element['#name']]['reuse_profile']))
+ ? $storage['pane_' . $element['#name']]['reuse_profile']
+ : $element['#reuse_profile_default'];
+ $storage['pane_' . $element['#name']]['reuse_profile'] = $reuse_profile;
+ $form_state->setStorage($storage);
+
/** @var \Drupal\profile\Entity\ProfileInterface $element_profile */
if ($element['#value'] == '_new') {
$element_profile = $profile_storage->create([
@@ -219,6 +243,29 @@ class ProfileSelect extends FormElement {
}
}
+ $called_class = get_called_class();
+ $reuse_enabled = (!empty($element['#reuse_profile_label']) && !empty($element['#reuse_profile_source']));
+ if ($reuse_enabled) {
+ $element['reuse_profile'] = [
+ '#title' => $element['#reuse_profile_label'],
+ '#type' => 'checkbox',
+ '#weight' => -5,
+ '#default_value' => $reuse_profile,
+ '#ajax' => [
+ 'callback' => [$called_class, 'reuseProfileAjax'],
+ 'wrapper' => $wrapper_id,
+ ],
+ '#element_validate' => [[$called_class, 'reuseProfileValidate']]
+ ];
+ }
+ if ($reuse_profile) {
+ foreach (Element::children($element) as $key) {
+ if (!in_array($key, ['reuse_profile'])) {
+ $element[$key]['#access'] = FALSE;
+ }
+ }
+ }
+
return $element;
}
@@ -282,10 +329,16 @@ class ProfileSelect extends FormElement {
$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);
+ $pane_id = $element['#name'];
+ $storage = $form_state->getStorage();
+ if (!isset($storage['pane_' . $pane_id]['reuse_profile']) || !$storage['pane_' . $pane_id]['reuse_profile']) {
+
+ 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);
@@ -311,20 +364,37 @@ class ProfileSelect extends FormElement {
* The current state of the form.
*/
public static function submitForm(array &$element, FormStateInterface $form_state) {
- /** @var \Drupal\profile\Entity\ProfileInterface $element_profile */
- $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);
- if ($element_profile->isNew()) {
- $element_profile->save();
+ $pane_id = $element['#name'];
+ $storage = $form_state->getStorage();
+ if (isset($storage['pane_' . $pane_id]['reuse_profile']) && $storage['pane_' . $pane_id]['reuse_profile']) {
+ if (is_numeric($element['#reuse_profile_source'])) {
+ // Load profile by ID
+ $element_profile = \Drupal::entityTypeManager()
+ ->getStorage('profile')
+ ->load($element['#reuse_profile_source']);
+ }
+ else {
+ // Load profile from a callback
+ $element_profile = call_user_func($element['#reuse_profile_source'], $element, $form_state, $form_state->getCompleteForm());
+ }
+ } else {
+ /** @var \Drupal\profile\Entity\ProfileInterface $element_profile */
+ $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);
+ if ($element_profile->isNew()) {
+ $element_profile->save();
+ }
}
-
- $element['#default_value'] = $element_profile;
- $element['#value'] = $element_profile->id();
}
+ $element['#default_value'] = $element_profile;
+ $element['#profile'] = $element_profile;
+ $element['#value'] = $element_profile->id();
+
$form_state->setValueForElement($element, $element_profile);
}
@@ -349,6 +419,44 @@ class ProfileSelect extends FormElement {
$form_state->setRebuild();
}
+ /**
+ * Reuse profile AJAX callback.
+ *
+ * @param array $form
+ * The complete form array.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The Request object.
+ *
+ * @return array
+ * The form element replace the wrapper with.
+ */
+ public static function reuseProfileAjax(array &$form, FormStateInterface $form_state, Request $request) {
+ $triggering_element = $form_state->getTriggeringElement();
+ $array_parents = $triggering_element['#array_parents'];
+ array_pop($array_parents);
+ return NestedArray::getValue($form, $array_parents);
+ }
+ /**
+ * The #element_validate callback for the reuse profile checkbox.
+ *
+ * @param array $element
+ * The form element.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ */
+ public static function reuseProfileValidate(array $element, FormStateInterface $form_state) {
+ $form = $form_state->getCompleteForm();
+ $profile_element_parents = $element['#parents'];
+ array_pop($profile_element_parents);
+ $profile_element = NestedArray::getValue($form, $profile_element_parents);
+ $pane_id = $profile_element['#name'];
+ $storage = $form_state->getStorage();
+ $storage['pane_' . $pane_id]['reuse_profile'] = $element['#value'];
+ $form_state->setStorage($storage);
+ }
+
/**
* Check if the address arrays are equal or not. In the equality comparition,
* empty string is regarded as the same with NULL.
diff --git a/modules/payment/src/Element/PaymentGatewayForm.php b/modules/payment/src/Element/PaymentGatewayForm.php
index ae43de8c..db84f1f4 100644
--- a/modules/payment/src/Element/PaymentGatewayForm.php
+++ b/modules/payment/src/Element/PaymentGatewayForm.php
@@ -21,6 +21,8 @@ use Drupal\Core\Render\Element\RenderElement;
* // On submit, the payment method will be created remotely, and the
* // entity updated, for access via $form_state->getValue('payment_method')
* '#default_value' => $payment_method,
+ * // Additional options to pass into the commerce_profile_select element.
+ * '#profile_select_options' => [],
* ];
* @endcode
*
diff --git a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
index 2c47e8b4..36aaa680 100644
--- a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
+++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
@@ -3,7 +3,7 @@
namespace Drupal\commerce_payment\Plugin\Commerce\CheckoutPane;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
-use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
+use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\BillingInformationPaneBase;
use Drupal\commerce_payment\PaymentOption;
use Drupal\commerce_payment\PaymentOptionsBuilderInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface;
@@ -26,7 +26,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* wrapper_element = "fieldset",
* )
*/
-class PaymentInformation extends CheckoutPaneBase {
+class PaymentInformation extends BillingInformationPaneBase {
/**
* The current user.
@@ -198,13 +198,14 @@ class PaymentInformation extends CheckoutPaneBase {
// Store the options for submitPaneForm().
$pane_form['#payment_options'] = $options;
+ $profile_select_options = $this->getProfileSelectOptions();
$default_payment_gateway_id = $default_option->getPaymentGatewayId();
$payment_gateway = $payment_gateways[$default_payment_gateway_id];
if ($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) {
- $pane_form = $this->buildPaymentMethodForm($pane_form, $form_state, $default_option);
+ $pane_form = $this->buildPaymentMethodForm($pane_form, $form_state, $default_option, $profile_select_options);
}
else {
- $pane_form = $this->buildBillingProfileForm($pane_form, $form_state);
+ $pane_form = $this->buildBillingProfileForm($pane_form, $form_state, $profile_select_options);
}
return $pane_form;
@@ -220,10 +221,12 @@ class PaymentInformation extends CheckoutPaneBase {
* @param \Drupal\commerce_payment\PaymentOption $payment_option
* The payment option.
*
+ * @param array $profile_select_options
+ *
* @return array
* The modified pane form.
*/
- protected function buildPaymentMethodForm(array $pane_form, FormStateInterface $form_state, PaymentOption $payment_option) {
+ protected function buildPaymentMethodForm(array $pane_form, FormStateInterface $form_state, PaymentOption $payment_option, array $profile_select_options) {
if ($payment_option->getPaymentMethodId() && !$payment_option->getPaymentMethodTypeId()) {
// Editing payment methods at checkout is not supported.
return $pane_form;
@@ -241,6 +244,7 @@ class PaymentInformation extends CheckoutPaneBase {
$pane_form['add_payment_method'] = [
'#type' => 'commerce_payment_gateway_form',
'#operation' => 'add-payment-method',
+ '#profile_select_options' => $profile_select_options,
'#default_value' => $payment_method,
];
@@ -252,13 +256,14 @@ class PaymentInformation extends CheckoutPaneBase {
*
* @param array $pane_form
* The pane form.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The form state of the parent form.
+ * @param FormStateInterface $form_state
+ * @param array $profile_select_options
+ * The profile select options.
*
* @return array
* The modified pane form.
*/
- protected function buildBillingProfileForm(array $pane_form, FormStateInterface $form_state) {
+ protected function buildBillingProfileForm(array $pane_form, FormStateInterface $form_state, $profile_select_options) {
$store = $this->order->getStore();
$pane_form['billing_information'] = [
@@ -270,7 +275,7 @@ class PaymentInformation extends CheckoutPaneBase {
'#available_countries' => $store->getBillingCountries(),
'#profile_type' => 'customer',
'#owner_uid' => $this->order->getCustomerId(),
- ];
+ ] + $profile_select_options;
return $pane_form;
}
diff --git a/modules/payment/src/PluginForm/PaymentMethodAddForm.php b/modules/payment/src/PluginForm/PaymentMethodAddForm.php
index 76b8cf78..4efb92e9 100644
--- a/modules/payment/src/PluginForm/PaymentMethodAddForm.php
+++ b/modules/payment/src/PluginForm/PaymentMethodAddForm.php
@@ -72,7 +72,7 @@ class PaymentMethodAddForm extends PaymentGatewayFormBase {
'#available_countries' => $store ? $store->getBillingCountries() : [],
'#profile_type' => 'customer',
'#owner_uid' => $payment_method->getOwnerId(),
- ];
+ ] + $form['#profile_select_options'];
return $form;
}