contacts_events-8.x-1.x-dev/src/Plugin/Field/FieldWidget/OrderItemTicketInlineEntityWidget.php

src/Plugin/Field/FieldWidget/OrderItemTicketInlineEntityWidget.php
<?php

namespace Drupal\contacts_events\Plugin\Field\FieldWidget;

use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowBase;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\inline_entity_form\Element\InlineEntityForm;
use Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormBase;
use Drupal\inline_entity_form\Plugin\Field\FieldWidget\InlineEntityFormComplex;

/**
 * Inline widget for tickets.
 *
 * @FieldWidget(
 *   id = "inline_entity_form_order_item_tickets",
 *   label = @Translation("Booking Tickets"),
 *   field_types = {
 *     "entity_reference"
 *   },
 *   multiple_values = true
 * )
 */
class OrderItemTicketInlineEntityWidget extends InlineEntityFormComplex {

  /**
   * {@inheritdoc}
   */
  protected function getTargetBundles() {
    // Don't allow creation of any other order item type other than ticket.
    return ['contacts_ticket'];
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return ['form_mode' => 'booking'] + parent::defaultSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $element = parent::settingsForm($form, $form_state);
    $element['allow_new']['#access'] = FALSE;
    $element['allow_existing']['#access'] = FALSE;
    $element['match_operator']['#access'] = FALSE;
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    return InlineEntityFormBase::settingsSummary();
  }

  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    // Set up a few bits early so we can pre-open a specific order item.
    if ($form_state->get('inline_entity_form_order_item_tickets')) {
      $early_open = $form_state->get('inline_entity_form_order_item_tickets');
    }
    else {
      $query_params = \Drupal::request()->query->all();
      if (!empty($query_params['op']) && !empty($query_params['id'])) {
        $early_open = [
          'op' => $query_params['op'],
          'id' => $query_params['id'],
        ];
      }
    }

    if (!empty($early_open)) {
      // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
      $parents = array_merge($element['#field_parents'], [$items->getName(), 'form']);
      $this->setIefId(sha1(implode('-', $parents)));
      // Only act if entities aren't already initialised.
      $location = ['inline_entity_form', $this->getIefId(), 'entities'];
      if ($form_state->get($location) === NULL) {
        $this->prepareFormState($form_state, $items, $this->isTranslating($form_state));
        $entities = $form_state->get($location);
        foreach ($entities as $delta => $entity) {
          if ($entity['entity']->id() == $early_open['id']) {
            $location[] = $delta;
            $location[] = 'form';
            $form_state->set($location, $early_open['op']);
            break;
          }
        }
      }
    }

    $element = parent::formElement($items, $delta, $element, $form, $form_state);

    // Disable the table drag as we don't want that.
    $element['entities']['#cache']['contexts'][] = 'user.permissions';
    $element['entities']['#disable_tabledrag'] = TRUE;

    // Override the field title as we are only dealing with tickets.
    $element['#field_title'] = $this->t('Tickets');

    if ($items->offsetExists($delta)) {
      // If the item exists but doesn't have an entity then remove the item.
      if (!$items[$delta]->entity) {
        unset($items[$delta]);
        unset($element['entities'][$delta]);
      }
    }

    foreach (Element::children($element['entities']) as $delta) {
      if (!isset($items[$delta]->entity) || $items[$delta]->entity->bundle() != 'contacts_ticket') {
        unset($element['entities'][$delta]);
      }
      // Check if the entities need reloading due to price change.
      elseif ($form_state->get('inline_entity_form_order_item_tickets_reload')) {
        $entity_id = $element['entities'][$delta]['#entity']->id();
        $entity_unchanced = \Drupal::entityTypeManager()->getStorage('commerce_order_item')->load($entity_id);
        $element['entities'][$delta]['#entity'] = $entity_unchanced;
      }
    }

    // Build a parents array for this element's values in the form.
    $parents = array_merge($element['#field_parents'], [
      $items->getName(),
      'form',
    ]);

