View source
<?php
namespace Drupal\commerce_shipping\Plugin\Commerce\CheckoutPane;
use Drupal\commerce\AjaxFormTrait;
use Drupal\commerce\InlineFormManager;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\commerce_shipping\Entity\ShipmentInterface;
use Drupal\commerce_shipping\OrderShipmentSummaryInterface;
use Drupal\commerce_shipping\PackerManagerInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\profile\Entity\ProfileInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ShippingInformation extends CheckoutPaneBase implements ContainerFactoryPluginInterface {
use AjaxFormTrait;
protected $entityTypeBundleInfo;
protected $inlineFormManager;
protected $packerManager;
protected $orderShipmentSummary;
public function __construct(array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfo $entity_type_bundle_info, InlineFormManager $inline_form_manager, PackerManagerInterface $packer_manager, OrderShipmentSummaryInterface $order_shipment_summary) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $checkout_flow, $entity_type_manager);
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->inlineFormManager = $inline_form_manager;
$this->packerManager = $packer_manager;
$this->orderShipmentSummary = $order_shipment_summary;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow = NULL) {
return new static($configuration, $plugin_id, $plugin_definition, $checkout_flow, $container
->get('entity_type.manager'), $container
->get('entity_type.bundle.info'), $container
->get('plugin.manager.commerce_inline_form'), $container
->get('commerce_shipping.packer_manager'), $container
->get('commerce_shipping.order_shipment_summary'));
}
public function defaultConfiguration() {
return [
'auto_recalculate' => TRUE,
'require_shipping_profile' => TRUE,
] + parent::defaultConfiguration();
}
public function buildConfigurationSummary() {
if (!empty($this->configuration['require_shipping_profile'])) {
$summary = $this
->t('Hide shipping costs until an address is entered: Yes') . '<br>';
}
else {
$summary = $this
->t('Hide shipping costs until an address is entered: No') . '<br>';
}
if (!empty($this->configuration['auto_recalculate'])) {
$summary .= $this
->t('Autorecalculate: Yes');
}
else {
$summary .= $this
->t('Autorecalculate: No');
}
return $summary;
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['require_shipping_profile'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Hide shipping costs until an address is entered'),
'#default_value' => $this->configuration['require_shipping_profile'],
];
$form['auto_recalculate'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Auto recalculate shipping costs when the shipping address changes'),
'#default_value' => $this->configuration['auto_recalculate'],
'#states' => [
'visible' => [
':input[name="configuration[panes][shipping_information][configuration][require_shipping_profile]"]' => [
'checked' => TRUE,
],
],
],
];
return $form;
}
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['require_shipping_profile'] = !empty($values['require_shipping_profile']);
$this->configuration['auto_recalculate'] = !empty($values['auto_recalculate']) && $this->configuration['require_shipping_profile'];
}
}
public function isVisible() {
if (!$this->order
->hasField('shipments')) {
return FALSE;
}
foreach ($this->order
->getItems() as $order_item) {
$purchased_entity = $order_item
->getPurchasedEntity();
if ($purchased_entity && $purchased_entity
->hasField('weight')) {
return TRUE;
}
}
return FALSE;
}
public function buildPaneSummary() {
$summary = [];
if ($this
->isVisible()) {
$summary = $this->orderShipmentSummary
->build($this->order, 'checkout');
}
return $summary;
}
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
$store = $this->order
->getStore();
$available_countries = [];
foreach ($store
->get('shipping_countries') as $country_item) {
$available_countries[] = $country_item->value;
}
$inline_form = $this->inlineFormManager
->createInstance('customer_profile', [
'profile_scope' => 'shipping',
'available_countries' => $available_countries,
'address_book_uid' => $this->order
->getCustomerId(),
'copy_on_save' => FALSE,
], $this
->getShippingProfile());
$pane_form['#wrapper_id'] = 'shipping-information-wrapper';
$pane_form['#prefix'] = '<div id="' . $pane_form['#wrapper_id'] . '">';
$pane_form['#suffix'] = '</div>';
$pane_form['#auto_recalculate'] = !empty($this->configuration['auto_recalculate']) && !empty($this->configuration['require_shipping_profile']);
$pane_form['#after_build'][] = [
static::class,
'autoRecalculateProcess',
];
$pane_form['shipping_profile'] = [
'#parents' => array_merge($pane_form['#parents'], [
'shipping_profile',
]),
'#inline_form' => $inline_form,
];
$pane_form['shipping_profile'] = $inline_form
->buildInlineForm($pane_form['shipping_profile'], $form_state);
$triggering_element = $form_state
->getTriggeringElement();
if (!$form_state
->has('shipping_profile') || isset($triggering_element['#parents']) && in_array('select_address', $triggering_element['#parents'])) {
$form_state
->set('shipping_profile', $inline_form
->getEntity());
}
$class = get_class($this);
if (isset($pane_form['shipping_profile']['select_address'])) {
$pane_form['shipping_profile']['select_address']['#ajax'] = [
'callback' => [
$class,
'ajaxRefreshForm',
],
'element' => $pane_form['#parents'],
];
$pane_form['shipping_profile']['select_address']['#recalculate'] = TRUE;
}
$pane_form['recalculate_shipping'] = [
'#type' => 'button',
'#value' => $this
->t('Recalculate shipping'),
'#recalculate' => TRUE,
'#ajax' => [
'callback' => [
$class,
'ajaxRefreshForm',
],
'element' => $pane_form['#parents'],
],
'#limit_validation_errors' => [
array_merge($pane_form['#parents'], [
'shipping_profile',
]),
],
'#after_build' => [
[
static::class,
'clearValues',
],
],
];
$pane_form['removed_shipments'] = [
'#type' => 'value',
'#value' => [],
];
$pane_form['shipments'] = [
'#type' => 'container',
];
$shipping_profile = $form_state
->get('shipping_profile');
$shipments = $this->order
->get('shipments')
->referencedEntities();
$recalculate_shipping = $form_state
->get('recalculate_shipping');
$can_calculate_rates = $this
->canCalculateRates($shipping_profile);
if ($recalculate_shipping && !$can_calculate_rates) {
$recalculate_shipping = FALSE;
$shipments = [];
}
if (!$shipping_profile
->isNew() && $shipping_profile
->hasTranslationChanges() && $can_calculate_rates) {
$shipping_profile
->save();
$inline_form
->setEntity($shipping_profile);
}
$force_packing = empty($shipments) && $can_calculate_rates;
if ($recalculate_shipping || $force_packing) {
list($shipments, $removed_shipments) = $this->packerManager
->packToShipments($this->order, $shipping_profile, $shipments);
$pane_form['removed_shipments']['#value'] = array_map(function ($shipment) {
return $shipment
->id();
}, $removed_shipments);
}
$single_shipment = count($shipments) === 1;
foreach ($shipments as $index => $shipment) {
$pane_form['shipments'][$index] = [
'#parents' => array_merge($pane_form['#parents'], [
'shipments',
$index,
]),
'#array_parents' => array_merge($pane_form['#parents'], [
'shipments',
$index,
]),
'#type' => $single_shipment ? 'container' : 'fieldset',
'#title' => $shipment
->getTitle(),
];
$form_display = EntityFormDisplay::collectRenderDisplay($shipment, 'checkout');
$form_display
->removeComponent('shipping_profile');
$form_display
->buildForm($shipment, $pane_form['shipments'][$index], $form_state);
$pane_form['shipments'][$index]['#shipment'] = $shipment;
}
if (!$this
->hasRateSelected($pane_form, $form_state)) {
array_map(function (ShipmentInterface $shipment) {
if (!$shipment
->isNew()) {
$shipment
->save();
}
}, $shipments);
$this->order
->set('shipments', $shipments);
$this->order
->save();
}
return $pane_form;
}
public static function autoRecalculateProcess(array $element, FormStateInterface $form_state) {
if ($element['#auto_recalculate']) {
$recalculate_button_selector = $element['recalculate_shipping']['#attributes']['data-drupal-selector'];
$element['#attached']['library'][] = 'commerce_shipping/shipping_checkout';
$element['#attached']['drupalSettings']['commerceShipping'] = [
'wrapper' => '#' . $element['#wrapper_id'],
'recalculateButtonSelector' => '[data-drupal-selector="' . $recalculate_button_selector . '"]',
];
$element['recalculate_shipping']['#attributes']['class'][] = 'js-hide';
}
return $element;
}
public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
$shipment_indexes = Element::children($pane_form['shipments']);
$triggering_element = $form_state
->getTriggeringElement();
$recalculate = !empty($triggering_element['#recalculate']);
$button_type = isset($triggering_element['#button_type']) ? $triggering_element['#button_type'] : '';
$inline_form = $pane_form['shipping_profile']['#inline_form'];
$profile = $inline_form
->getEntity();
$form_state
->set('shipping_profile', $profile);
$shipments = [];
foreach ($shipment_indexes as $index) {
if (!isset($pane_form['shipments'][$index]['#shipment'])) {
continue;
}
$shipment = clone $pane_form['shipments'][$index]['#shipment'];
$form_display = EntityFormDisplay::collectRenderDisplay($shipment, 'checkout');
$form_display
->removeComponent('shipping_profile');
$form_display
->extractFormValues($shipment, $pane_form['shipments'][$index], $form_state);
$form_display
->validateFormValues($shipment, $pane_form['shipments'][$index], $form_state);
$shipment
->setShippingProfile($profile);
$shipments[] = $shipment;
}
if (!$recalculate && $button_type == 'primary' && !$shipments) {
$recalculate = TRUE;
$this
->messenger()
->addError($this
->t('Please select a shipping method.'));
$form_state
->setRebuild(TRUE);
}
$form_state
->set('recalculate_shipping', $recalculate);
if (!empty($triggering_element['#rate'])) {
array_map(function (ShipmentInterface $shipment) {
if (!$shipment
->isNew()) {
$shipment
->save();
}
}, $shipments);
$this->order
->set('shipments', $shipments);
$this->order
->save();
}
}
public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
$inline_form = $pane_form['shipping_profile']['#inline_form'];
$profile = $inline_form
->getEntity();
$shipments = [];
foreach (Element::children($pane_form['shipments']) as $index) {
if (!isset($pane_form['shipments'][$index]['#shipment'])) {
continue;
}
$shipment = clone $pane_form['shipments'][$index]['#shipment'];
$form_display = EntityFormDisplay::collectRenderDisplay($shipment, 'checkout');
$form_display
->removeComponent('shipping_profile');
$form_display
->extractFormValues($shipment, $pane_form['shipments'][$index], $form_state);
$shipment
->setShippingProfile($profile);
$shipment
->save();
$shipments[] = $shipment;
}
$this->order->shipments = $shipments;
$removed_shipment_ids = $pane_form['removed_shipments']['#value'];
if (!empty($removed_shipment_ids)) {
$shipment_storage = $this->entityTypeManager
->getStorage('commerce_shipment');
$removed_shipments = $shipment_storage
->loadMultiple($removed_shipment_ids);
$shipment_storage
->delete($removed_shipments);
}
}
public static function clearValues(array $element, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
if (!$triggering_element) {
return $element;
}
$triggering_element_name = end($triggering_element['#parents']);
if (in_array($triggering_element_name, [
'recalculate_shipping',
'select_address',
], TRUE)) {
$user_input =& $form_state
->getUserInput();
$parents = $element['#parents'];
array_pop($parents);
$parents[] = 'shipments';
NestedArray::unsetValue($user_input, $parents);
}
return $element;
}
protected function getShippingProfile() {
$shipping_profile = NULL;
foreach ($this->order
->get('shipments')
->referencedEntities() as $shipment) {
$shipping_profile = $shipment
->getShippingProfile();
break;
}
if (!$shipping_profile) {
$profile_type_id = 'customer';
$order_type_id = $this->order
->bundle();
$order_bundle_info = $this->entityTypeBundleInfo
->getBundleInfo('commerce_order');
if (!empty($order_bundle_info[$order_type_id]['shipping_profile_type'])) {
$profile_type_id = $order_bundle_info[$order_type_id]['shipping_profile_type'];
}
$shipping_profile = $this->entityTypeManager
->getStorage('profile')
->create([
'type' => $profile_type_id,
'uid' => 0,
]);
}
return $shipping_profile;
}
protected function canCalculateRates(ProfileInterface $profile) {
if (!empty($this->configuration['require_shipping_profile'])) {
$violations = $profile
->get('address')
->validate();
return count($violations) === 0;
}
return TRUE;
}
protected function hasRateSelected(array $pane_form, FormStateInterface $form_state) {
$user_input = NestedArray::getValue($form_state
->getUserInput(), $pane_form['#parents']);
if (empty($user_input['shipments'])) {
return FALSE;
}
foreach ($user_input['shipments'] as $values) {
if (!empty(array_filter($values['shipping_method']))) {
return TRUE;
}
}
return FALSE;
}
}