external_entities-8.x-2.x-dev/src/FieldMapper/FieldMapperBase.php

src/FieldMapper/FieldMapperBase.php
<?php

namespace Drupal\external_entities\FieldMapper;

use Drupal\Component\Plugin\ConfigurableInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Plugin\PluginDependencyTrait;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\DataReferenceInterface;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\external_entities\Entity\ExternalEntityTypeInterface;
use Drupal\external_entities\Form\XnttSubformState;
use Drupal\external_entities\Plugin\PluginDebugTrait;
use Drupal\external_entities\Plugin\PluginFormTrait;
use Drupal\external_entities\PropertyMapper\PropertyMapperInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * A base class for field mappers.
 */
abstract class FieldMapperBase extends PluginBase implements FieldMapperInterface, ConfigurableInterface, PluginFormInterface {

  use PluginDependencyTrait;
  use PluginFormTrait;
  use PluginDebugTrait;

  /**
   * Default property mapper plugin id to use.
   */
  const DEFAULT_PROPERTY_MAPPER = 'simple';

  /**
   * The external entity type this field mapper is configured for.
   *
   * @var \Drupal\external_entities\Entity\ExternalEntityTypeInterface
   */
  protected $externalEntityType;

  /**
   * The field name this field mapper is configured for.
   *
   * @var string
   */
  protected $fieldName;

  /**
   * The field definition of the mapped field.
   *
   * @var \Drupal\Core\Field\FieldDefinitionInterface
   */
  protected $fieldDefinition;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The property mapper manager.
   *
   * @var \Drupal\Component\Plugin\PluginManagerInterface
   */
  protected $propertyMapperManager;

  /**
   * The logger channel factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerChannelFactory;

  /**
   * The field mapper plugin logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannel
   */
  protected $logger;

  /**
   * Array of categories of definitions of properties, keyed by their names.
   *
   * First level keys are property categories as defined in
   * \Drupal\external_entities\FieldMapper\FieldMapperInterface by *_PROPERTIES
   * constants.
   * Second level keys are property names.
   *
   * @var \Drupal\Core\TypedData\DataDefinitionInterface[][]
   */
  protected $properties;

  /**
   * Constructs a FieldMapperBase object.
   *
   * The configuration parameters is expected to contain the external entity
   * type (key ExternalEntityTypeInterface::XNTT_TYPE_PROP) and the field name
   * (key 'field_name') this field mapper is instanciated for.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The identifier for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   *   The string translation service.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger channel factory.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager.
   * @param \Drupal\Component\Plugin\PluginManagerInterface $property_mapper_manager
   *   The property mapper plugin manager.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    $plugin_definition,
    TranslationInterface $string_translation,
    LoggerChannelFactoryInterface $logger_factory,
    EntityFieldManagerInterface $entity_field_manager,
    PluginManagerInterface $property_mapper_manager,
  ) {
    $this->debugLevel = $configuration['debug_level'] ?? NULL;
    $this->setConfiguration($configuration);
    $configuration = $this->getConfiguration();
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->setStringTranslation($string_translation);
    $this->loggerChannelFactory = $logger_factory;
    $this->logger = $this->loggerChannelFactory->get('xntt_field_mapper_' . $plugin_id);
    $this->entityFieldManager = $entity_field_manager;
    $this->propertyMapperManager = $property_mapper_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('string_translation'),
      $container->get('logger.factory'),
      $container->get('entity_field.manager'),
      $container->get('plugin.manager.external_entities.property_mapper')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    $dependencies = [];
    $properties = $this->getProperties();
    foreach ($properties as $property_name => $property) {
      $mapper = $this->getPropertyMapper($property_name);
      if (!empty($mapper)) {
        $dependencies = NestedArray::mergeDeep(
          $dependencies,
          $this->getPluginDependencies($mapper)
        );
      }
    }
    return $dependencies;
  }

  /**
   * {@inheritdoc}
   */
  public function getLabel() :string {
    $plugin_definition = $this->getPluginDefinition();
    return $plugin_definition['label'];
  }

  /**
   * {@inheritdoc}
   */
  public function getDescription() :string {
    $plugin_definition = $this->getPluginDefinition();
    return $plugin_definition['description'] ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldTypes() :array {
    $plugin_definition = $this->getPluginDefinition();
    // @todo Allow extensions to alter the list.
    // Use case: a new derived field type that should be supported like its
    // ancestor (ie. a new "text_new" field type derived from the "text" field
    // type should be able to say that it is compatible with field mappers
    // supporting the "text" field type).
    // See also FieldMapperManager::getCompatibleFieldMappers().
    return $plugin_definition['field_types'] ?? ['*'];
  }

  /**
   * {@inheritdoc}
   */
  public function getConfiguration() {
    return $this->configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration) {
    $configuration = NestedArray::mergeDeepArray(
      [
        $this->defaultConfiguration(),
        $configuration,
      ],
      TRUE
    );
    if (!empty($configuration[ExternalEntityTypeInterface::XNTT_TYPE_PROP])
        && $configuration[ExternalEntityTypeInterface::XNTT_TYPE_PROP] instanceof ExternalEntityTypeInterface
    ) {
      $this->externalEntityType = $configuration[ExternalEntityTypeInterface::XNTT_TYPE_PROP];
    }
    unset($configuration[ExternalEntityTypeInterface::XNTT_TYPE_PROP]);
    if (!empty($configuration['field_name'])) {
      $this->fieldName = $configuration['field_name'];
      unset($configuration['field_name']);
    }
    $this->configuration = $configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'property_mappings' => [],
    ];
  }

  /**
   * Returns default values to create a property mapper.
   *
   * This configuration includes internal elements that must not be serialized
   * such as the reference to current external entity.
   *
   * @param string $property_name
   *   Name of the corresponding property.
   *
   * @return array
   *   The property mapper default configuration with internal elements.
   */
  protected function getPropertyMapperDefaultConfiguration(
    string $property_name,
  ) :array {
    $external_entity_type = $this->getExternalEntityType();
    return [
      ExternalEntityTypeInterface::XNTT_TYPE_PROP => $external_entity_type,
      'field_name' => $this->fieldName,
      'property_name' => $property_name,
      'main_property' => ($this->getMainPropertyName() == $property_name),
      'required_field' => in_array($this->fieldName, $external_entity_type->getRequiredFields()),
      'debug_level' => $this->getDebugLevel(),
    ];
  }