    // Loop over the tickets and add the cancel form and operation as required.
    $entities = $form_state->get([
      'inline_entity_form',
      $this->getIefId(),
      'entities',
    ]);
    $workflow = NULL;
    $has_form = (bool) $form_state
      ->get(['inline_entity_form', $this->getIefId(), 'form']);
    foreach (Element::children($element['entities']) as $delta) {
      $entity_element = &$element['entities'][$delta];
      $entity = $entity_element['#entity'];
      $entity_form = $entities[$delta]['form'] ?? FALSE;

      // If we are showing the row, update the AJAX settings to hide the form
      // actions.
      if (!$entity_form) {
        // Make each entity form action hide the submit buttons.
        foreach (Element::children($entity_element['actions']) as $key) {
          $type = $entity_element['actions'][$key]['#type'] ?? NULL;
          if ($type == 'submit' && isset($entity_element['actions'][$key]['#ajax'])) {
            // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
            $entity_element['actions'][$key]['#ajax']['callback'] = [static::class, 'ajaxCallback'];
          }
        }
      }
      // Otherwise hide the add new action so we don't have multiple open.
      else {
        $has_form = TRUE;
      }

      if ($entity->getPurchasedEntity()->access('transfer')) {
        // Add the transfer operation.
        if (!$entity_form) {
          $entity_element['actions']['transfer'] = [
            '#type' => 'link',
            '#title' => $this->t('Transfer ticket'),
            '#url' => Url::fromRoute('entity.contacts_ticket.transfer_form', ['contacts_ticket' => $entity->getPurchasedEntityId()]),
            '#attributes' => [
              'class' => [
                'button',
                'btn',
                'btn-primary',
                'order-lg-last',
                'mb-1',
                'mb-sm-2',
              ],
            ],
          ];
        }
      }

      // Check if we have cancel access.
      if (!$entity->access('cancel')) {
        continue;
      }

      // Add the cancel operation if we're not in a form already.
      if (!$entity_form) {
        $entity_element['actions']['cancel'] = [
          '#type' => 'submit',
          '#value' => $this->t('Cancel ticket'),
          '#name' => 'ief-' . $this->getIefId() . '-entity-edit-' . $delta,
          '#limit_validation_errors' => [],
          '#ajax' => [
            'callback' => [static::class, 'ajaxCallback'],
            'wrapper' => 'inline-entity-form-' . $this->getIefId(),
          ],
          '#submit' => [[$this, 'cancelSubmit']],
          '#ief_row_delta' => $delta,
          '#ief_row_form' => 'cancel',
        ];
      }
      // Show the cancel form if it has already been expanded.
      elseif ($entity_form == 'cancel') {
        $entity_element['form'] = [
          '#type' => 'container',
          '#attributes' => ['class' => ['ief-form', 'ief-form-row']],
          // Used by Field API and controller methods to find the relevant
          // values in $form_state.
          '#parents' => array_merge($parents, ['entities', $delta, 'form']),
          // Store the entity on the form, later modified in the controller.
          '#entity' => $entity,
          // Identifies the IEF widget to which the form belongs.
          '#ief_id' => $this->getIefId(),
          // Identifies the table row to which the form belongs.
          '#ief_row_delta' => $delta,
        ];
        $parent_langcode = $items->getEntity()->language()->getId();
        $this->buildCancelForm($entity_element['form'], $delta, $parent_langcode, $parents);
      }
    }

    // If we have an expanded item, hide all other operations.
    if ($has_form) {
      // Use a process to disable the outer form actions.
      $element['#process'][] = [static::class, 'disableActions'];

      // Disable IEF actions such as 'Add'.
      $element['actions']['#disabled'] = TRUE;

      // Loop over the rows to disable row operations.
      foreach (Element::children($element['entities']) as $delta) {
        $element['entities'][$delta]['actions']['#disabled'] = TRUE;
      }
    }

    // Set the IEF actions to use the ajax callback.
    foreach (Element::children($element['actions']) as $key) {
      // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
      $element['actions'][$key]['#ajax']['callback'] = [static::class, 'ajaxCallback'];
    }

