334 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			334 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
| <?php
 | |
| 
 | |
| namespace Drupal\commerce_shipping;
 | |
| 
 | |
| use Drupal\Component\Utility\NestedArray;
 | |
| use Drupal\Core\Entity\Entity\EntityFormDisplay;
 | |
| use Drupal\Core\Entity\EntityFormInterface;
 | |
| use Drupal\Core\Form\BaseFormIdInterface;
 | |
| use Drupal\Core\Form\FormStateInterface;
 | |
| use Drupal\Core\Render\Element;
 | |
| use Drupal\Core\Session\AccountInterface;
 | |
| use Drupal\Core\StringTranslation\StringTranslationTrait;
 | |
| use Drupal\profile\Entity\ProfileInterface;
 | |
| 
 | |
| /**
 | |
|  * Default implementation of profile field copying ("Billing same as shipping").
 | |
|  *
 | |
|  * Supports copying at checkout and on the admin order edit page.
 | |
|  * At checkout the shipping and billing panes can be on the same step, or
 | |
|  * on separate steps, assuming that the shipping pane comes first.
 | |
|  *
 | |
|  * Note that a billing profile can either be populated from the shipping
 | |
|  * profile or from the address book, never both at the same time.
 | |
|  * When profile field copying is enabled, the address book elements are hidden
 | |
|  * and the address book is not populated with the billing information.
 | |
|  */
 | |
| class ProfileFieldCopy implements ProfileFieldCopyInterface {
 | |
| 
 | |
|   use StringTranslationTrait;
 | |
| 
 | |
|   /**
 | |
|    * The current user.
 | |
|    *
 | |
|    * @var \Drupal\Core\Session\AccountInterface
 | |
|    */
 | |
|   protected $currentUser;
 | |
| 
 | |
|   /**
 | |
|    * Constructs a new ProfileFieldCopy object.
 | |
|    *
 | |
|    * @param \Drupal\Core\Session\AccountInterface $current_user
 | |
|    *   The current user.
 | |
|    */
 | |
|   public function __construct(AccountInterface $current_user) {
 | |
|     $this->currentUser = $current_user;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * {@inheritdoc}
 | |
|    */
 | |
|   public function supportsForm(array &$inline_form, FormStateInterface $form_state) {
 | |
|     if (!isset($inline_form['#profile_scope']) || $inline_form['#profile_scope'] != 'billing') {
 | |
|       return FALSE;
 | |
|     }
 | |
|     $order = static::getOrder($form_state);
 | |
|     if (!$order) {
 | |
|       // The inline form is being used outside of an order context
 | |
|       // (e.g. the payment method add/edit screen).
 | |
|       return FALSE;
 | |
|     }
 | |
|     if (!$order->hasField('shipments')) {
 | |
|       // The order is not shippable.
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * {@inheritdoc}
 | |
|    */
 | |
|   public function alterForm(array &$inline_form, FormStateInterface $form_state) {
 | |
|     $shipping_profile = static::getShippingProfile($form_state);
 | |
|     if (!$shipping_profile) {
 | |
|       // No source information is available.
 | |
|       return;
 | |
|     }
 | |
|     $billing_profile = static::getBillingProfile($inline_form);
 | |
|     $shipping_form_display = static::getFormDisplay($shipping_profile, 'shipping');
 | |
|     $shipping_fields = array_keys($shipping_form_display->getComponents());
 | |
|     $user_input = (array) NestedArray::getValue($form_state->getUserInput(), $inline_form['#parents']);
 | |
|     // Copying is enabled by default for new billing profiles.
 | |
|     $enabled = $billing_profile->getData('copy_fields', $billing_profile->isNew());
 | |
|     if ($user_input) {
 | |
|       if (isset($user_input['copy_fields'])) {
 | |
|         $enabled = (bool) ($user_input['copy_fields']['enable'] ?? FALSE);
 | |
|       }
 | |
|       elseif (isset($user_input['select_address'])) {
 | |
|         $enabled = FALSE;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     $inline_form['copy_fields'] = [
 | |
|       '#parents' => array_merge($inline_form['#parents'], ['copy_fields']),
 | |
|       '#type' => 'container',
 | |
|       '#weight' => -1000,
 | |
|       '#shipping_fields' => $shipping_fields,
 | |
|       '#has_form' => FALSE,
 | |
|     ];
 | |
|     $inline_form['copy_fields']['enable'] = [
 | |
|       '#type' => 'checkbox',
 | |
|       '#title' => $this->getCopyLabel($inline_form),
 | |
|       '#default_value' => $enabled,
 | |
|       '#ajax' => [
 | |
|         'callback' => [get_class($this), 'ajaxRefresh'],
 | |
|         'wrapper' => $inline_form['#id'],
 | |
|       ],
 | |
|     ];
 | |
| 
 | |
|     if ($enabled) {
 | |
|       // Copy over the current shipping field values, allowing widgets such as
 | |
|       // TaxNumberDefaultWidget to rely on them. These values might change
 | |
|       // during submit, so the profile is populated again in submitForm().
 | |
|       $billing_profile->populateFromProfile($shipping_profile, $shipping_fields);
 | |
|       // Disable address book copying and remove all existing fields.
 | |
|       $inline_form['copy_to_address_book'] = [
 | |
|         '#type' => 'value',
 | |
|         '#value' => FALSE,
 | |
|       ];
 | |
|       foreach (Element::getVisibleChildren($inline_form) as $key) {
 | |
|         if (!in_array($key, ['copy_fields', 'copy_to_address_book'])) {
 | |
|           $inline_form[$key]['#access'] = FALSE;
 | |
|         }
 | |
|       }
 | |
|       // Add field widgets for any non-copied billing fields.
 | |
|       $form_display = static::getFormDisplay($billing_profile, 'billing', $shipping_fields);
 | |
|       $billing_fields = array_keys($form_display->getComponents());
 | |
|       if ($billing_fields) {
 | |
|         $form_display->buildForm($billing_profile, $inline_form['copy_fields'], $form_state);
 | |
|         $inline_form['copy_fields']['#has_form'] = TRUE;
 | |
|       }
 | |
|       // Replace the existing validate/submit handlers with custom ones.
 | |
|       foreach ($inline_form['#element_validate'] as &$validate_handler) {
 | |
|         if ($validate_handler[1] == 'runValidate') {
 | |
|           $validate_handler = [get_class($this), 'validateForm'];
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       foreach ($inline_form['#commerce_element_submit'] as &$submit_handler) {
 | |
|         if ($submit_handler[1] == 'runSubmit') {
 | |
|           $submit_handler = [get_class($this), 'submitForm'];
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       $billing_profile->unsetData('copy_fields');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Gets the copy label for the given inline form.
 | |
|    *
 | |
|    * @param array $inline_form
 | |
|    *   The inline form.
 | |
|    *
 | |
|    * @return string
 | |
|    *   The copy label.
 | |
|    */
 | |
|   protected function getCopyLabel(array $inline_form) {
 | |
|     /** @var \Drupal\commerce\Plugin\Commerce\InlineForm\EntityInlineFormInterface $plugin */
 | |
|     $plugin = $inline_form['#inline_form'];
 | |
|     $configuration = $plugin->getConfiguration();
 | |
|     $is_owner = FALSE;
 | |
|     if (empty($configuration['admin'])) {
 | |
|       $is_owner = $this->currentUser->id() == $configuration['address_book_uid'];
 | |
|     }
 | |
| 
 | |
|     if ($is_owner) {
 | |
|       $copy_label = $this->t('My billing information is the same as my shipping information.');
 | |
|     }
 | |
|     else {
 | |
|       $copy_label = $this->t('Billing information is the same as the shipping information.');
 | |
|     }
 | |
| 
 | |
|     return $copy_label;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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, -2));
 | |
|     return $inline_form;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Validates the inline form.
 | |
|    *
 | |
|    * @param array $inline_form
 | |
|    *   The inline form.
 | |
|    * @param \Drupal\Core\Form\FormStateInterface $form_state
 | |
|    *   The current state of the form.
 | |
|    */
 | |
|   public static function validateForm(array &$inline_form, FormStateInterface $form_state) {
 | |
|     $shipping_fields = $inline_form['copy_fields']['#shipping_fields'];
 | |
|     if ($inline_form['copy_fields']['#has_form']) {
 | |
|       $billing_profile = static::getBillingProfile($inline_form);
 | |
|       $form_display = static::getFormDisplay($billing_profile, 'billing', $shipping_fields);
 | |
|       $form_display->extractFormValues($billing_profile, $inline_form['copy_fields'], $form_state);
 | |
|       $form_display->validateFormValues($billing_profile, $inline_form['copy_fields'], $form_state);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Submits the inline form.
 | |
|    *
 | |
|    * @param array $inline_form
 | |
|    *   The inline form.
 | |
|    * @param \Drupal\Core\Form\FormStateInterface $form_state
 | |
|    *   The current state of the form.
 | |
|    */
 | |
|   public static function submitForm(array &$inline_form, FormStateInterface $form_state) {
 | |
|     $shipping_fields = $inline_form['copy_fields']['#shipping_fields'];
 | |
|     $shipping_profile = static::getShippingProfile($form_state);
 | |
|     $billing_profile = static::getBillingProfile($inline_form);
 | |
| 
 | |
|     $billing_profile->populateFromProfile($shipping_profile, $shipping_fields);
 | |
|     if ($inline_form['copy_fields']['#has_form']) {
 | |
|       $form_display = static::getFormDisplay($billing_profile, 'billing', $shipping_fields);
 | |
|       $form_display->extractFormValues($billing_profile, $inline_form['copy_fields'], $form_state);
 | |
|     }
 | |
|     $billing_profile->setData('copy_fields', TRUE);
 | |
|     $billing_profile->unsetData('copy_to_address_book');
 | |
|     // Transfer the source address book ID to ensure that the right option
 | |
|     // is preselected when the copy_fields checkbox is unchecked.
 | |
|     $address_book_profile_id = $shipping_profile->getData('address_book_profile_id');
 | |
|     if ($address_book_profile_id && $shipping_profile->bundle() == $billing_profile->bundle()) {
 | |
|       $billing_profile->setData('address_book_profile_id', $address_book_profile_id);
 | |
|     }
 | |
|     $billing_profile->save();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Gets the billing profile from the inline form.
 | |
|    *
 | |
|    * @param array $inline_form
 | |
|    *   The inline form.
 | |
|    *
 | |
|    * @return \Drupal\profile\Entity\ProfileInterface
 | |
|    *   The profile.
 | |
|    */
 | |
|   protected static function getBillingProfile(array &$inline_form) {
 | |
|     /** @var \Drupal\commerce\Plugin\Commerce\InlineForm\EntityInlineFormInterface $plugin */
 | |
|     $plugin = $inline_form['#inline_form'];
 | |
|     $profile = $plugin->getEntity();
 | |
|     assert($profile instanceof ProfileInterface);
 | |
| 
 | |
|     return $profile;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Gets the shipping profile from the parent form.
 | |
|    *
 | |
|    * @param \Drupal\Core\Form\FormStateInterface $form_state
 | |
|    *   The current state of the form.
 | |
|    *
 | |
|    * @return \Drupal\profile\Entity\ProfileInterface|null
 | |
|    *   The shipping profile, or NULL if not found.
 | |
|    */
 | |
|   protected static function getShippingProfile(FormStateInterface $form_state) {
 | |
|     if ($form_state->has('shipping_profile')) {
 | |
|       // Shipping information on the same step as the billing information.
 | |
|       $shipping_profile = $form_state->get('shipping_profile');
 | |
|     }
 | |
|     else {
 | |
|       $order = static::getOrder($form_state);
 | |
|       $profiles = $order->collectProfiles();
 | |
|       $shipping_profile = $profiles['shipping'] ?? NULL;
 | |
|     }
 | |
| 
 | |
|     return $shipping_profile;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Gets the order from the parent form.
 | |
|    *
 | |
|    * @param \Drupal\Core\Form\FormStateInterface $form_state
 | |
|    *   The current state of the form.
 | |
|    *
 | |
|    * @return \Drupal\commerce_order\Entity\OrderInterface|null
 | |
|    *   The order, or NULL if not found (unrecognized form).
 | |
|    */
 | |
|   protected static function getOrder(FormStateInterface $form_state) {
 | |
|     $form_object = $form_state->getFormObject();
 | |
|     if (!($form_object instanceof BaseFormIdInterface)) {
 | |
|       return NULL;
 | |
|     }
 | |
| 
 | |
|     $order = NULL;
 | |
|     if ($form_object instanceof EntityFormInterface) {
 | |
|       $entity = $form_object->getEntity();
 | |
|       if ($entity->getEntityTypeId() == 'commerce_order') {
 | |
|         $order = $entity;
 | |
|       }
 | |
|     }
 | |
|     elseif ($form_object->getBaseFormId() == 'commerce_checkout_flow') {
 | |
|       /** @var \Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface $form_object */
 | |
|       $order = $form_object->getOrder();
 | |
|     }
 | |
| 
 | |
|     return $order;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Gets the form display for the given profile and form mode.
 | |
|    *
 | |
|    * @param \Drupal\profile\Entity\ProfileInterface $profile
 | |
|    *   The profile.
 | |
|    * @param string $form_mode
 | |
|    *   The form mode.
 | |
|    * @param string[] $remove_fields
 | |
|    *   The fields to remove.
 | |
|    *
 | |
|    * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
 | |
|    *   The form display.
 | |
|    */
 | |
|   protected static function getFormDisplay(ProfileInterface $profile, $form_mode, array $remove_fields = []) {
 | |
|     // @todo Investigate a static cache for form displays, since we load the
 | |
|     //   billing/shipping ones twice (once in CustomerProfile, once here).
 | |
|     $form_display = EntityFormDisplay::collectRenderDisplay($profile, $form_mode);
 | |
|     $form_display->removeComponent('revision_log_message');
 | |
|     foreach ($form_display->getComponents() as $name => $component) {
 | |
|       if (in_array($name, $remove_fields)) {
 | |
|         $form_display->removeComponent($name);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return $form_display;
 | |
|   }
 | |
| 
 | |
| }
 |