  /**
   * Get the external entity type being operated for.
   *
   * @return \Drupal\external_entities\Entity\ExternalEntityTypeInterface
   *   The external entity type definition.
   */
  protected function getExternalEntityType() :ExternalEntityTypeInterface {
    return $this->externalEntityType;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldDefinition() :?FieldDefinitionInterface {
    if (empty($this->fieldDefinition)) {
      if (empty($this->fieldName)) {
        $this->logger->warning('FieldMapperBase::getFieldDefinition(): Missing field name.');
        return NULL;
      }
      elseif (empty($this->externalEntityType)) {
        $this->logger->warning('FieldMapperBase::getFieldDefinition(): Missing external entity reference.');
        return NULL;
      }

      $fields = $this->getExternalEntityType()->getMappableFields();

      $this->fieldDefinition = $fields[$this->fieldName] ?? NULL;
    }
    return $this->fieldDefinition;
  }

  /**
   * {@inheritdoc}
   */
  public function getProperties(
    int $category = FieldMapperInterface::MAPPABLE_PROPERTIES,
  ) :array {
    if (empty($this->properties)) {
      $this->initProperties();
    }

    return $this->properties[$category];
  }

  /**
   * Initialize internal "properties" member.
   */
  protected function initProperties() {
    $field_definition = $this->getFieldDefinition();
    $this->properties = [
      static::NON_MAPPABLE_PROPERTIES => [],
      static::GENERAL_PROPERTIES => [],
      static::SPECIFIC_PROPERTIES => [],
      static::MAPPABLE_PROPERTIES => [],
      static::ALL_PROPERTIES => [],
    ];
    if (empty($field_definition)) {
      $this->logger->warning('FieldMapperBase::initProperties(): Missing field definition.');
      return;
    }
    $properties = $field_definition
      ->getFieldStorageDefinition()
      ->getPropertyDefinitions();
    foreach ($properties as $property_name => $property) {
      $property_class = $property->getClass();
      if (!$property->isReadOnly()
          && !$property->isComputed()
          && (is_subclass_of($property_class, PrimitiveInterface::class)
            || is_subclass_of($property_class, DataReferenceInterface::class))
      ) {
        $this->properties[static::GENERAL_PROPERTIES][$property_name] = $property;
      }
      else {
        $this->properties[static::NON_MAPPABLE_PROPERTIES][$property_name] = $property;
      }
    }
    $this->properties[static::MAPPABLE_PROPERTIES] =
      $this->properties[static::GENERAL_PROPERTIES]
      + $this->properties[static::SPECIFIC_PROPERTIES];
    $this->properties[static::ALL_PROPERTIES] =
      $this->properties[static::MAPPABLE_PROPERTIES]
      + $this->properties[static::NON_MAPPABLE_PROPERTIES];
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyMappings() :array {
    return $this->configuration['property_mappings'] ?? [];
  }

  /**
   * {@inheritdoc}
   */
  public function getMainPropertyName() :string|null {
    $field_definition = $this->getFieldDefinition();
    if (empty($field_definition)) {
      $this->logger->warning('FieldMapperBase::getMainPropertyName(): Missing field definition.');
      return NULL;
    }

    return $field_definition
      ->getFieldStorageDefinition()
      ->getMainPropertyName();
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyMapping(?string $property_name = NULL) :array {
    if (empty($property_name)) {
      // Get main property.
      $property_name = $this->getMainPropertyName();
    }
    return $this->configuration['property_mappings'][$property_name] ?? [];
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyMapper(?string $property_name = NULL) :?PropertyMapperInterface {
    $property_mapper = NULL;
    if (!isset($property_name)) {
      $property_name = $this->getMainPropertyName();
    }
    $mapping_def = $this->getPropertyMapping($property_name);
    if (!empty($mapping_def['id'])) {
      $config = NestedArray::mergeDeep(
        $this->getPropertyMapperDefaultConfiguration($property_name),
        ($mapping_def['config'] ?? [])
      );

      $property_mapper = $this->propertyMapperManager->createInstance(
        $mapping_def['id'],
        $config
      );
      if ($debug_level = $this->getDebugLevel()) {
        $property_mapper->setDebugLevel($debug_level);
      }
    }
    return $property_mapper;
  }

  /**
   * {@inheritdoc}
   */
  public function getMappedSourceFieldName(
    ?string $property_name = NULL,
  ) :?string {
    $property_name ??= $this->getMainPropertyName();
    $source_id_field = NULL;
    $property_mapper = $this->getPropertyMapper($property_name);
    if ($property_mapper) {
      $source_id_field = $property_mapper->getMappedSourceFieldName();
    }
    return $source_id_field;
  }

  /**
   * {@inheritdoc}
   */
  public function extractFieldValuesFromRawData(
    array $raw_data,
    array &$context = [],
  ) :?array {
    if (2 <= $this->getDebugLevel()) {
      $this->logger->debug(
        "FieldMapperBase::extractFieldValuesFromRawData(): extracting Drupal field data from:\n@raw_data",
        [
          '@raw_data' => print_r($raw_data, TRUE),
        ]
      );
    }

    $field_array = [];
    $empty_deltas = [];
    // Get field mappable properties.
    $properties = $this->getProperties();
    $main_property_name = $this->getMainPropertyName();
    if ($main_property_name && in_array($main_property_name, $properties)) {
      // Make sure we got something for the main property.
      $mapper = $this->getPropertyMapper($main_property_name);
      if (empty($mapper)) {
        // No mapper for the main property, stop here.
        return [];
      }
      // We process the main property here, no need to re-process it.
      unset($properties[$main_property_name]);
      $property_values = $mapper->extractPropertyValuesFromRawData(
        $raw_data,
        $context
      );
      if (2 <= $this->getDebugLevel()) {
        $this->logger->debug(
          "FieldMapperBase::extractFieldValuesFromRawData(): values from main property '@main_property' using mapper '@mapper':\n@property_values",
          [
            '@main_property' => $main_property_name,
            '@mapper' => $mapper->getPluginId(),
            '@property_values' => print_r($property_values, TRUE),
          ]
        );
      }

      foreach ($property_values as $delta => $property_value) {
        $field_array[$delta][$main_property_name] = $property_value;
        if (!isset($property_value)) {
          $empty_deltas[$delta] = $delta;
        }
      }
    }

    foreach ($properties as $property_name => $property) {
      $mapper = $this->getPropertyMapper($property_name);
      if (!empty($mapper)) {
        $property_values = $mapper->extractPropertyValuesFromRawData(
          $raw_data,
          $context
        );
        if (2 <= $this->getDebugLevel()) {
          $this->logger->debug(
            "FieldMapperBase::extractFieldValuesFromRawData(): values from property '@property' using mapper '@mapper':\n@property_values",
            [
              '@property' => $property_name,
              '@mapper' => $mapper->getPluginId(),
              '@property_values' => print_r($property_values, TRUE),
            ]
          );
        }
        foreach ($property_values as $delta => $property_value) {
          $field_array[$delta][$property_name] = $property_value;
        }
      }
    }
    // Remove empty field values.
    $field_array = array_filter(
      $field_array,
      function ($delta) use ($empty_deltas) {
        return !array_key_exists($delta, $empty_deltas);
      },
      ARRAY_FILTER_USE_KEY
    );
    if (2 <= $this->getDebugLevel()) {
      $this->logger->debug(
        "FieldMapperBase::extractFieldValuesFromRawData(): extracted Drupal field data:\n@field_array",
        [
          '@field_array' => print_r($field_array, TRUE),
        ]
      );
    }

    return $field_array;
  }

  /**
   * {@inheritdoc}
   */
  public function addFieldValuesToRawData(
    array $field_values,
    array &$raw_data,
    array &$context,
  ) :void {
    // Get field mappable properties.
    $properties = $this->getProperties();
    foreach ($properties as $property_name => $property) {
      $mapper = $this->getPropertyMapper($property_name);
      if (!empty($mapper)) {
        // Convert [delta][property] structure to [property][delta] structure,
        // so that each property can be set in the raw data all at once in one
        // setter operation.
        $propery_values = [];
        foreach ($field_values as $field_value) {
          if (array_key_exists($property_name, $field_value)) {
            $propery_values[] = $field_value[$property_name];
          }
        }
        $mapper->addPropertyValuesToRawData(
          $propery_values,
          $raw_data,
          $context
        );
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function couldReverseFieldMapping() :bool {
    $could_reverse = TRUE;
    // Get field mappable properties.
    $properties = $this->getProperties();
    foreach ($properties as $property_name => $property) {
      $mapper = $this->getPropertyMapper($property_name);
      if (!empty($mapper) && !$mapper->couldReversePropertyMapping()) {
        $could_reverse = FALSE;
        break;
      }
    }
    return $could_reverse;
  }

  /**
   * Form constructor.
   *
   * @param array $form
   *   An associative array containing the initial structure of the plugin form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form. Calling code should pass on a subform
   *   state created through
   *   \Drupal\Core\Form\SubformState::createForSubform().
   *
   * @return array
   *   The form structure.
   */
  public function buildConfigurationForm(
    array $form,
    FormStateInterface $form_state,
  ) {
    $form['#type'] ??= 'container';

    $general_properties = $this->getProperties(static::GENERAL_PROPERTIES);
    foreach ($general_properties as $property => $property_def) {
      $form['property_mappings'][$property] = [
        '#type' => 'details',
        '#title' => $this->t('Mapping for %property', ['%property' => $property_def->getLabel()]),
        '#open' => TRUE,
        '#attributes' => [
          'id' => ($form['#attributes']['id'] ??= uniqid('fm', TRUE))
          . '_'
          . $property,
        ],
      ];
      $this->buildPropertyMapperSelectForm($form, $form_state, $property, $property_def);
      $this->buildPropertyMapperConfigForm($form, $form_state, $property, $property_def);
    }
    // Hide title if we got only one property.
    if ((1 === count($general_properties))
      && empty($this->getProperties(static::SPECIFIC_PROPERTIES))
    ) {
      $form['property_mappings'][key($general_properties)]['#type'] = 'container';
    }
    return $form;
  }

  /**
   * Build a form element for selecting a field property mapper.
   *
   * @param array &$form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param string $property_name
   *   The property name.
   * @param \Drupal\Core\TypedData\DataDefinitionInterface $property_definition
   *   The property definition.
   */
  public function buildPropertyMapperSelectForm(
    array &$form,
    FormStateInterface $form_state,
    string $property_name,
    DataDefinitionInterface $property_definition,
  ) {
    $field_type = $this->getFieldDefinition()->getType();
    $property_mappers = $this
      ->propertyMapperManager
      ->getCompatiblePropertyMappers($field_type, $property_name);
    if (empty($property_mappers)) {
      return;
    }

    $main_property = ($this->getMainPropertyName() == $property_name);
    $current_property_mapper_def = $this->getPropertyMapping($property_name);
    $current_property_mapper_id =
      $current_property_mapper_def['id']
      ?? ($main_property ? static::DEFAULT_PROPERTY_MAPPER : '');
    foreach ($property_mappers as $property_mapper_id => $definition) {
      if ($current_property_mapper_id == $property_mapper_id) {
        $config = NestedArray::mergeDeep(
          $this->getPropertyMapperDefaultConfiguration($property_name),
          ($current_property_mapper_def['config'] ?? [])
        );
      }
      else {
        $config = $this->getPropertyMapperDefaultConfiguration($property_name);
      }
      $property_mapper = $this->propertyMapperManager->createInstance($property_mapper_id, $config);
      $property_mapper_options[$property_mapper_id] = $property_mapper->getLabel();
    }

    $form['property_mappings'][$property_name]['id'] = [
      '#type' => 'select',
      '#title' => $this->t('Mapping type:'),
      '#default_value' => $current_property_mapper_id,
      '#options' => $property_mapper_options,
      '#sort_options' => TRUE,
      '#empty_value' => $main_property ? NULL : '',
      '#empty_option' => $main_property ? NULL : $this->t('Not mapped'),
      '#required' => $main_property,
      '#wrapper_attributes' => ['class' => ['xntt-inline']],
      '#attributes' => [
        'class' => ['xntt-field'],
        'autocomplete' => 'off',
      ],
      '#label_attributes' => ['class' => ['xntt-label']],
      '#ajax' => [
        'callback' => [get_class($this), 'buildAjaxParentSubForm'],
        'wrapper' => ($form['#attributes']['id'] ??= uniqid('fm', TRUE))
        . '_'
        . $property_name,
        'method' => 'replaceWith',
        'effect' => 'fade',
      ],
    ];
  }

  /**
   * Build a form element for configuring a field property mapping.
   *
   * @param array &$form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param string $property_name
   *   The property name.
   * @param \Drupal\Core\TypedData\DataDefinitionInterface $property_definition
   *   The property definition.
   */
  public function buildPropertyMapperConfigForm(
    array &$form,
    FormStateInterface $form_state,
    string $property_name,
    DataDefinitionInterface $property_definition,
  ) {
    $is_main_property = ($this->getMainPropertyName() == $property_name);
    $property_mapper_def = $this->getPropertyMapping($property_name);
    $property_mapper_id =
      $form_state->getValue(
        ['property_mappings', $property_name, 'id']
      )
      ?? $property_mapper_def['id']
      ?? ($is_main_property ? static::DEFAULT_PROPERTY_MAPPER : '');
    if (empty($property_mapper_id)) {
      // No property mapper, do not change the form and stop here.
      return;
    }
    elseif ($property_mapper_id == ($property_mapper_def['id'] ?? '')) {
      // Get property mapper instance form field mapper.
      $property_mapper = $this->getPropertyMapper($property_name);
    }
    else {
      $config = $this->getPropertyMapperDefaultConfiguration($property_name);
      // Generate a new property mapper instance.
      $property_mapper = $this->propertyMapperManager->createInstance(
        $property_mapper_id,
        $config
      );
    }

    $property_mapper_form_state = XnttSubformState::createForSubform(
      ['property_mappings', $property_name, 'config'],
      $form,
      $form_state
    );
    $config_wrapper_id =
      ($form['property_mappings'][$property_name]['#attributes']['id']
      ?? uniqid('fm', TRUE))
      . '_config';
    $form['property_mappings'][$property_name]['config'] = [
      '#type' => 'container',
      // If #parents is not set here, sub-element names will not follow the tree
      // structure.
      '#parents' => [...($form['#parents'] ?? []), 'property_mappings', $property_name, 'config'],
      '#attributes' => [
        'id' => $config_wrapper_id,
      ],
    ];
    $form['property_mappings'][$property_name]['config'] =
      $property_mapper->buildConfigurationForm(
        $form['property_mappings'][$property_name]['config'],
        $property_mapper_form_state
      );
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(
    array &$form,
    FormStateInterface $form_state,
  ) {
    // Validate property mapper settings.
    $general_properties = $this->getProperties(static::GENERAL_PROPERTIES);
    $this->validatePropertyForms($form, $form_state, $general_properties);
  }

  /**
   * Validate property mapper settings.
   *
   * @param array &$form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $properties
   *   An array of property definitions with configuration forms to validate.
   */
  protected function validatePropertyForms(
    array &$form,
    FormStateInterface $form_state,
    array $properties,
  ) {
    $external_entity_type = $this->getExternalEntityType();
    if (empty($external_entity_type) || empty($this->getFieldDefinition())) {
      return;
    }
    foreach ($properties as $property_name => $property_def) {
      $property_mapper_id = $form_state->getValue(
        ['property_mappings', $property_name, 'id']
      );
      if ($property_mapper_id) {
        $config = $this->getPropertyMapperDefaultConfiguration($property_name);
        $property_mapper = $this->propertyMapperManager->createInstance(
          $property_mapper_id,
          $config
        );
        if ($property_mapper instanceof PluginFormInterface) {
          $property_mapper_form_state = XnttSubformState::createForSubform(
            ['property_mappings', $property_name, 'config'],
            $form,
            $form_state
          );
          $property_mapper->validateConfigurationForm($form['property_mappings'][$property_name]['config'], $property_mapper_form_state);
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    // Submit and save property mapper configs.
    $general_properties = $this->getProperties(static::GENERAL_PROPERTIES);
    $this->submitPropertyForms($form, $form_state, $general_properties);
    $this->setConfiguration($form_state->getValues());
  }

  /**
   * Submit and save property mapper configs.
   *
   * @param array &$form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $properties
   *   An array of property definitions with configuration forms to submit.
   */
  protected function submitPropertyForms(
    array &$form,
    FormStateInterface $form_state,
    array $properties,
  ) {
    $property_mappers = $form_state->getValue('property_mappings', []);
    foreach ($properties as $property_name => $property_def) {
      $property_mapper_id = $form_state->getValue(
        ['property_mappings', $property_name, 'id']
      );
      if ($property_mapper_id) {
        $config = $this->getPropertyMapperDefaultConfiguration($property_name);
        $property_mapper = $this->propertyMapperManager->createInstance(
          $property_mapper_id,
          $config
        );
        if ($property_mapper instanceof PluginFormInterface) {
          $property_mapper_form_state = XnttSubformState::createForSubform(
            ['property_mappings', $property_name, 'config'],
            $form,
            $form_state
          );
          $property_mapper->submitConfigurationForm(
            $form['property_mappings'][$property_name]['config'],
            $property_mapper_form_state
          );
          $property_mappers[$property_name] = [
            'id' => $property_mapper_id,
            'config' => $property_mapper->getConfiguration(),
          ];
        }
      }
    }
    $form_state->setValue('property_mappings', $property_mappers);
  }

}

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

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