    return $element;
  }

  /**
   * Process callback to disable the outer form actions.
   */
  public static function disableActions(array $element, FormStateInterface $form_state, &$complete_form) {
    foreach (Element::children($complete_form['actions']) as $key) {
      $complete_form['actions'][$key]['#attributes']['disabled'] = TRUE;
    }
    return $element;
  }

  /**
   * Submission handler for cancel button.
   *
   * @param array $form
   *   The form being submitted.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state of the form being submitted.
   */
  public function cancelSubmit(array $form, FormStateInterface $form_state) {
    $element = inline_entity_form_get_element($form, $form_state);
    $ief_id = $element['#ief_id'];
    $delta = $form_state->getTriggeringElement()['#ief_row_delta'];

    $form_state->setRebuild();
    // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
    $form_state->set(['inline_entity_form', $ief_id, 'entities', $delta, 'form'], $form_state->getTriggeringElement()['#ief_row_form']);
  }

  /**
   * {@inheritdoc}
   */
  public static function buildEntityFormActions($element) {
    $element = parent::buildEntityFormActions($element);
    foreach (Element::children($element['actions']) as $key) {
      // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
      $element['actions'][$key]['#submit'][] = [static::class, 'clearTicketFromState'];
      // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
      $element['actions'][$key]['#ajax']['callback'] = [static::class, 'ajaxCallback'];

      if ($key == 'ief_cancel_save') {
        // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
        $element['actions'][$key]['#submit'][] = [static::class, 'clearTicketFromState'];
        $element['actions'][$key]['#value'] = new TranslatableMarkup('Cancel ticket');
      }
      if ($key == 'ief_cancel_cancel') {
        $element['actions'][$key]['#value'] = new TranslatableMarkup('Close');
      }
    }

    return $element;
  }

  /**
   * Submission callback to clear the ticket from the form state.
   *
   * @param array $entity_form
   *   The entity form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public static function clearTicketFromState(array &$entity_form, FormStateInterface $form_state) {
    $form_state->set('ticket', NULL);
  }

  /**
   * {@inheritdoc}
   */
  protected function getEntityTypeLabels() {
    // The admin has specified the exact labels that should be used.
    if ($this->getSetting('override_labels')) {
      return [
        'singular' => $this->getSetting('label_singular'),
        'plural' => $this->getSetting('label_plural'),
      ];
    }
    else {
      return [
        'singular' => $this->t('ticket'),
        'plural' => $this->t('tickets'),
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function isApplicable(FieldDefinitionInterface $field_definition) {
    // Only allow for order items on the contacts booking bundle of orders.
    return $field_definition->getTargetEntityTypeId() == 'commerce_order'
      && $field_definition->getName() == 'order_items';
  }

  /**
   * {@inheritdoc}
   */
  public static function submitSaveEntity($entity_form, FormStateInterface $form_state) {
    // TicketInlineForm::save handles saving new order items, but we need to
    // make sure we track the correct item, so always pull from the ticket form.
    $ticket_form = $entity_form['purchased_entity']['widget'][0]['inline_entity_form'];
    $order_item = $ticket_form['#entity']->getOrderItem();
    $entity_form['#entity'] = $order_item;

    parent::submitSaveEntity($entity_form, $form_state);

    $form_object = $form_state->getFormObject();
    // The form object may be a CheckoutFlowBase object which does not extend
    // FormBase so needs another method for accessing the order.
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = $form_object instanceof CheckoutFlowBase ? $form_object->getOrder() : $form_object->getEntity();

    // We're explicitly updating the order items, so skip refreshing. Otherwise
    // we get a double save and the entity in the form is out of date.
    $order->setRefreshState(OrderInterface::REFRESH_SKIP);

    // Ensure the item is added to the order and the total recalculated.
    $order->addItem($order_item)
      ->save();

    // Allow form element to rebuild entities after price recalculation.
    $form_state->set('inline_entity_form_order_item_tickets_reload', TRUE);
  }

  /**
   * Builds cancel form.
   *
   * @param array $form
   *   Form array structure.
   * @param string $bundle
   *   Entity bundle.
   * @param string $parent_langcode
   *   The parent language code.
   * @param array $parents
   *   Array of parent element names.
   */
  protected function buildCancelForm(array &$form, $bundle, $parent_langcode, array $parents) {
    /** @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $form['#entity'];

    $entity_label = $this->inlineFormHandler->getEntityLabel($entity);
    $labels = $this->getEntityTypeLabels();

    if ($entity_label) {
      $message = $this->t('Are you sure you want to cancel %label?', ['%label' => $entity_label]);
    }
    else {
      $message = $this->t('Are you sure you want to cancel this %entity_type?', ['%entity_type' => $labels['singular']]);
    }

    $form['message'] = [
      '#theme_wrappers' => ['container'],
      '#markup' => $message,
    ];

    $this->setSetting('form_mode', 'cancel');
    $form['inline_entity_form'] = $this->getInlineEntityForm(
      'cancel',
      $entity->bundle(),
      $parent_langcode,
      $bundle,
      // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
      array_merge($parents, ['inline_entity_form', 'entities', $bundle, 'form']),
      $entity
    );

    $form['inline_entity_form']['#process'] = [
      [InlineEntityForm::class, 'processEntityForm'],
      [get_class($this), 'addIefSubmitCallbacks'],
      [get_class($this), 'buildEntityFormActions'],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function addIefSubmitCallbacks($element) {
    // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
    $element['#ief_element_submit'][] = [get_called_class(), 'submitConfirmCancel'];
    $element = parent::addIefSubmitCallbacks($element);
    return $element;
  }

  /**
   * Cancel form submit callback.
   *
   * The row is identified by #ief_row_delta stored on the triggering
   * element.
   * This isn't an #element_validate callback to avoid processing the
   * cancel form when the main form is submitted.
   *
   * @param array $element
   *   The complete parent form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state of the parent form.
   */
  public static function submitConfirmCancel(array &$element, FormStateInterface $form_state) {
    $cancel_button = $form_state->getTriggeringElement();

    // Only trigger on corrent form operation and trigger button.
    if ($element['#op'] !== 'cancel' || end($cancel_button['#parents']) !== 'ief_cancel_save') {
      return;
    }

    $delta = $cancel_button['#ief_row_delta'];

    /** @var \Drupal\Core\Entity\EntityInterface $entity */
    /** @var \Drupal\commerce_order\Entity\OrderItemInterface $entity */
    $entity = $element['#entity'];
    $entity_id = $entity->id();

    $form_values = NestedArray::getValue($form_state->getValues(), $element['#parents']);
    $form_state->setRebuild();

    // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
    $widget_state = $form_state->get(['inline_entity_form', $element['#ief_id']]);
    // This entity hasn't been saved yet, we can just unlink it.
    if (empty($entity_id) || ($cancel_button['#allow_existing'] && empty($form_values['delete']))) {
      unset($widget_state['entities'][$delta]);
    }
    // The entity has been saved and OrderItemCancelForm::submitForm() isn't
    // called by IEF so update the order item and ticket accordingly.
    else {
      /** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItem $state */
      $state = $entity->get('state')->first();
      $state->applyTransitionById('cancel');

      /** @var \Drupal\contacts_events\Entity\Ticket $ticket */
      $ticket = $entity->getPurchasedEntity();

      if (!$entity->get('mapped_price')->isEmpty()) {
        $mapped_price = $entity->get('mapped_price')->first()->getValue();
        $mapped_price['class_overridden'] = TRUE;
        $entity->set('mapped_price', $mapped_price);
        $ticket->setMappedPrice($mapped_price);
      }
      $entity->save();

      // Clear contact and email so they could be re-booked on to event.
      $ticket->set('contact', NULL);
      $ticket->set('email', NULL);
      $ticket->save();

      // The order item's state change won't be reflected in the UI
      // unless the order item is explicitly set back into the form for IEF
      // to pick up.
      $widget_state['entities'][$delta]['entity'] = $entity;
    }

    // Show status message to user.
    \Drupal::messenger()->addStatus(new TranslatableMarkup('You have successfully cancelled this ticket.'));

    // phpcs:ignore Drupal.Arrays.Array.LongLineDeclaration
    $form_state->set(['inline_entity_form', $element['#ief_id']], $widget_state);
    $form_state->setRebuild();
  }

  /**
   * {@inheritdoc}
   */
  public static function submitConfirmRemove($form, FormStateInterface $form_state) {
    parent::submitConfirmRemove($form, $form_state);

    $element = inline_entity_form_get_element($form, $form_state);
    $remove_button = $form_state->getTriggeringElement();
    $delta = $remove_button['#ief_row_delta'];

    /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
    $order_item = $element['entities'][$delta]['form']['#entity'];

    // Get the right order entity.
    $form_object = $form_state->getFormObject();
    // The form object may be a CheckoutFlowBase object which does not extend
    // FormBase so needs another method for accessing the order.
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = $form_object instanceof CheckoutFlowBase ? $form_object->getOrder() : $form_object->getEntity();

    // Remove the order item from the order, save the order and delete the order
    // item. We do this immediately so you don't have to separate save the page.
    $order->removeItem($order_item);
    $order->save();
    $order_item->delete();

    // Show status message to user.
    \Drupal::messenger()->addStatus(new TranslatableMarkup('This unconfirmed ticket has been removed.'));
  }

  /**
   * Ajax callback to show or hide outer form actions.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   An AJAX response containing both the default IEF update and the commands
   *   for showing or hiding the outer form actions.
   */
  public static function ajaxCallback(array $form, FormStateInterface $form_state) {
    // Wrap the inline entity form ajax callback so we can add commands.
    // To do that, build a response from the callback.
    // @see FormAjaxResponseBuilder::buildResponse
    $form = inline_entity_form_get_element($form, $form_state);
    $request = \Drupal::request();
    $route_match = \Drupal::routeMatch();
    $response = \Drupal::service('main_content_renderer.ajax')->renderResponse($form, $request, $route_match);

    // Replace the outer form actions.
    $complete_form = $form_state->getCompleteForm();
    $actions = [];
    foreach (Element::children($complete_form['actions']) as $key) {
      $actions[$key] = $complete_form['actions'][$key];
    }
    $response->addCommand(new HtmlCommand('#edit-actions', $actions));

    return $response;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc