entity_reference_inline-8.x-1.x-dev/src/Plugin/Field/FieldWidget/EntityReferenceInlineWidget.php

src/Plugin/Field/FieldWidget/EntityReferenceInlineWidget.php
<?php
 
/**
 * @file
 * Contains \Drupal\entity_reference_inline\Plugin\Field\FieldWidget\EntityReferenceInlineWidget.
 */
 
namespace Drupal\entity_reference_inline\Plugin\Field\FieldWidget;
 
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\content_translation\ContentTranslationManagerInterface;
use Drupal\content_translation\Controller\ContentTranslationController;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Ajax\RestripeCommand;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldFilteredMarkup;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use \QueryPath;
 
/**
 * Plugin implementation of the 'entity_reference_inline' widget.
 *
 * @FieldWidget(
 *   id = "entity_reference_inline",
 *   label = @Translation("Entity reference inline widget"),
 *   description = @Translation("An inline entity form of the referenced entities."),
 *   entity_deep_serialization = TRUE,
 *   field_types = {
 *     "entity_reference_inline"
 *   }
 * )
 */
class EntityReferenceInlineWidget extends WidgetBase implements ContainerFactoryPluginInterface {
 
  const NEW_ELEMENT_RETURN_TABLE = 'return_table';
 
  const NEW_ELEMENT_RETURN_SINGLE_ELEMENT = 'return_single_row';
 
  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;
 
  /**
   * The entity display repository.
   *
   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
   */
  protected $entityDisplayRepository;
 
  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;
 
  /**
   * The entity form displays to be used a referenced entity bundle.
   *
   * An associative array, keyed by the entity form display id and valued by
   * the corresponding entity form display.
   *
   * @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface[]
   */
  protected $referencedEntityFormDisplays;
 
  /**
   * The content translation controller.
   *
   * @var \Drupal\content_translation\Controller\ContentTranslationController
   */
  protected $contentTranslationController;
 
  /**
   * The content translation manager.
   *
   * @var \Drupal\content_translation\ContentTranslationManagerInterface
   */
  protected $contentTranslationManager;
 
  /**
   * The entity type bundle info.
   *
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $entityTypeBundleInfo;
 
  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;
 
  /**
   * Proxy for the current user account.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;
 
  /**
   * The referenced entity type.
   *
   * @var \Drupal\Core\Entity\ContentEntityTypeInterface
   */
  protected $referencedEntityType;
 
  /**
   * A temporary storage of the field items argument of ::extractFormValues().
   *
   * We set this property in ::extractFormValues() before we call the parent in
   * order to able to access the field item list in ::massageFormValues() when
   * it is called from within ::extractFormValues() of the parent class.
   *
   * @var \Drupal\Core\Field\FieldItemListInterface
   */
  protected $extractFormValuesFieldItemList;
 
  /**
   * A temporary storage for reused entities inside a form.
   *
   * This property will be used to hold the entity clones when the entity is
   * build from the user input. If inside the same form a reused entity occurs
   * then it will have at all the places the same entity reference, which will
   * be needed by the fields in their preSave to properly update the properties.
   *
   * @var array
   */
  public static $builtEntities = [];
 
  /**
   * Constructs a EntityReferenceInlineWidget object.
   *
   * @param array $plugin_id
   *   The plugin_id for the widget.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The definition of the field to which the widget is associated.
   * @param array $settings
   *   The widget settings.
   * @param array $third_party_settings
   *   Any third party settings.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
   *   The entity display repository.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param \Drupal\content_translation\Controller\ContentTranslationController
   *   The content translation controller.
   * @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
   *   A content translation manager instance.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
   *   The entity type bundle info.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler to invoke the entity reference inline form alter hook with.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   The current user account.
   */
  public function __construct($plugin_id, $plugin_definition,
                              FieldDefinitionInterface $field_definition,
                              array $settings,
                              array $third_party_settings,
                              LanguageManagerInterface $language_manager,
                              EntityDisplayRepositoryInterface $entity_display_repository,
                              EntityTypeManagerInterface $entity_type_manager,
                              ContentTranslationController $content_translation_controller,
                              ContentTranslationManagerInterface $content_translation_manager,
                              EntityTypeBundleInfoInterface $entity_type_bundle_info,
                              ModuleHandlerInterface $module_handler,
                              AccountProxyInterface $current_user) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
    $this->languageManager = $language_manager;
    $this->entityDisplayRepository = $entity_display_repository;
    $this->entityTypeManager = $entity_type_manager;
    $this->contentTranslationController = $content_translation_controller;
    $this->contentTranslationManager = $content_translation_manager;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
    $this->moduleHandler = $module_handler;
    $this->currentUser = $current_user;
  }
 
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $plugin_id,
      $plugin_definition,
      $configuration['field_definition'],
      $configuration['settings'],
      $configuration['third_party_settings'],
      $container->get('language_manager'),
      $container->get('entity_display.repository'),
      $container->get('entity_type.manager'),
      $container->get('entity_reference_inline.content_translation.controller'),
      $container->get('content_translation.manager'),
      $container->get('entity_type.bundle.info'),
      $container->get('module_handler'),
      $container->get('current_user')
    );
  }
 
  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    return [
      'form_mode' => 'default',
      'form_modes_bundles' => [],
      'new_element_return_mode' => static::NEW_ELEMENT_RETURN_SINGLE_ELEMENT
    ] + parent::defaultSettings();
  }
 
  /**
   * {@inheritdoc}
   *
   * The only setting we support at the moment is the form mode, which should be
   * used to build the form of the referenced entities.
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $bundle_options = $this->getBundleOptions();
    if (empty($bundle_options)) {
      $element['form_mode'] = [
        '#type' => 'select',
        '#title' => $this->t('Form modes'),
        '#default_value' => $this->getSetting('form_mode'),
        '#options' => $this->getFormModeOptions(),
        '#description' => $this->t('Select the form mode to use for entity representation.'),
      ];
    }
    else {
      $element['form_modes_bundles'] = [
        '#type' => 'fieldset',
        '#title' => $this->t('Form modes per bundle'),
        '#description' => $this->t('Select the form mode to use for entity representation.'),
      ];
      $form_modes_bundles = $this->getSetting('form_modes_bundles');
      foreach ($bundle_options as $bundle_name => $bundle_label) {
        $element['form_modes_bundles'][$bundle_name] = [
          '#type' => 'select',
          '#title' => $this->t('Form modes of bundle "%bundle_label"', ['%bundle_label' => $bundle_label]),
          '#default_value' => isset($form_modes_bundles[$bundle_name]) ? $form_modes_bundles[$bundle_name] : 'default',
          '#options' => $this->getFormModeOptions($bundle_name),
        ];
      }
    }
 
    $element['new_element_return_mode'] = [
      '#type' => 'select',
      '#title' => $this->t('New element return modes'),
      '#default_value' => $this->getSetting('new_element_return_mode'),
      '#options' => [
        static::NEW_ELEMENT_RETURN_SINGLE_ELEMENT => $this->t('Return and insert a single table row.'),
        static::NEW_ELEMENT_RETURN_TABLE => $this->t('Return and replace the whole field table.')
      ],
      '#description' => $this->t('Select new element return mode to use when creating new elements.'),
    ];
 
    return $element;
  }
 
  /**
   * {@inheritdoc}
   *
   * We set the widget state by ourselves to prevent appending an empty item and
   * handle the removal of items. Additionally we provide meta information for
   * entity_reference_inline_preprocess_field_multiple_value_form.
   */
  public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
    $field_name = $this->fieldDefinition->getName();
    $parents = $form['#parents'];
 
    // Store field information in $form_state.
    if (!static::getWidgetState($parents, $field_name, $form_state)) {
      $field_state = $this->initializeFieldState($items);
      static::setWidgetState($parents, $field_name, $form_state, $field_state);
    }
    else {
      $field_state = static::getWidgetState($parents, $field_name, $form_state);
    }
 
    $widget_form = parent::form($items, $form, $form_state, $get_delta);
 
    // Remove field items, removed by the ajax callback "Remove".
    foreach (array_keys($field_state['deltas_removed']) as $delta) {
      unset($widget_form['widget'][$delta]);
    }
 
    $number_of_rows = $items->count() - count($field_state['deltas_removed']);
    $last_delta = $items->count() - 1;
 
    // Needed only for the remove button functionality.
    if ($this->isCardinalityUnlimited()) {
      $table_id = $this->getEntityReferenceFieldTableId($form);
      $widget_form['widget'] += [
        '#base_widget' => 'entity_reference_inline',
        '#form_parents' => implode('-', $parents),
        '#is_cardinality_unlimited' => TRUE,
        '#table_id' => $table_id
      ];
      $widget_form['widget']['add_more']['add_more']['#ajax']['table_id'] = $table_id;
      $widget_form['widget']['add_more']['add_more']['#ajax']['number_of_rows'] = $number_of_rows;
      $widget_form['widget']['add_more']['add_more']['#ajax']['last_delta'] = $last_delta;
    }
 
    $widget_form['#attached']['library'][] = 'entity_reference_inline/base-theme';
 
    return $widget_form;
  }
 
  /**
   * Prepares a table id based on the parents and the field name.
   *
   * @param $form
   *   The entity form.
   *
   * @return string
   *   A clean css identifier for the field table.
   */
  protected function getEntityReferenceFieldTableId($form) {
    $table_id_parts = $form['#parents'];
    $table_id_parts[] = $this->fieldDefinition->getName();
    $table_id_parts[] = 'entity-reference-inline-field-table';
    return Html::cleanCssIdentifier(implode('-', $table_id_parts));
  }
 
  /**
   * Prepares the initial field state.
   *
   * @param \Drupal\Core\Field\FieldItemListInterface $items
   *   An array of the field values. When creating a new entity this may be NULL
   *   or an empty array to use default values.
   *
   * @return array
   *   The initial field state.
   */
  protected function initializeFieldState(FieldItemListInterface $items) {
    return [
      // Do not add an empty field item.
      'items_count' => count($items) - 1,
      'array_parents' => [],
      'deltas_removed' => [],
      'initial_delta_values' => [],
      'new_element_return_mode' => $this->getSetting('new_element_return_mode'),
    ];
  }
 
  /**
   * {@inheritdoc}
   */
  protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
    $field_name = $this->fieldDefinition->getName();
    $parents = $form['#parents'];
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
    $field_state = static::getWidgetState($parents, $field_name, $form_state);
 
    // If initial_delta_values is emtpty then the addMoreSubmit is not the
    // triggering element.
    if ($field_state['initial_delta_values']) {
      // Determine the number of widgets to display.
      $max = $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED ? $field_state['items_count'] : $cardinality - 1;
 
      for ($delta = 0; $delta <= $max; $delta++) {
        if (!isset($items[$delta])) {
          $initial_delta_values = isset($field_state['initial_delta_values'][$delta]) ? $field_state['initial_delta_values'][$delta] : [];
          // Add the current form language code as the language of the new
          // entity being created.
          if ($this->getReferencedEntityType()->isTranslatable() && ($langcode_key = $this->getReferencedEntityType()->getKey('langcode'))) {
            if (!isset($initial_delta_values[$langcode_key]) && ($form_langcode = $form_state->getFormObject()->getFormLangcode($form_state))) {
              $initial_delta_values[$langcode_key] = $form_langcode;
            }
          }
          $initial_delta_values = ['entity' => $this->getReferencedEntityStorage()->create($initial_delta_values)];
          $items->appendItem($initial_delta_values);
        }
      }
    }
 
    $elements = parent::formMultipleElements($items, $form, $form_state);
 
    // We do not use unique id because this wrapper should not change on each
    // ajax call because it is used in addMoreAjax as a selector and if it
    // changes on each ajax call we we'll have the new name but the DOM will
    // still have the old one and we'll not be able to address it with the
    // returned ajax commands.
    $id_prefix = implode('-', array_merge($parents, [$field_name]));
 
    if (empty($elements)) {
      $title = $this->fieldDefinition->getLabel();
      $description = FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription()));
 
      $elements += [
        '#theme' => 'field_multiple_value_form',
        '#field_name' => $field_name,
        '#cardinality' => $cardinality,
        '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
        '#required' => $this->fieldDefinition->isRequired(),
        '#title' => $title,
        '#description' => $description,
        // max delta is needed in ::addMoreAjax and if there no elements at the
        // moment we want it to be < 0 as 0 means the first element.
        '#max_delta' => -1,
      ];
 
      // Add 'add more' button, if not working with a programmed form.
      if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
        $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
        $elements['#prefix'] = '<div id="' . $wrapper_id . '">';
        $elements['#suffix'] = '</div>';
 
        $elements['add_more'] = [
          '#type' => 'submit',
          '#name' => strtr($id_prefix, '-', '_') . '_add_more',
          '#value' => $this->t('Add another item'),
          '#attributes' => ['class' => ['field-add-more-submit']],
          '#limit_validation_errors' => [array_merge($form['#parents'], [$field_name])],
          '#validate' =>  [],
          '#submit' => [[get_class($this), 'addMoreSubmit']],
          '#ajax' => [
            'callback' => [get_class($this), 'addMoreAjax'],
            'wrapper' => $wrapper_id,
          ],
        ];
      }
    }
 
    if (isset($elements['add_more'])) {
 
      $field_name_css = Html::cleanCssIdentifier($this->fieldDefinition->getName());
      $specific_class = "entity-reference-inline-add-more-container-{$field_name_css}";
 
      // Because of the theme for fields we could only use the add_more key to
      // add the bundle to it and therefore in order for the bundle list to be
      // in front of the add_more button we have to place them in a container
      // and put the bundle list before the add_more key.
      $elements['add_more'] = [
        '#type' => 'container',
        'add_more_bundle' => [],
        'add_more' => $elements['add_more'],
        '#attributes' => ['class' => ['entity-reference-inline-add-more-container', $specific_class]],
      ];
 
      $elements['add_more']['add_more']['#attributes']['class'][] = 'field-add-more-submit-' . strtr($field_name, ['_' => '-']);
      $elements['add_more']['add_more']['#attributes']['class'][] = 'entity-reference-inline-add-more-submit';
 
      // We have to place the bundle select under the 'add_more' key because
      // otherwise the template function will (try to) convert it to a table
      // row and will fail as the #row_id is not set on this element.
      // @see template_preprocess_field_multiple_value_form()
      // @see entity_reference_inline_preprocess_field_multiple_value_form()
      $this->addBundleOptions($elements['add_more']);
    }
 
    if ($this->getSetting('new_element_return_mode') == static::NEW_ELEMENT_RETURN_SINGLE_ELEMENT) {
      // We do not use the replace command but append our elements directly in
      // the table, so there is no need for adding a prefix and defining a
      // wrapper.
      unset($elements['add_more']['#ajax']['wrapper']);
      unset($elements['#prefix']);
      unset($elements['#suffix']);
    }
 
    $elements['add_more']['#ajax']['effect'] = 'slide';
 
 
    // Adding an empty array as #validate so that
    // ContentEntityForm::validateForm is not executed, which if executed will
    // call ContentEntityForm::buildEntity and for each field's widget
    // WidgetBase::extractFormValues will be called so that the parent entity
    // is build from the user input and then the entity is validated. However
    // if the form gets really big validation in ajax calls might only slow
    // down the system. Therefor we turn of the validation for this kind of
    // ajax calls, as the whole form and the entity will be validated when
    // submitting the form.
    if (!isset($elements['add_more']['#validate'])) {
     // $elements['add_more']['#validate'] =  [];
    }
 
    // Invoke the entity reference inline form_multiple_elements alter hooks.
    $context = [
      'items' => $items,
    ];
    $hooks = [
      'entity_reference_inline_form_multiple_elements',
      'entity_reference_inline_' . $this->getReferencedEntityType()->id() . '_form_multiple_elements',
      'entity_reference_inline_' . $this->getReferencedEntityType()->id() . '_' . $this->getSetting('form_mode') . '_form_multiple_elements',
    ];
    $this->moduleHandler->alter($hooks, $elements, $form_state, $context);
 
    return $elements;
  }
 
  /**
   * Sets available bundle options to the add more element.
   */
  protected function addBundleOptions(&$element) {
    if ($bundle_field_name = $this->getReferencedEntityType()->getKey('bundle')) {
      $bundle_options = $this->getBundleOptions();
 
      // Only show a select list if there is more than one bundle configured
      // to be used on this field.
      if (count($bundle_options) > 1) {
        // Using the bundle field name as a key we do not need to add extra
        // handling for the bundle field when creating the entity from the
        // submitted form values.
        $entity_type_label = $this->getReferencedEntityType()->getLabel();
        $field_name_css = Html::cleanCssIdentifier($this->fieldDefinition->getName());
        $specific_class = "entity-reference-inline-add-more-bundle-{$field_name_css}";
        $element['add_more_bundle'] = [
          '#type' => 'container',
          '#attributes' => ['class' => ['entity-reference-inline-add-more-submit-bundle', $specific_class]],
          $bundle_field_name => [
            '#type' => 'select',
            '#options' => $bundle_options,
            '#empty_option' => $this->t('- Select element type to add -'),
            '#entity_type_label' => $entity_type_label,
          ],
        ];
        $element['add_more']['#validate'][] = [static::class, 'addMoreValidateBundle'];
      }
      else {
        // We have to reset the array in order to access any key.
        reset($bundle_options);
        // We do not need a select list if only one bundle is available.
        $element['add_more_bundle'] = [
          '#type' => 'container',
          $bundle_field_name => [
            '#type' => 'value',
            '#value' => key($bundle_options),
          ],
 
        ];
      }
      $element['add_more']['#bundle_field_name'] = $bundle_field_name;
      $element['add_more']['#allowed_bundles'] = $bundle_options;
    }
  }
 
  /**
   * Returns the bundle options.
   *
   * @return array
   *   The bundle options, keyed by the bundle machine name, valued by the
   *   bundle label.
   */
  protected function getBundleOptions() {
    $bundle_options = [];
    if ($this->getReferencedEntityType()->hasKey('bundle') && ($handler_settings = $this->fieldDefinition->getSetting('handler_settings')) && !empty($handler_settings['target_bundles'])) {
      // Prepare the bundle options with labels.
      $available_bundles = $this->entityTypeBundleInfo->getBundleInfo($this->getFieldSetting('target_type'));
      // We use array flip in case a base field definition is not using for
      // key and value the referenced bundle but only for value.
      $bundle_options = array_intersect_key($available_bundles, array_flip($handler_settings['target_bundles']));
      array_walk($bundle_options, function (&$bundle_info) {
        $bundle_info = $bundle_info['label'];
      });
    }
    return $bundle_options;
  }
 
  /**
   * Validates that when adding a new entity a bundle is selected.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public static function addMoreValidateBundle(array $form, FormStateInterface $form_state) {
    $triggering_element = $form_state->getTriggeringElement();
    $parents = $triggering_element['#parents'];
    array_pop($parents);
    $parents[] = 'add_more_bundle';
    $parents[] = $triggering_element['#bundle_field_name'];
    $selected_bundle = $form_state->getValue($parents);
 
    if (!empty($selected_bundle)) {
      $name = array_shift($parents) . '[' . implode('][', $parents) . ']';
      $form_state->setTemporaryValue('entity_reference_inline_reset_bundle_select', $name);
    }
    else {
      $array_parents = $triggering_element['#array_parents'];
      array_pop($array_parents);
      $array_parents[] = 'add_more_bundle';
      $array_parents[] = $triggering_element['#bundle_field_name'];
      $add_more_bundle_element = NestedArray::getValue($form, $array_parents);
      $form_state->setError($add_more_bundle_element, t('Please select the type of the %entity_type_label to add.', ['%entity_type_label' => $add_more_bundle_element['#entity_type_label']]));
    }
  }
 
  /**
   * {@inheritdoc}
   *
   * If the element at this delta has been removed by an ajax call, there is no
   * need for it to be completely initialized and as a performance optimization
   * we return a dummy element which is going to be removed in ::form before
   * the widget form is returned.
   *
   * Note: If it is needed to extend from the widget and to alter the form
   * element of an entity then ::formElement() should be overridden and not
   * ::formSingleElement() in order for the changes to be available in the hook
   * invocations.
   */
  protected function formSingleElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    $field_state = static::getWidgetState($form['#parents'], $this->fieldDefinition->getName(), $form_state);
    if (isset($field_state['deltas_removed'][$delta])) {
      $element = ['#type' => 'dummy'];
    }
    else {
      $element = parent::formSingleElement($items, $delta, $element, $form, $form_state);
 
      if ($this->getReferencedEntityType()->isTranslatable()) {
        $referenced_entity = $items[$delta]->entity;
        // If the entity is new it should be already in the correct translation!
        $referenced_entity = $referenced_entity->isNew() ? $referenced_entity : $this->prepareTranslation($referenced_entity, $delta, $form['#parents'], $form_state);
      }
      else {
        $referenced_entity = $items[$delta]->entity;
      }
 
      $form_display = $this->getFormDisplay($referenced_entity->getEntityTypeId(), $referenced_entity->bundle());
 
      // Invoke the entity reference inline form alter hooks.
      $context = [
        'entity' => $referenced_entity,
        'form_display' => $form_display,
        'parent_item' => $items[$delta],
        'wrapped_entity_form' => &$element
      ];
      $hooks = [
        'entity_reference_inline_form',
        'entity_reference_inline_' . $referenced_entity->getEntityTypeId() . '_form',
        'entity_reference_inline_' . $referenced_entity->getEntityTypeId() . '_' . $this->getSetting('form_mode') . '_form',
      ];
      $this->moduleHandler->alter($hooks, $element['details_element'], $form_state, $context);
    }
    return $element;
  }
 
  /**
   * Checks if the provided element is only a dummy one, an replacement for
   * a removed delta element.
   *
   * @param array $element
   *   The element for which to check if it is removed.
   *
   * @return bool
   *   TRUE, if the element is removed, FALSE otherwise.
   */
  protected static function isElementRemoved(array $element) {
    return isset($element['#type']) && ($element['#type'] == 'dummy');
  }
 
  /**
   * {@inheritdoc}
   */
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
    if ($this->getReferencedEntityType()->isTranslatable()) {
      $referenced_entity = $items[$delta]->entity;
      // If the entity is new it should be already in the correct translation!
      $referenced_entity = $referenced_entity->isNew() ? $referenced_entity : $this->prepareTranslation($referenced_entity, $delta, $form['#parents'], $form_state);
    }
    else {
      $referenced_entity = $items[$delta]->entity;
    }
 
    // The parents are not set by the parent class when this function is called,
    // but EntityFormDisplay::buildForm will set them to an empty array if not
    // already present. In order to not break the structure by calling
    // EntityFormDisplay we have to ensure the parents are set correctly.
    $element['#parents'] = array_merge($form['#parents'], [$this->fieldDefinition->getName(), $delta]);
    $form_display = $this->getFormDisplay($referenced_entity->getEntityTypeId(), $referenced_entity->bundle());
    $form_display->buildForm($referenced_entity, $element, $form_state);
 
    if ($referenced_entity->isNew()) {
      $entity_type = $referenced_entity->getEntityType();
      if ($bundle_field_name = $entity_type->getKey('bundle')) {
        $element[$bundle_field_name] = [
          '#type' => 'value',
          '#value' => $referenced_entity->bundle(),
        ];
      }
    }
 
    if ($referenced_entity instanceof EntityChangedInterface) {
      // Changed must be sent to the client, for later overwrite error checking.
      // TODO find a better way to include the changed timestamp.
      $element['changed'] = [
        '#type' => 'hidden',
        '#default_value' => $referenced_entity->getChangedTime(),
      ];
    }
 
    $this->addEntityMetaInformation($referenced_entity, $element);
 
    // Add attributes to the form element.
    $attributes = [
      'class' => [
        'entity-reference-inline-details',
        'entity-reference-inline-' . str_replace('_', '-', $referenced_entity->getEntityTypeId()),
        'entity-reference-inline-' . str_replace('_', '-', $referenced_entity->getEntityTypeId() . '--' . $referenced_entity->bundle()),
      ],
    ];
    $entity_id = $referenced_entity->id();
    if ($entity_id) {
      $attributes['entity-reference-inline-id'] = $entity_id;
    }
 
    // Wrap the entity form into details for a better structure of the form.
    $wrapped_entity_form = [
      '#type' => 'details',
      '#title' => $referenced_entity->label(),
      '#open' => TRUE,
      '#attributes' => $attributes,
      'details_element' => &$element,
    ];
 
    $this->addRemoveButton($wrapped_entity_form, $form);
 
    return $wrapped_entity_form;
  }
 
  /**
   * {@inheritdoc}
   */
  public static function addMoreSubmit(array $form, FormStateInterface $form_state) {
    // We don't call the parent as the add more button is wrapped in a
    // container and widget element is two levels up instead of one.
 
    $button = $form_state->getTriggeringElement();
 
    // The widget form with all the elements.
    // Go two levels up in the form, to the widgets container.
    $elements = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
 
    // ----- parent::addMoreSubmit ---------
    $field_name = $elements['#field_name'];
    $parents = $elements['#field_parents'];
 
    // Increment the items count.
    $field_state = static::getWidgetState($parents, $field_name, $form_state);
    $field_state['items_count']++;
    #static::setWidgetState($parents, $field_name, $form_state, $field_state);
 
    $form_state->setRebuild();
    // ----- parent::addMoreSubmit ---------
 
    if (isset($elements['add_more']['add_more_bundle']['#parents'])) {
      $field_state['initial_delta_values'][$field_state['items_count']] = $form_state->getValue($elements['add_more']['add_more_bundle']['#parents']);
    }
    static::setWidgetState($parents, $field_name, $form_state, $field_state);
 
    // Store the previous options which will be used in ::addMoreAjax.
    static::addMorePreserveTemporaryWeightElementPreviousOptions($elements, $form_state);
  }
 
  /**
   * Retrieves and stores the previous weight options into the form state.
   *
   * A helper method for ::addMoreSubmit which output will be used later in
   * ::addMoreAjax.
   *
   * @param $elements
   *   The elements of which to estimate the weight options.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  protected static function addMorePreserveTemporaryWeightElementPreviousOptions($elements, FormStateInterface $form_state) {
    // Store the previous options which will be used in ::addMoreAjax.
    $weight_element_previous_options = [];
    $form_state->set('weight_element_previous_options', []);
    foreach ($elements as $key => $child) {
      if (Element::child($key)) {
        if (!static::isElementRemoved($elements[$key]) && isset($elements[$key]['_weight']['#type']) && $elements[$key]['_weight']['#type'] == 'select') {
          $form_state->setTemporaryValue('weight_element_previous_options', $elements[$key]['_weight']['#options']);
          $weight_element_previous_options = $elements[$key]['_weight']['#options'];
          break;
        }
      }
    }
    $form_state->setTemporaryValue('weight_element_previous_options', $weight_element_previous_options);
  }
 
  /**
   * {@inheritdoc}
   */
  public static function addMoreAjax(array $form, FormStateInterface $form_state) {
    $button = $form_state->getTriggeringElement();
 
    // Go two levels up in the form, to the widgets container.
    $elements = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2));
    $field_name = $elements['#field_name'];
    $parents = $elements['#field_parents'];
    $return_single_row = static::getNewElementReturnMode($parents, $field_name, $form_state) == static::NEW_ELEMENT_RETURN_SINGLE_ELEMENT;
 
    if ($return_single_row) {
      $response = new AjaxResponse();
 
      // If any errors occurred return them without replacing the element as it
      // might have not been properly initialized.
      if (static::addFormErrorsToAjaxResponse($response, $form_state)) {
        return $response;
      }
 
      $button = $form_state->getTriggeringElement();
      $table_id = '#' . $button['#ajax']['table_id'];
      $number_of_rows = $button['#ajax']['number_of_rows'];
 
      // Core replaces hole table and set ajax-new-content class only on new
      // content. Here only the new row is added and the added row still has
      // the class ajax-new-content, which needs to be removed now as its not
      // new anymore.
      $response->addCommand(new InvokeCommand($table_id . ' .ajax-new-content', 'removeClass', ['ajax-new-content']));
 
      // Add a DIV around the delta receifving the Ajax effect.
      $delta = $elements['#max_delta'];
 
      // A storage for the weight elements that have to be updated in case a
      // select element is used instead of a number element, which is decided
      // by Drupal\Core\Render\Element\Weight::processWeight.
      $_weight_elements = [];
 
      // Render as less as possible!
      if ($number_of_rows > 1) {
        foreach ($elements as $key => $child) {
          if (Element::child($key) && ($key != $delta)) {
            if (!static::isElementRemoved($elements[$key]) && isset($elements[$key]['_weight']['#type']) && ($elements[$key]['_weight']['#type'] == 'select')) {
              $_weight_elements[$key] = $elements[$key]['_weight'];
              unset($elements[$key]);
            }
            else {
              unset($elements[$key]);
            }
          }
        }
      }
 
      // Update the weight elements of the other rows if they are rendered as
      // select instead as number elements. We do insert the new options
      // instead of replacing only the previous weight elements as by doing so
      // and cutting them out through query path might get extremely slow with
      // a big html.
      // Updating the weight is necessary in order for tabledrag.js to work
      // properly after a new element is inserted and moved around.
      $new_options = array_diff_assoc($elements[$delta]['_weight']['#options'], $form_state->getTemporaryValue('weight_element_previous_options'));
      $form_state->setTemporaryValue('weight_element_previous_options', []);
      array_walk($new_options, function (&$value, $key) {$value = '<option value="' . $key . '">' . $value . '</option>';});
      foreach ($_weight_elements as $_weight_element) {
        foreach ($new_options as $new_option) {
          $response->addCommand(new AppendCommand('[name="' . $_weight_element['#name'] . '"]', $new_option));
        }
      }
 
      // Needed by entity_reference_inline_preprocess_field_multiple_value_form.
      $elements[$delta]['#new_ajax_row'] = TRUE;
 
      // The library is needed only if a new element has been added to make its
      // row draggable.
      $elements['#attached']['library'][] = 'entity_reference_inline/entity-reference-inline-make-draggable';
 
      // Now after we've minimized the content to be rendered we can render.
      $html = (string) static::drupalRenderRoot($elements);
 
      // If tbody already exists we just return the rendered row, otherwise we
      // have to append the tbody in the first command and in the second command
      // the row, so that no matter if it is the first row in the table or
      // following one we get in attach behaviors as context always just a row
      // and not e.g. "table > tbody > tr" or "tbody > tr".
 
      // Get only the last row.
      $last_table_row = QueryPath::withHTML5($html, $table_id . ' > tbody > tr:last')->html();
 
      // If the last delta is bigger than 0 it means tbody is already present.
      if ($delta > 0) {
        $response->addCommand(new AppendCommand($table_id . ' > tbody', $last_table_row));
      }
      else {
        // Two possibilities:
        // 1. Insert tbody along with the first row in one command
        // $tbody = qp($dom, $table_id_jquery_selector . ' > tbody')->html();
        // $response->addCommand(new AppendCommand($table_id_jquery_selector, $tbody));
 
        // 2. Insert first tbody and after that the row
        //    This unifies the insertion of rows and listening to changed context in
        //    js where with this implementation the changed context for each inserted
        //    row now will be the row and not like in option 1 at inserting the first
        //    row the tbody.
        $response->addCommand(new AppendCommand($table_id, '<tbody></tbody>'));
        $response->addCommand(new AppendCommand($table_id . ' > tbody', $last_table_row));
      }
 
      // Restripe the table.
      $response->addCommand(new RestripeCommand($table_id));
 
      // Add the attachments e.g. if we haven't initialized ckeditor yet it's
      // library will be returned.
      $response->setAttachments(isset($elements['#attached']) ? $elements['#attached'] : []);
 
      // Reset selected bundle.
      $identifier = "[name=\"{$form_state->getTemporaryValue('entity_reference_inline_reset_bundle_select')}\"]";
      $response->addCommand(new InvokeCommand($identifier, 'prop', array('selectedIndex', 0)));
      return $response;
    }
    else {
      // ----- parent::addMoreAjax ---------
 
      // Ensure the widget allows adding additional items.
      if ($elements['#cardinality'] != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
        return;
      }
 
      // Add a DIV around the delta receiving the Ajax effect.
      $delta = $elements['#max_delta'];
      $elements[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($elements[$delta]['#prefix']) ? $elements[$delta]['#prefix'] : '');
      $elements[$delta]['#suffix'] = (isset($elements[$delta]['#suffix']) ? $elements[$delta]['#suffix'] : '') . '</div>';
 
      return $elements;
      // ----- parent::addMoreAjax ---------
    }
  }
 
  /**
   * Wraps the renderRoot function of the renderer service.
   */
  protected static function drupalRenderRoot(&$elements) {
    return \Drupal::service('renderer')->renderRoot($elements);
  }
 
  /**
   * Adds status messages to the ajax response if any errors occurred.
   *
   * @param \Drupal\Core\Ajax\AjaxResponse $response
   *   The ajax response.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return bool
   *   Returns TRUE if the form state contains errors and they have been added
   *   to the ajax response, FALSE otherwise.
   */
  protected static function addFormErrorsToAjaxResponse(AjaxResponse $response, FormStateInterface $form_state) {
    if ($errors = $form_state->getErrors()) {
      $display = '';
      $status_messages = ['#type' => 'status_messages'];
      if ($messages = \Drupal::service('renderer')->renderRoot($status_messages)) {
        $display = '<div class="views-messages">' . $messages . '</div>';
      }
      $options = [
        'dialogClass' => 'views-ui-dialog',
        'width' => '50%',
      ];
 
      // Attach the library necessary for using the OpenModalDialogCommand and
      // set the attachments for this Ajax response.
      $status_messages['#attached']['library'][] = 'core/drupal.dialog.ajax';
      $response->setAttachments($status_messages['#attached']);
 
      $response->addCommand(new OpenModalDialogCommand(t('Error Messages'), $display, $options));
 
      return TRUE;
    }
    return FALSE;
  }
 
  /**
   * Prepares the translation for the referenced entity, in case of being on a
   * entity translate page the target translation will added to the entity if
   * not yet present, otherwise the target entity translation will be returned.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $referenced_entity
   * @param int $delta
   *   The order of this item in the array of sub-elements (0, 1, 2, etc.).
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface
   */
  protected function prepareTranslation(ContentEntityInterface $referenced_entity, $delta, $parents, FormStateInterface $form_state) {
    // Add translation page.
    if (($source_language = $form_state->get(['content_translation', 'source'])) && ($target_language = $form_state->get(['content_translation', 'target']))) {
      $src_langcode = $source_language->getId();
      $target_langcode = $target_language->getId();
 
      if ($referenced_entity->hasTranslation($target_langcode)) {
        return $referenced_entity->getTranslation($target_langcode);
      }
      else {
        // If the referenced entity does not have the source language we are
        // translating the main entity from then use its current language as
        // source.
        if (!$referenced_entity->hasTranslation($src_langcode)) {
          $source_language = $this->getTranslationSourceLanguage($referenced_entity, $delta, $parents, $form_state);
          $src_langcode = $source_language->getId();
        }
 
        // Checks whether the entity is enabled for content translation.
        if ($this->contentTranslationManager->isEnabled($referenced_entity->getEntityTypeId(), $referenced_entity->bundle())) {
          $this->contentTranslationController->prepareTranslation($referenced_entity, $source_language, $target_language);
          $translation = $referenced_entity->getTranslation($target_language->getId());
          $metadata = $this->contentTranslationManager->getTranslationMetadata($translation);
          $metadata->setSource($src_langcode);
          return $translation;
        }
        else {
          return $this->translateEntity($referenced_entity, $src_langcode, $target_langcode);
        }
      }
    }
 
    // Target langcode is the langcode of the entity being displayed at the
    // moment. It might be as well the target translation if the entity is being
    // translated at the moment. We retrieve the language from the from object
    // as if the entity reference field is not translatable the parent entity
    // will be loaded for the current field in its default language even if
    // the parent entity form is shown in a different language.
    $form_lang_code = $form_state->getFormObject()->getFormLangcode($form_state);
    $target_langcode = $form_lang_code;
 
    if ($referenced_entity->hasTranslation($target_langcode)) {
      return $referenced_entity->getTranslation($target_langcode);
    }
    else {
      $source_language = $this->getTranslationSourceLanguage($referenced_entity, $delta, $parents, $form_state);
 
      // Checks whether the entity is enabled for content translation.
      if ($this->contentTranslationManager->isEnabled($referenced_entity->getEntityTypeId(), $referenced_entity->bundle())) {
        $target_language = $this->languageManager->getLanguage($target_langcode);
 
        $this->contentTranslationController->prepareTranslation($referenced_entity, $source_language, $target_language);
        $translation = $referenced_entity->getTranslation($target_language->getId());
        $metadata = $this->contentTranslationManager->getTranslationMetadata($translation);
        $metadata->setSource($source_language->getId());
        return $translation;
      }
      else {
        return $this->translateEntity($referenced_entity, $source_language->getId(), $target_langcode);
      }
    }
 
  }
 
  /**
   * Determines the source language from which the entity will be translated.
   *
   * A helper method for ::prepareTranslation.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $referenced_entity
   * @param $delta
   * @param $parents
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @return LanguageInterface
   */
  protected function getTranslationSourceLanguage(ContentEntityInterface $referenced_entity, $delta, $parents, FormStateInterface $form_state) {
    return $referenced_entity->language();
  }
 
  /**
   * Translates an entity.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity to translate.
   * @param $src_langcode
   *   The source language code.
   * @param $target_langcode
   *   The target language code.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface
   *   The translated entity.
   */
  protected function translateEntity(ContentEntityInterface $entity, $src_langcode, $target_langcode) {
    $source_translation = $entity->getTranslation($src_langcode);
    $target_translation = $entity->addTranslation($target_langcode, $source_translation->toArray());
    // TODO find a better way.
    if (method_exists($target_translation, 'setCreatedTime')) {
      $target_translation->setCreatedTime(REQUEST_TIME);
    }
    // TODO find a better way.
    if (method_exists($target_translation, 'setAuthor')) {
      /** @var \Drupal\user\UserInterface $user */
      $user = $this->entityTypeManager->getStorage('user')->load($this->currentUser->id());
      $target_translation->setAuthor($user);
    }
 
    // Make sure we do not inherit the affected status from the source values.
    if ($target_translation->getEntityType()->isRevisionable()) {
      $target_translation->setRevisionTranslationAffected(NULL);
    }
 
    return $target_translation;
  }
 
  /**
   * Adds the remove button to the given form element.
   *
   * @param array $element
   * @param array $form
   */
  protected function addRemoveButton(array &$element, array $form) {
    if ($this->isCardinalityUnlimited()) {
      $field_name = $this->fieldDefinition->getName();
      $parents = $element['details_element']['#parents'];
      $id_parts = $parents;
      $id_parts[] = 'entity-reference-inline-row';
      $id_prefix = Html::cleanCssIdentifier(implode('-', $parents));
      $element['#row_id'] = $id_prefix;
 
      $element['remove'] = [
        '#type' => 'submit',
        '#name' => $id_prefix . '-remove',
        '#value' => $this->t('Remove this item'),
        '#attributes' => ['class' => ['field-remove-submit', 'field-remove-submit-' . strtr($field_name, ['_' => '-'])]],
        '#limit_validation_errors' => [array_merge($parents, [$field_name])],
        '#submit' => [[get_class($this), 'removeSubmit']],
        '#ajax' => [
          'callback' => [get_class($this), 'removeAjax'],
          'effect' => 'fade',
          'row_id' => $element['#row_id'],
          'table_id' => $this->getEntityReferenceFieldTableId($form),
        ],
        '#validate' => [],
        '#weight' => 1000,
      ];
    }
  }
 
  /**
   * Checks whether the cardinality of the field is unlimited.
   *
   * @return bool
   */
  protected function isCardinalityUnlimited() {
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
    return $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED;
  }
 
  /**
   * Adds entity meta information, which will be later used by
   * ::loadEntityFromMetaInformation in ::massageFormValues to load the entity.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   * @param $element
   */
  protected function addEntityMetaInformation(ContentEntityInterface $entity, &$element) {
    $id_field_name = $entity->getEntityType()->getKey('id');
    $element[$id_field_name] = [
      '#type' => 'hidden',
      '#value' => $entity->id()
    ];
  }
 
  /**
   * Loads the entity from the given meta information as added by
   * ::addEntityMetaInformation.
   *
   * @param $values
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface|null
   */
  protected function loadEntityFromMetaInformation($values) {
    $id_field_name = $this->getReferencedEntityType()->getKey('id');
    $entity = NULL;
    if (isset($values[$id_field_name])) {
      $entity = $this->getReferencedEntityStorage()->load($values[$id_field_name]);
    }
    return $entity;
  }
 
  /**
   * {@inheritdoc}
   */
  public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
    // Allow access to the items for ::massageFormValues().
    $this->extractFormValuesFieldItemList = $items;
    parent::extractFormValues($items, $form, $form_state);
    $this->extractFormValuesFieldItemList = NULL;
 
    // Flag the parent entity with the language for which we have edited the
    // entity in order to check only this language for translation changes on
    // save in the pre-save of the field item.
    // @see \Drupal\entity_reference_inline\Plugin\Field\FieldType\EntityReferenceInlineItem::preSave()
    $parent = $items->getEntity();
    // This method is called also when we are on the field config form, so we
    // have to explicitly check that we are on a content entity form.
    if (!isset($parent->inlineEditedLangcode) && ($form_object = $form_state->getFormObject()) && $form_object instanceof ContentEntityFormInterface) {
      $parent->inlineEditedLangcode = $form_object->getFormLangcode($form_state);
    }
  }
 
  /**
   * {@inheritdoc}
   *
   * We extract the information from the submitted values needed to rebuild the
   * referenced entities and then return the newly built entities instead of
   * the values of their fields.
   */
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
    $form_langcode = $form_state->getFormObject()->getFormLangcode($form_state);
 
    foreach ($values as $delta => &$delta_values) {
      if (isset($delta_values['remove'])) {
        unset($delta_values['remove']);
      }
      $original_delta = $delta_values['_original_delta'];
 
      // Retrieve the sub-form.
      if (isset($form[$this->fieldDefinition->getName()])) {
        $element_form = &$form[$this->fieldDefinition->getName()]['widget'][$original_delta];
      }
      // The field name will not be present in the form structure on the field
      // settings for its default value.
      elseif (isset($form['widget'])) {
        $element_form = &$form['widget'][$original_delta];
      }
 
      $element_form = $element_form['details_element'];
 
      // Load the entity.
      if (isset($this->extractFormValuesFieldItemList)) {
        $entity = $this->extractFormValuesFieldItemList[$original_delta]->entity;
      }
      else {
        // This should never happen.
        @trigger_error('The entity should be loaded from the field items instead from the meta information.', E_USER_DEPRECATED);
        $entity = $this->loadEntityFromMetaInformation($delta_values);
      }
 
      // ContentEntityForm::buildEntity is cloning the main form entity and
      // setting the submitted values on the cloned entity, so that the
      // original one is not altered and on form rebuild the form is rebuild
      // based on the form values and new field items are added. Entity
      // references are not cloned and this is why we clone the entity here
      // before setting it on the cloned field, this way the whole structure
      // will be cloned.
      if (!$entity->isNew()) {
        // We have to ensure that if an entity is reused inside the form state
        // that at all the places we'll be using the same entity object
        // reference instead of having different object references with the same
        // values mapped. This widget doesn't take care of ensuring that a
        // reused entity will be assigned the same form values - this has to be
        // taken care of from the widget extending from the current one and
        // offering this ability.
        $entity_type_id = $entity->getEntityTypeId();
        $entity_id = $entity->id();
        if (!$this->getBuiltEntity($entity_type_id, $entity_id)) {
          $this->setBuiltEntity(clone $entity);
        }
        $entity = $this->getBuiltEntity($entity_type_id, $entity_id);
      }
      else {
        $entity = clone $entity;
      }
 
      // Process entity translation and language.
      if ($this->getReferencedEntityType()->isTranslatable()) {
        // If we've created a new entity with a previous form language code and
        // now the form language code is changed then we want only to create the
        // entity for the language for which we are going to save the form.
        // The form language could be changed e.g. through the LanguageWidget.
        if ($entity->isNew() && ($entity->language()->getId() != $form_langcode) && (count($entity->getTranslationLanguages()) == 1)) {
          $entity_values = $entity->toArray();
          $entity_values[$this->getReferencedEntityType()->getKey('langcode')] = $form_langcode;
          // The bundle structure must be flat e.g. bundle=>bundle_type instead
          // as returned by toArray - bundle=>[0 => [target_id => bundle_type]].
          if ($bundle_field_name = $this->getReferencedEntityType()->getKey('bundle')) {
            $entity_values[$bundle_field_name] = $entity->bundle();
          }
          $entity = $this->getReferencedEntityStorage()->create($entity_values);
        }
        else {
          // If the entity is new it should be already in the correct translation!
          $entity = $this->prepareTranslation($entity, $original_delta, $form['#parents'], $form_state);
        }
      }
 
      // Build the entity.
      $this->buildEntity($delta_values, $element_form, $form_state, $entity);
 
      // Remove all the values which are entity fields, leave only the rest,
      // such as '_original_delta' and '_weight'.
      foreach ($entity as $name => $field) {
        unset($delta_values[$name]);
      }
      $delta_values['entity'] = $entity;
    }
 
    return $values;
  }
 
  /**
   * Builds an updated entity object based upon the submitted form values.
   *
   * For building the updated entity object the submitted form values are
   * copied to entity properties and all the specified entity builders for
   * copying form values to entity properties are invoked.
   *
   * @param array $delta_values
   *   The submitted delta form values produced by the widget.
   * @param array $element_form
   *   The form structure of the delta element, a sub-element of a larger form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity mapped to the delta values. This parameter is given by
   *   reference to allow extending the class and exchanging the entity object.
   */
  protected function buildEntity(array &$delta_values, array $element_form, FormStateInterface $form_state, ContentEntityInterface &$entity) {
    $this->mapDeltaFormValuesToEntity($delta_values, $element_form, $form_state, $entity);
 
    // Invoke all specified builders for copying form values to entity
    // properties. Here we create a dedicated form state containing only the
    // entity specific form values, as there are entity builders such
    // ContentTranslationHandler::entityFormEntityBuild, which expect that the
    // value are at the first level and does not search by field parents like
    // WidgetBase does.
    if (isset($element_form['#entity_builders'])) {
      $entity_form_state = new FormState();
      $entity_form_state->setValues($delta_values);
      $entity_form_state->setStorage($form_state->getStorage());
      $entity_form_state->setFormObject($form_state->getFormObject());
      $entity_form_state->setValidationComplete($form_state->isValidationComplete());
      $entity_form_state->setSubmitHandlers($form_state->getSubmitHandlers());
      $entity_form_state->setUserInput($form_state->getUserInput());
 
      // Add the form display to the form state as it might be needed by the
      // entity builders.
      $entity_form_display = $this->getFormDisplay($entity->getEntityTypeId(), $entity->bundle());
      /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
      $form_object = $form_state->getFormObject();
      $main_form_display = $form_object->getFormDisplay($form_state);
      $form_object->setFormDisplay($entity_form_display, $entity_form_state);
 
      foreach ($element_form['#entity_builders'] as $function) {
        call_user_func_array($function, [$entity->getEntityTypeId(), $entity, &$element_form, &$entity_form_state]);
      }
      // Replace the delta values with the ones from the entity form state, as
      // they could have changed in some of the entity builders functions.
      $delta_values = $entity_form_state->getValues();
 
      // As the entity builders might've altered the form state storage we have
      // to set back the updated storage to the parent form state.
      $form_state->setStorage($entity_form_state->getStorage());
      $form_object->setFormDisplay($main_form_display, $form_state);
 
      // A form builder might have altered the user input so we have to set it
      // back.
      $form_state->setUserInput($entity_form_state->getUserInput());
    }
 
    // Validate the generated entity. This shoudl be done after the entity
    // builders have run.
    $this->doValidate($element_form, $form_state, $entity);
 
    // Flag the entity with the language for which we have edited the entity in
    // order to check only this language for translation changes on save in the
    // pre-save of the field item.
    // @see \Drupal\entity_reference_inline\Plugin\Field\FieldType\EntityReferenceInlineItem::preSave()
    $entity->inlineEditedLangcode = $entity->language()->getId();
  }
 
  /**
   * Processes the delta form values and maps them to the corresponding entity.
   *
   * @param array $delta_values
   *   The submitted delta form values produced by the widget.
   * @param array $element_form
   *   The form structure of the delta element, a sub-element of a larger form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity mapped to the delta values. This parameter is given by
   *   reference to allow extending the class and exchanging the entity object.
   */
  protected function mapDeltaFormValuesToEntity(array &$delta_values, array $element_form, FormStateInterface $form_state, ContentEntityInterface &$entity) {
    $entity_form_display = $this->getFormDisplay($entity->getEntityTypeId(), $entity->bundle());
 
    // First, extract values from widgets for the form display mode 'edit_by_user_text'.
    $extracted = $entity_form_display->extractFormValues($entity, $element_form, $form_state);
 
    // Then extract the values of fields that are not rendered through widgets,
    // by simply copying from top-level form values. This leaves the fields
    // that are not being edited within this form untouched.
    foreach ($delta_values as $name => $delta_value) {
      if ($entity->hasField($name) && !isset($extracted[$name])) {
        $entity->set($name, $delta_value);
      }
    }
 
    // Update the entity changed timestamp after it has been validated if a new
    // translation was added to it.
    if ($entity->isNewTranslation()) {
      // Checks whether the entity is enabled for content translation.
      if ($this->contentTranslationManager->isEnabled($entity->getEntityTypeId(), $entity->bundle())) {
        $metadata = $this->contentTranslationManager->getTranslationMetadata($entity);
        $metadata->setChangedTime(REQUEST_TIME);
      }
      elseif ($entity instanceof EntityChangedInterface) {
        $entity->setChangedTime(REQUEST_TIME);
      }
    }
  }
 
  /**
   * Validates the generated entity.
   *
   * @param array $element_form
   *   The form structure of the delta element, a sub-element of a larger form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity to be validated.
   */
  protected function doValidate(array $element_form, FormStateInterface $form_state, ContentEntityInterface $entity) {
    // Only validate during form validation running and skip during submit!
    if (!$form_state->isValidationComplete()) {
      // Backup the errors so that we can estimate later  the new one added
      // by the validation and modify them.
      $errors_previous = $form_state->getErrors();
      $this->getFormDisplay($entity->getEntityTypeId(), $entity->bundle())->validateFormValues($entity, $element_form, $form_state);
      $errors_after = $form_state->getErrors();
 
      // Estimate the new errors and modify them.
      $new_errors = array_diff_key($errors_after, $errors_previous);
 
      foreach ($new_errors as $name => &$message) {
        $message = $this->t('Referenced entity (:ref_entity_type) ":ref_entity": @message', [':ref_entity_type' => $entity->getEntityType()->getLabel(), ':ref_entity' => $entity->label(), '@message' => $message]);
      }
 
      // TODO ensure the corresponding entities are marked with an error class.
      // Restore the errors.
      $form_state->clearErrors();
      foreach (array_merge($errors_previous, $new_errors) as $name => $message) {
        $form_state->setErrorByName($name, $message);
      }
    }
  }
 
  /**
   * Submission handler for the "Remove item" button.
   */
  public static function removeSubmit(array $form, FormStateInterface $form_state) {
    $button = $form_state->getTriggeringElement();
 
    // Go one level up in the form, to the element to be removed.
    $delta_parents = array_slice($button['#array_parents'], 0, -1);
    $delta = end($delta_parents);
    $field_parents = array_slice($button['#array_parents'], 0, -2);
 
    $element = NestedArray::getValue($form, $field_parents);
    $field_name = $element['#field_name'];
    $parents = $element['#field_parents'];
 
    // Flag the delta item as removed.
    $field_state = static::getWidgetState($parents, $field_name, $form_state);
    $field_state['deltas_removed'] = isset($field_state['deltas_removed']) ? $field_state['deltas_removed'] : [];
    $field_state['deltas_removed'][$delta] = TRUE;
    static::setWidgetState($parents, $field_name, $form_state, $field_state);
 
    $form_state->setRebuild();
  }
 
  /**
   * Ajax callback for the "Remove item" button.
   */
  public static function removeAjax(array $form, FormStateInterface $form_state) {
    $button = $form_state->getTriggeringElement();
    $table_id = '#' . $button['#ajax']['table_id'];
    $row_id = '#' . $button['#ajax']['row_id'];
 
    $response = new AjaxResponse();
    $response->addCommand(new RemoveCommand($row_id));
    $response->addCommand(new RestripeCommand($table_id));
    return $response;
  }
 
  /**
   * Returns the referenced entity type.
   *
   * @return \Drupal\Core\Entity\EntityTypeInterface|null
   */
  protected function getReferencedEntityType() {
    if (!isset($this->referencedEntityType)) {
      $target_type = $this->getFieldSetting('target_type');
      $this->referencedEntityType = $this->entityTypeManager->getDefinition($target_type);
    }
    return $this->referencedEntityType;
  }
 
  /**
   * Returns the entity storage for the referenced entity type.
   *
   * @return \Drupal\Core\Entity\EntityStorageInterface
   */
  protected function getReferencedEntityStorage() {
    $target_type = $this->getFieldSetting('target_type');
    return $this->entityTypeManager->getStorage($target_type);
  }
 
  /**
   * Returns the EntityFormDisplay for the referenced entity.
   *
   * @param string $entity_type_id
   *   The entity type id.
   * @param string $bundle
   *   The entity bundle.
   *
   * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
   *   The entity form display for the given entity.
   */
  protected function getFormDisplay($entity_type_id, $bundle) {
    if ($this->getReferencedEntityType()->hasKey('bundle')) {
      $form_modes_bundles = $this->getSetting('form_modes_bundles');
      // Fallback to the default.
      $form_mode = isset($form_modes_bundles[$bundle]) ? $form_modes_bundles[$bundle] : $this->getSetting('form_mode');
    }
    else {
      $form_mode = $this->getSetting('form_mode');
    }
 
    $entity_form_display_id = implode('.', [$entity_type_id, $bundle, $form_mode]);
 
    if (!isset($this->referencedEntityFormDisplays[$entity_form_display_id])) {
      $this->referencedEntityFormDisplays[$entity_form_display_id] = EntityFormDisplay::load($entity_form_display_id);
 
      // The form display will not be present if not explicitly created, so
      // at this place we do it just like the core does and create it on the
      // fly without saving it.
      if (!isset($this->referencedEntityFormDisplays[$entity_form_display_id])) {
        $this->referencedEntityFormDisplays[$entity_form_display_id] = EntityFormDisplay::create([
          'targetEntityType' => $entity_type_id,
          'bundle' => $bundle,
          'mode' => $form_mode,
          'status' => TRUE,
        ]);
      }
    }
 
    return $this->referencedEntityFormDisplays[$entity_form_display_id];
  }
 
  /**
   * Returns the form display options.
   *
   * @param $bundle
   *   (optional) The bundle for which to retrieve the form mode options.
   * @return array
   *   List of form modes.
   */
  protected function getFormModeOptions($bundle = NULL) {
    $target_type = $this->getFieldSetting('target_type');
    $form_modes = isset($bundle) ? $this->entityDisplayRepository->getFormModeOptionsByBundle($target_type, $bundle) : $this->entityDisplayRepository->getFormModeOptions($target_type);
 
    return $form_modes;
  }
 
  /**
   * Get the new element return mode from the widget state.
   *
   * @return string
   *   The new element return mode.
   */
  protected static function getNewElementReturnMode($parents, $field_name, FormStateInterface $form_state) {
    $field_state = static::getWidgetState($parents, $field_name, $form_state);
    return $field_state['new_element_return_mode'];
  }
 
  /**
   * Returns the entity object that is used in case of reused entities.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param mixed $entity_id
   *   The entity ID.
   *
   * @return \Drupal\Core\Entity\EntityInterface|NULL
   *   The built entity object or NULL if not set.
   */
  protected function getBuiltEntity($entity_type_id, $entity_id) {
    return isset(static::$builtEntities[$entity_type_id][$entity_id]) ? static::$builtEntities[$entity_type_id][$entity_id] : NULL;
  }
 
  /**
   * Adds the entity object to the built entity list.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   */
  protected function setBuiltEntity(EntityInterface $entity) {
    static::$builtEntities[$entity->getEntityTypeId()][$entity->id()] = $entity;
  }
 
}

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

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