MTShop/web/modules/contrib/commerce_shipping/src/ProfileFieldCopy.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;
}
}