class ProfileFieldCopy in Commerce Shipping 8.2
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.
Hierarchy
- class \Drupal\commerce_shipping\ProfileFieldCopy implements ProfileFieldCopyInterface uses StringTranslationTrait
Expanded class hierarchy of ProfileFieldCopy
1 string reference to 'ProfileFieldCopy'
1 service uses ProfileFieldCopy
File
- src/
ProfileFieldCopy.php, line 27
Namespace
Drupal\commerce_shippingView source
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'];
}
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;
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ProfileFieldCopy:: |
protected | property | The current user. | |
ProfileFieldCopy:: |
public static | function | Ajax callback. | |
ProfileFieldCopy:: |
public | function |
Alters the inline form. Overrides ProfileFieldCopyInterface:: |
|
ProfileFieldCopy:: |
protected static | function | Gets the billing profile from the inline form. | |
ProfileFieldCopy:: |
protected | function | Gets the copy label for the given inline form. | |
ProfileFieldCopy:: |
protected static | function | Gets the form display for the given profile and form mode. | |
ProfileFieldCopy:: |
protected static | function | Gets the order from the parent form. | |
ProfileFieldCopy:: |
protected static | function | Gets the shipping profile from the parent form. | |
ProfileFieldCopy:: |
public static | function | Submits the inline form. | |
ProfileFieldCopy:: |
public | function |
Gets whether the given inline form is supported. Overrides ProfileFieldCopyInterface:: |
|
ProfileFieldCopy:: |
public static | function | Validates the inline form. | |
ProfileFieldCopy:: |
public | function | Constructs a new ProfileFieldCopy object. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |