external_entities-8.x-2.x-dev/src/Entity/ExternalEntityType.php

src/Entity/ExternalEntityType.php
<?php

namespace Drupal\external_entities\Entity;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\external_entities\DataAggregator\DataAggregatorInterface;
use Drupal\external_entities\Event\ExternalEntitiesEvents;
use Drupal\external_entities\Event\ExternalEntityGetMappableFieldsEvent;
use Drupal\external_entities\FieldMapper\FieldMapperInterface;
use Psr\Log\LoggerInterface;

/**
 * Defines the external_entity_type entity.
 *
 * A note on plugin managers and plugin configs: external entity types configs
 * store the config of all their plugins. It is the "static" plugin config. Once
 * instanciated, a plugin may alter its own config, and especially its
 * sub-plugin configs. It is the "dynamic" plugin config. Therefore, "static"
 * and "dynamic" config may be desynchronized. To avoid this problem, the
 * external entity type object will try to provide the dynamic config of a
 * plugin first if available and otherwise use the static one. When the ::get()
 * or ::set() methods are used, it will synchronize configs.
 *
 * @ConfigEntityType(
 *   id = "external_entity_type",
 *   label = @Translation("External entity type"),
 *   handlers = {
 *     "list_builder" = "Drupal\external_entities\ExternalEntityTypeListBuilder",
 *     "form" = {
 *       "add" = "Drupal\external_entities\Form\ExternalEntityTypeForm",
 *       "edit" = "Drupal\external_entities\Form\ExternalEntityTypeForm",
 *       "delete" = "Drupal\external_entities\Form\ExternalEntityTypeDeleteForm",
 *     }
 *   },
 *   config_prefix = "external_entity_type",
 *   admin_permission = "administer external entity types",
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "label",
 *   },
 *   links = {
 *     "edit-form" = "/admin/structure/external-entity-types/{external_entity_type}",
 *     "delete-form" = "/admin/structure/external-entity-types/{external_entity_type}/delete",
 *   },
 *   config_export = {
 *     "id",
 *     "label",
 *     "label_plural",
 *     "base_path",
 *     "description",
 *     "content_class",
 *     "read_only",
 *     "debug_level",
 *     "field_mappers",
 *     "field_mapping_notes",
 *     "data_aggregator",
 *     "data_aggregator_notes",
 *     "language_settings",
 *     "persistent_cache_max_age",
 *     "annotation_entity_type_id",
 *     "annotation_bundle_id",
 *     "annotation_field_name",
 *     "annotation_inherited_fields",
 *     "locks"
 *  }
 * )
 */
class ExternalEntityType extends ConfigEntityBase implements ConfigurableExternalEntityTypeInterface, EntityWithPluginCollectionInterface {

  use StringTranslationTrait;

  /**
   * Indicates that entities of this external entity type should not be cached.
   */
  const CACHE_DISABLED = 0;

  /**
   * Default field mapper plugin id.
   */
  const DEFAULT_FIELD_MAPPER = 'generic';

  /**
   * Default data aggregator plugin id.
   */
  const DEFAULT_DATA_AGGREGATOR = 'single';

  /**
   * Default data aggregator identifier used in collection.
   */
  const DEFAULT_DATA_AGGREGATOR_KEY = 'default';

  /**
   * The external entity type ID.
   *
   * @var string
   */
  protected $id;

  /**
   * The human-readable name of the external entity type.
   *
   * @var string
   */
  protected $label;

  /**
   * The plural human-readable name of the external entity type.
   *
   * @var string
   */
  protected $label_plural;

  /**
   * The derived external entity base path.
   *
   * @var string
   */
  protected $base_path;

  /**
   * The external entity type description.
   *
   * @var string
   */
  protected $description;

  /**
   * The external entity class used for content instances.
   *
   * @var string
   */
  protected $content_class = ExternalEntity::class;

  /**
   * Whether or not entity types of this external entity type are read only.
   *
   * @var bool
   */
  protected $read_only;

  /**
   * Debug level of current instance.
   *
   * @var int
   */
  protected $debug_level;

  /**
   * Array of field mapper plugin settings.
   *
   * @var array
   */
  protected $field_mappers = [];

  /**
   * Field mapping administrative notes.
   *
   * @var string
   */
  protected $field_mapping_notes = '';

  /**
   * Array a mappable fields of this instance.
   *
   * @var \Drupal\Core\Field\FieldDefinitionInterface[]
   */
  protected $mappableFields = [];

  /**
   * Field mapper plugin collection.
   *
   * @var \Drupal\Core\Plugin\DefaultLazyPluginCollection
   */
  protected $fieldMapperPluginCollection;

  /**
   * Data aggregator plugin settings.
   *
   * @var array
   */
  protected $data_aggregator = [];

  /**
   * Data aggregator plugin collection.
   *
   * @var \Drupal\Core\Plugin\DefaultLazyPluginCollection
   */
  protected $dataAggregatorCollection;

  /**
   * Data aggregator administrative notes.
   *
   * @var string
   */
  protected $data_aggregator_notes = '';

  /**
   * Language settings.
   *
   * The value of key 'overrides' should be empty if there are no language
   * overrides. Also, language override arrays, if they are set, should always
   * contain a non empty array for at least one of the keys 'field_mappers' or
   * 'data_aggregator'.
   *
   * @var array
   *
   *   An array with the following structure:
   *   @code
   *   [
   *     'overrides' => [
   *        'fr' => [
   *          // Optional field mapping override.
   *          'field_mappers' => [
   *            // Field mapper configs by fields.
   *          ],
   *          // Optional data aggregator override.
   *          'data_aggregator' => [
   *            // Data aggregator config.
   *          ],
   *        ],
   *        'es' => [
   *          ...
   *        ],
   *        ...
   *     ],
   *   ]
   *   @endcode
   */
  protected $language_settings = [];

  /**
   * Max age entities of this external entity type may be persistently cached.
   *
   * @var int
   */
  protected $persistent_cache_max_age = self::CACHE_DISABLED;

  /**
   * The annotations entity type id.
   *
   * @var string
   */
  protected $annotation_entity_type_id;

  /**
   * The annotations bundle id.
   *
   * @var string
   */
  protected $annotation_bundle_id;

  /**
   * The field this external entity is referenced from by the annotation entity.
   *
   * @var string
   */
  protected $annotation_field_name;

  /**
   * Local cache for the annotation field.
   *
   * @var array
   *
   * @see ExternalEntityType::getAnnotationField()
   */
  protected $annotationField;

  /**
   * The list of fields the external entity should inherit from its annotation.
   *
   * @var string[]
   */
  protected $annotation_inherited_fields = [];

  /**
   * Locks configuration for this external entity type.
   *
   * @var array
   */
  protected $locks = [];

  /**
   * {@inheritdoc}
   */
  public function getPluginCollections() {
    return [
      'dataAggregatorCollection' => $this->getDataAggregatorPluginCollection(),
      'fieldMapperPluginCollection' => $this->getFieldMapperPluginCollection(),
    ];
  }

  /**
   * Returns the field mapper plugin collection.
   *
   * @return \Drupal\Core\Plugin\DefaultLazyPluginCollection
   *   The field mapper plugin collection.
   */
  public function getFieldMapperPluginCollection() {
    // If the field mapper collection has not been loaded yet, load it from
    // static config.
    if (!isset($this->fieldMapperPluginCollection)) {
      $fm_configs = [];
      $mappable_fields = $this->getMappableFields();
      // Default mapping.
      foreach ($mappable_fields as $field_name => $field_def) {
        if (!empty($this->field_mappers[$field_name]['id'])) {
          $fm_configs[$field_name] =
            ['id' => $this->field_mappers[$field_name]['id']]
            + NestedArray::mergeDeep(
              $this->getFieldMapperDefaultConfiguration($field_name),
              $this->field_mappers[$field_name]['config'] ?? []
            );
        }
      }
      // Load language field mapping overrides if needed.
      $language_manager = $this->languageManager();
      if (!empty($this->language_settings)
        && $language_manager->isMultilingual()
      ) {
        $languages = $language_manager->getLanguages();
        $default_langcode = $language_manager->getDefaultLanguage()->getId();
        foreach ($languages as $langcode => $lang) {
          if ($langcode === $default_langcode) {
            continue;
          }
          foreach ($mappable_fields as $field_name => $field_def) {
            if (!empty($this->language_settings['overrides'][$langcode]['field_mappers'][$field_name]['id'])) {
              $fm_configs[$langcode . '-' . $field_name] =
                ['id' => $this->language_settings['overrides'][$langcode]['field_mappers'][$field_name]['id']]
                + NestedArray::mergeDeep(
                  $this->getFieldMapperDefaultConfiguration($field_name),
                  $this->language_settings['overrides'][$langcode]['field_mappers'][$field_name]['config']
                );
            }
          }
        }
      }
      // Set collection.
      $this->fieldMapperPluginCollection = new DefaultLazyPluginCollection(
        \Drupal::service('plugin.manager.external_entities.field_mapper'),
        $fm_configs
      );
    }
    return $this->fieldMapperPluginCollection;
  }

  /**
   * Returns the data aggregator plugin collection.
   *
   * @return \Drupal\Core\Plugin\DefaultLazyPluginCollection
   *   The data aggregator plugin collection.
   */
  public function getDataAggregatorPluginCollection() {
    if (!isset($this->dataAggregatorCollection)) {
      // Set default data aggregator plugin settings.
      $da_config = [
        static::DEFAULT_DATA_AGGREGATOR_KEY =>
        ['id' => $this->getDataAggregatorId()]
        + NestedArray::mergeDeep(
          $this->getDataAggregatorDefaultConfiguration(),
          $this->getDataAggregatorConfig()
        ),
      ];
      // Load language data aggregator overrides if needed.
      $language_manager = $this->languageManager();
      if (!empty($this->language_settings)
        && $language_manager->isMultilingual()
      ) {
        $languages = $language_manager->getLanguages();
        $default_langcode = $language_manager->getDefaultLanguage()->getId();
        foreach ($languages as $langcode => $lang) {
          if ($langcode === $default_langcode) {
            continue;
          }
          if (!empty($this->language_settings['overrides'][$langcode]['data_aggregator']['id'])) {
            $da_id = $this->language_settings['overrides'][$langcode]['data_aggregator']['id'];
            $da_config[$langcode] =
            ['id' => $da_id]
            + NestedArray::mergeDeep(
              $this->getDataAggregatorDefaultConfiguration(),
              $this->language_settings['overrides'][$langcode]['data_aggregator']['config'] ?? []
            );
          }
        }
      }
      $this->dataAggregatorCollection = new DefaultLazyPluginCollection(
        \Drupal::service('plugin.manager.external_entities.data_aggregator'),
        $da_config
      );
    }
    return $this->dataAggregatorCollection;
  }

  /**
   * {@inheritdoc}
   */
  public function createDuplicate() {
    $clone = parent::createDuplicate();
    // Clear local plugin instances to make sure they won't be shared.
    $clone->dataAggregatorCollection = NULL;
    $clone->fieldMapperPluginCollection = NULL;
    return $clone;
  }

  /**
   * {@inheritdoc}
   */
  public function getLogger() :LoggerInterface {
    return $this->logger($this->id() ?: 'external_entities');
  }

  /**
   * {@inheritdoc}
   */
  public function getLabel() :string {
    return $this->label ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function getPluralLabel() :string {
    return $this->label_plural ?? '';
  }

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

  /**
   * {@inheritdoc}
   */
  public function isReadOnly() :bool {
    return (bool) $this->read_only;
  }

  /**
   * {@inheritdoc}
   */
  public function getDebugLevel() :int {
    return $this->debug_level ?? 0;
  }

  /**
   * {@inheritdoc}
   */
  public function setDebugLevel(int $debug_level = 1) :self {
    $this->debug_level = $debug_level;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getMappableFields(bool $reload = FALSE) :array {
    if (empty($this->mappableFields) || $reload) {
      $this->mappableFields = [];

      $derived_entity_type = $this->getDerivedEntityType();

      $fields = [];
      if (!empty($derived_entity_type)) {
        $derived_entity_type_id = $derived_entity_type->id();
        $fields = $this
          ->entityFieldManager()
          ->getFieldDefinitions(
            $derived_entity_type_id,
            $derived_entity_type_id
          );
      }
      $excluded_fields = [
        ExternalEntityInterface::ANNOTATION_FIELD,
        // Hide language fields that are managed on backend.
        'langcode',
        'default_langcode',
      ];
      $this->mappableFields = array_filter(
        $fields,
        function (FieldDefinitionInterface $field) use ($excluded_fields) {
          // Annotation field as well as language field are not mappable and
          // have a dedicated management.
          return !in_array($field->getName(), $excluded_fields)
            && !$field->isComputed();
        }
      );

      // Allow other modules to alter mappable fields.
      $event = new ExternalEntityGetMappableFieldsEvent(
        $this,
        $this->mappableFields
      );
      \Drupal::service('event_dispatcher')->dispatch(
        $event,
        ExternalEntitiesEvents::GET_MAPPABLE_FIELDS
      );

      $this->mappableFields = $event->getMappableFields();
    }

    return $this->mappableFields;
  }

  /**
   * {@inheritdoc}
   */
  public function getEditableFields(bool $reload = FALSE) :array {
    $fields = $this->getMappableFields($reload);
    $editable_fields = [];
    if ($this->isReadOnly()) {
      $editable_fields = array_fill_keys(
        array_keys($fields),
        FALSE
      );
    }
    else {
      foreach (array_keys($fields) as $field_name) {
        // @todo The lang code is not available here while, depending on the
        // language, the field may or may not be editable. How to fix that?
        $field_mapper = $this->getFieldMapper($field_name);
        if (empty($field_mapper)
          || !$field_mapper->couldReverseFieldMapping()
        ) {
          $editable_fields[$field_name] = FALSE;
        }
        else {
          $editable_fields[$field_name] = TRUE;
        }
      }
    }
    return $editable_fields;
  }

  /**
   * {@inheritdoc}
   */
  public function getRequiredFields() :array {
    $fields = [
      'id',
      'title',
    ];

    return $fields;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldMapperDefaultConfiguration(
    string $field_name,
  ) :array {
    return [
      // Allow the mapper to call back into the entity type (e.g., to fetch
      // additional data like field lists from the remote service).
      ExternalEntityTypeInterface::XNTT_TYPE_PROP => $this,
      'field_name' => $field_name,
      'debug_level' => $this->getDebugLevel(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldMapperId(
    string $field_name,
    ?string $langcode = NULL,
  ) :string {
    // Get field mapper according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || !isset($this->language_settings['overrides'][$langcode]['field_mappers'][$field_name])
    ) {
      $plugin_key = $field_name;
      $field_mappers = &$this->field_mappers;
    }
    else {
      $plugin_key = $langcode . '-' . $field_name;
      $field_mappers = &$this->language_settings['overrides'][$langcode]['field_mappers'];
    }

    // Update id from collection if available.
    if (isset($this->fieldMapperPluginCollection)
      && ($this->fieldMapperPluginCollection->has($plugin_key))
    ) {
      $field_mappers[$field_name]['id'] =
        $this->fieldMapperPluginCollection->get($plugin_key)->getPluginId();
    }
    return $field_mappers[$field_name]['id'] ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function setFieldMapperId(
    string $field_name,
    string $field_mapper_id,
    ?string $langcode = NULL,
  ) :self {
    // Make sure field is mappable.
    $mappable_fields = $this->getMappableFields();
    if (!empty($field_name) && !empty($mappable_fields[$field_name])) {
      // Get field mapper according to langcode.
      $default_langcode = \Drupal::config('system.site')->get('langcode');
      if (empty($langcode) || ($langcode === $default_langcode)) {
        $plugin_key = $field_name;
        $this->field_mappers[$field_name] ??= [];
        $field_mapper = &$this->field_mappers[$field_name];
      }
      else {
        $plugin_key = $langcode . '-' . $field_name;
        $this->language_settings['overrides'][$langcode]['field_mappers'][$field_name] ??= [];
        $field_mapper = &$this->language_settings['overrides'][$langcode]['field_mappers'][$field_name];
      }

      // Only update if changed.
      if ($field_mapper_id != ($field_mapper['id'] ?? '')) {
        // Update collection.
        if (isset($this->fieldMapperPluginCollection)) {
          $this->fieldMapperPluginCollection->removeInstanceId($plugin_key);
          if (!empty($field_mapper_id)) {
            $this->fieldMapperPluginCollection->addInstanceId(
              $plugin_key,
              ['id' => $field_mapper_id]
              + $this->getFieldMapperDefaultConfiguration($field_name)
            );
          }
        }
        // Initialize the field mapper setting array if not already.
        if (!empty($field_mapper_id)) {
          $field_mapper = [
            'id' => $field_mapper_id,
            'config' => [],
          ];
        }
      }
      elseif (isset($this->fieldMapperPluginCollection)
        && !$this->fieldMapperPluginCollection->has($plugin_key)
        && !empty($field_mapper_id)
      ) {
        // If the id is not changed but got a collection, we still need to
        // update the collection.
        $this->fieldMapperPluginCollection->addInstanceId(
          $plugin_key,
          ['id' => $field_mapper_id]
          + $this->getFieldMapperDefaultConfiguration($field_name)
        );
      }
    }
    else {
      $this->getLogger()->warning(
        'Tried to set a field mapper on a non-mappable field ('
        . $field_name
        . ').'
      );
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isFieldMappingOverridden(
    string $langcode,
    ?string $field_name = NULL,
  ) :bool {
    if (!empty($field_name)) {
      return isset($this->language_settings['overrides'][$langcode]['field_mappers'][$field_name]);
    }
    else {
      return !empty($this->language_settings['overrides'][$langcode]['field_mappers']);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldMapper(
    string $field_name,
    ?string $langcode = NULL,
  ) :FieldMapperInterface|null {
    if (empty($field_name)) {
      $this->getLogger()->warning(
        'No field machine name provided!'
      );
      return NULL;
    }
    // Make sure field is mappable.
    $mappable_fields = $this->getMappableFields();
    if (empty($mappable_fields[$field_name])) {
      $this->getLogger()->warning(
        "Given field ($field_name) is not mappable!"
      );
      return NULL;
    }

    // Get field mapper according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || !isset($this->language_settings['overrides'][$langcode]['field_mappers'][$field_name])
    ) {
      $plugin_key = $field_name;
      $field_mapper = &$this->field_mappers[$field_name];
    }
    else {
      $plugin_key = $langcode . '-' . $field_name;
      $field_mapper = &$this->language_settings['overrides'][$langcode]['field_mappers'][$field_name];
    }
    if (empty($field_mapper['id'])) {
      // Not mapped.
      return NULL;
    }

    // Initialize plugin collection if needed and test if we got a mapping.
    if (!$this->getFieldMapperPluginCollection()->has($plugin_key)) {
      return NULL;
    }

    return $this->fieldMapperPluginCollection->get($plugin_key);
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldMapperConfig(
    string $field_name,
    ?string $langcode = NULL,
  ) :array {
    // Get field mapper according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || !isset($this->language_settings['overrides'][$langcode]['field_mappers'][$field_name])
    ) {
      $plugin_key = $field_name;
      $field_mapper = &$this->field_mappers[$field_name];
    }
    else {
      $plugin_key = $langcode . '-' . $field_name;
      $field_mapper = &$this->language_settings['overrides'][$langcode]['field_mappers'][$field_name];
    }

    // Update static config if a plugin was loaded.
    if (isset($this->fieldMapperPluginCollection)
        && $this->fieldMapperPluginCollection->has($plugin_key)
    ) {
      $field_mapper['config'] =
        $this->fieldMapperPluginCollection->get($plugin_key)->getConfiguration();
      // Remove id field added by the DefaultLazyPluginCollection.
      unset($field_mapper['config']['id']);
      // Remove external entity type member.
      unset($field_mapper['config'][ExternalEntityTypeInterface::XNTT_TYPE_PROP]);
      // Remove field name member.
      unset($field_mapper['config']['field_name']);
    }
    return $field_mapper['config'] ?? [];
  }

  /**
   * {@inheritdoc}
   */
  public function setFieldMapperConfig(
    string $field_name,
    array $field_mapper_config,
    ?string $langcode = NULL,
  ) :self {
    // Get field mapper according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || !isset($this->language_settings['overrides'][$langcode]['field_mappers'][$field_name])
    ) {
      $plugin_key = $field_name;
      $field_mapper = &$this->field_mappers[$field_name];
    }
    else {
      $plugin_key = $langcode . '-' . $field_name;
      $field_mapper = &$this->language_settings['overrides'][$langcode]['field_mappers'][$field_name];
    }

    // Make sure field is mappable.
    $mappable_fields = $this->getMappableFields();
    if (!empty($field_name)
      && !empty($mappable_fields[$field_name])
      && !empty($field_mapper['id'])
    ) {
      // Update plugin config.
      if (isset($this->fieldMapperPluginCollection)
        && ($this->fieldMapperPluginCollection->has($plugin_key))
      ) {
        $this->fieldMapperPluginCollection->setInstanceConfiguration(
          $plugin_key,
          ['id' => $field_mapper['id']]
          + NestedArray::mergeDeep(
            $this->getFieldMapperDefaultConfiguration($field_name),
            $field_mapper_config
          )
        );
      }
      // Also update local config in case plugin instances are cleared.
      $field_mapper['config'] = $field_mapper_config;
    }
    elseif (empty($field_mapper['id'])) {
      $this->getLogger()->warning(
        'Tried to configure mapping on a field ('
        . $field_name
        . ') that has no field mapper set.'
      );
    }
    else {
      $this->getLogger()->warning(
        'Tried to configure mapping on a non-mappable field ('
        . $field_name
        . ').'
      );
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldMappingNotes() :string {
    return $this->field_mapping_notes ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function setFieldMappingNotes(
    string $field_mapping_notes,
  ) :self {
    $this->field_mapping_notes = $field_mapping_notes;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getDataAggregatorDefaultConfiguration() :array {
    return [
      // Allow the aggregator to call back into the entity type.
      ExternalEntityTypeInterface::XNTT_TYPE_PROP => $this,
      'debug_level' => $this->getDebugLevel(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getDataAggregatorId(?string $langcode = NULL) :string {
    // Get data aggregator according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || empty($this->language_settings['overrides'][$langcode]['data_aggregator']['id'])
    ) {
      $data_aggregator_id = $this->data_aggregator['id'] ?? NULL;
    }
    else {
      $data_aggregator_id = $this->language_settings['overrides'][$langcode]['data_aggregator']['id'];
    }
    return $data_aggregator_id ?? static::DEFAULT_DATA_AGGREGATOR;
  }

  /**
   * {@inheritdoc}
   */
  public function setDataAggregatorId(
    string $data_aggregator_id,
    ?string $langcode = NULL,
  ) :self {

    // Get data aggregator according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode) || ($langcode === $default_langcode)) {
      $data_aggregator = &$this->data_aggregator;
      $plugin_key = static::DEFAULT_DATA_AGGREGATOR_KEY;
    }
    else {
      $data_aggregator = &$this->language_settings['overrides'][$langcode]['data_aggregator'];
      $plugin_key = $langcode;
    }

    // Only update if changed.
    if (!empty($data_aggregator_id)
      && ($data_aggregator_id != ($data_aggregator['id'] ?? ''))
    ) {
      // Update collection.
      if (isset($this->dataAggregatorCollection)) {
        $this->dataAggregatorCollection->removeInstanceId($plugin_key);
        $this->dataAggregatorCollection->addInstanceId(
          $plugin_key,
          ['id' => $data_aggregator_id]
          + $this->getDataAggregatorDefaultConfiguration()
        );
      }
      // Update static config.
      $data_aggregator['id'] = $data_aggregator_id;
      $data_aggregator['config'] = [];
    }
    elseif (isset($this->dataAggregatorCollection)
      && !$this->dataAggregatorCollection->has($plugin_key)
      && !empty($data_aggregator_id)
    ) {
      // If the id is not changed but got a collection, we still need to
      // update the collection.
      $this->dataAggregatorCollection->addInstanceId(
        $plugin_key,
        ['id' => $data_aggregator_id]
        + $this->getDataAggregatorDefaultConfiguration()
      );
    }

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isDataAggregatorOverridden(
    string $langcode,
  ) :bool {
    return !empty($this->language_settings['overrides'][$langcode]['data_aggregator']['id']);
  }

  /**
   * {@inheritdoc}
   */
  public function getDataAggregator(?string $langcode = NULL) :DataAggregatorInterface {
    // Get data aggregator according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || empty($this->language_settings['overrides'][$langcode]['data_aggregator']['id'])
    ) {
      $plugin_key = static::DEFAULT_DATA_AGGREGATOR_KEY;
    }
    else {
      $plugin_key = $langcode;
    }
    // Initialize plugin collection if needed.
    if (!isset($this->dataAggregatorCollection)) {
      $this->getPluginCollections();
    }
    $data_aggregator = $this->dataAggregatorCollection->get($plugin_key);
    return $data_aggregator;
  }

  /**
   * {@inheritdoc}
   */
  public function getDataAggregatorConfig(?string $langcode = NULL) :array {
    // Get data aggregator according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || empty($this->language_settings['overrides'][$langcode]['data_aggregator']['id'])
    ) {
      $plugin_key = static::DEFAULT_DATA_AGGREGATOR_KEY;
      $this->data_aggregator ??= [];
      $data_aggregator = &$this->data_aggregator;
    }
    else {
      $plugin_key = $langcode;
      $data_aggregator = &$this->language_settings['overrides'][$langcode]['data_aggregator'];
    }

    // Update static config if a plugin was loaded.
    if (isset($this->dataAggregatorCollection)
      && $this->dataAggregatorCollection->has($plugin_key)
    ) {
      $data_aggregator['config'] = $this->dataAggregatorCollection->get($plugin_key)->getConfiguration();
      // Remove id field added by the DefaultLazyPluginCollection.
      unset($data_aggregator['config']['id']);
    }
    return $data_aggregator['config'] ?? [];
  }

  /**
   * {@inheritdoc}
   */
  public function setDataAggregatorConfig(
    array $data_aggregator_config,
    ?string $langcode = NULL,
  ) :self {
    // Get data aggregator according to langcode.
    $default_langcode = \Drupal::config('system.site')->get('langcode');
    if (empty($langcode)
      || ($langcode === $default_langcode)
      || empty($this->language_settings['overrides'][$langcode]['data_aggregator']['id'])
    ) {
      $plugin_key = static::DEFAULT_DATA_AGGREGATOR_KEY;
      $this->data_aggregator ??= [];
      $data_aggregator = &$this->data_aggregator;
    }
    else {
      $plugin_key = $langcode;
      $data_aggregator = &$this->language_settings['overrides'][$langcode]['data_aggregator'];
    }

    if (!empty($data_aggregator['id'])) {
      // Check if we got a local plugin instance lodaded already.
      if (isset($this->dataAggregatorCollection)) {
        $new_plugin_config =
          ['id' => $data_aggregator['id']]
          + NestedArray::mergeDeep(
            $this->getDataAggregatorDefaultConfiguration(),
            $data_aggregator_config
          );
        $this->dataAggregatorCollection->setInstanceConfiguration(
          $plugin_key,
          $new_plugin_config
        );
      }
      // Update static config.
      $data_aggregator['config'] = $data_aggregator_config;
    }
    else {
      $this->getLogger()->warning(
        'Tried to set data aggregator config with no data aggregator set.'
      );
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getDataAggregatorNotes() :string {
    return $this->data_aggregator_notes ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function setDataAggregatorNotes(
    string $data_aggregator_notes,
  ) :self {
    $this->data_aggregator_notes = $data_aggregator_notes;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getLanguageSettings() :array {
    return $this->language_settings ?? [];
  }

  /**
   * {@inheritdoc}
   */
  public function setLanguageSettings(
    array $language_settings,
  ) :self {
    $this->language_settings = $language_settings;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    return Cache::mergeTags(
      Cache::mergeTags(parent::getCacheTags(), $this->getCacheTagsToInvalidate()),
      [
        $this->getEntityTypeId() . '_values',
        'entity_field_info',
      ]
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTagsToInvalidate() {
    return Cache::mergeTags(
      parent::getCacheTagsToInvalidate(),
      [
        $this->getEntityTypeId() . '_values:' . $this->id(),
      ]
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getPersistentCacheMaxAge() :int {
    return $this->persistent_cache_max_age;
  }

  /**
   * {@inheritdoc}
   */
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    parent::postSave($storage, $update);

    // Clear the entity type definitions cache so changes flow through to the
    // related entity types.
    $this->entityTypeManager()->clearCachedDefinitions();

    // Clear the router cache to prevent RouteNotFoundException errors caused
    // by the Field UI module.
    \Drupal::service('router.builder')->rebuild();

    // Rebuild local actions so that the 'Add field' action on the 'Manage
    // fields' tab appears.
    \Drupal::service('plugin.manager.menu.local_action')->clearCachedDefinitions();

    // Clear the static and persistent cache.
    $storage->resetCache();
    $derived_entity_type_id = $this->getDerivedEntityTypeId();
    if ($this->entityTypeManager()->hasDefinition($derived_entity_type_id)) {
      $this
        ->entityTypeManager()
        ->getStorage($derived_entity_type_id)
        ->resetCache();
    }

    $edit_link = $this->toLink($this->t('Edit entity type'), 'edit-form')->toString();
    if ($update) {
      $this->getLogger()->notice(
        'Entity type %label has been updated.',
        ['%label' => $this->label(), 'link' => $edit_link]
      );
    }
    else {
      // Notify storage to create the database schema.
      $entity_type = $this->entityTypeManager()->getDefinition($derived_entity_type_id);
      \Drupal::service('entity_type.listener')
        ->onEntityTypeCreate($entity_type);

      $this->getLogger()->notice(
        'Entity type %label has been added.',
        ['%label' => $this->label(), 'link' => $edit_link]
      );
    }

  }

  /**
   * {@inheritdoc}
   */
  public function delete() {
    // Remove references to this entity type from entity reference fields.
    $entity_type_id = $this->getDerivedEntityTypeId();
    $field_configs = $this->entityTypeManager()
      ->getStorage('field_config')
      ->loadByProperties(['field_type' => 'entity_reference']);
    $field_names_to_delete = [];
    foreach ($field_configs as $field_config) {
      $target_type = $field_config->getSetting('target_type');
      if (isset($target_type) && ($target_type === $entity_type_id)) {
        $field_name = $field_config->get('field_name');
        $field_names_to_delete[$field_name] = $field_name;
        $field_config->delete();
      }
    }
    foreach ($field_names_to_delete as $field_name) {
      // Aucune instance n'utilise ce stockage, on peut le supprimer.
      $field_storage = \Drupal::entityTypeManager()
        ->getStorage('field_storage_config')
        ->load("node.{$field_name}");
      if ($field_storage) {
        $field_storage->delete();
      }
    }
    parent::delete();
  }

  /**
   * {@inheritdoc}
   */
  public static function postDelete(EntityStorageInterface $storage, array $entities) {
    parent::postDelete($storage, $entities);
    \Drupal::service('entity_type.manager')->clearCachedDefinitions();
  }

  /**
   * {@inheritdoc}
   */
  public function getDerivedEntityTypeId() :string {
    return $this->id() ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function getDerivedEntityType() :ContentEntityTypeInterface|null {
    return $this->entityTypeManager()->getDefinition($this->getDerivedEntityTypeId(), FALSE);
  }

  /**
   * {@inheritdoc}
   */
  public function isAnnotatable() :bool {
    return $this->getAnnotationEntityTypeId()
      && $this->getAnnotationBundleId()
      && $this->getAnnotationFieldName();
  }

  /**
   * {@inheritdoc}
   */
  public function getAnnotationEntityTypeId() :string|null {
    return $this->annotation_entity_type_id;
  }

  /**
   * {@inheritdoc}
   */
  public function getAnnotationBundleId() :string|null {
    return $this->annotation_bundle_id ?: $this->getAnnotationEntityTypeId();
  }

  /**
   * {@inheritdoc}
   */
  public function getAnnotationFieldName() :string|null {
    return $this->annotation_field_name;
  }

  /**
   * Returns the entity field manager.
   *
   * @return \Drupal\Core\Entity\EntityFieldManagerInterface
   *   The entity field manager.
   */
  protected function entityFieldManager() :EntityFieldManagerInterface {
    return \Drupal::service('entity_field.manager');
  }

  /**
   * {@inheritdoc}
   */
  public function getAnnotationField() :FieldDefinitionInterface|null {
    if (!isset($this->annotationField) && $this->isAnnotatable()) {
      $field_definitions = $this->entityFieldManager()->getFieldDefinitions($this->getAnnotationEntityTypeId(), $this->getAnnotationBundleId());
      $annotation_field_name = $this->getAnnotationFieldName();
      if (!empty($field_definitions[$annotation_field_name])) {
        $this->annotationField = $field_definitions[$annotation_field_name];
      }
    }

    return $this->annotationField;
  }

  /**
   * {@inheritdoc}
   */
  public function getInheritedAnnotationFields() :array {
    $fields = [];

    if ($this->isAnnotatable()) {
      $annotation_field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->getAnnotationEntityTypeId(), $this->getAnnotationBundleId());
      return array_filter($annotation_field_definitions, function (FieldDefinitionInterface $field_definition) {
        return in_array($field_definition->getName(), $this->annotation_inherited_fields, TRUE);
      });
    }

    return $fields;
  }

  /**
   * {@inheritdoc}
   */
  public function getBasePath() :string {
    $base_path =
      $this->base_path
      ?? ''
      ?: str_replace('_', '-', strtolower($this->id ?? ''));
    return $base_path;
  }

  /**
   * {@inheritdoc}
   */
  public function getLocks() :?array {
    return $this->locks;
  }

  /**
   * Gets the logger for a specific channel.
   *
   * @param string $channel
   *   The name of the channel.
   *
   * @return \Psr\Log\LoggerInterface
   *   The logger for this channel.
   */
  protected function logger($channel) :LoggerInterface {
    return \Drupal::getContainer()->get('logger.factory')->get($channel);
  }

  /**
   * {@inheritdoc}
   */
  public function get($property_name) {
    // When using plugin collections, get static config data from their plugin
    // dynamic configs as their config may have been updated.
    if ('field_mappers' == $property_name) {
      // Update field mapping static config if needed.
      if (isset($this->fieldMapperPluginCollection)) {
        $field_mappers = [];
        foreach ($this->getMappableFields() as $field_name => $field_def) {
          if ($this->fieldMapperPluginCollection->has($field_name)) {
            // Set static config.
            $field_mappers[$field_name] = [
              'id' => $this->getFieldMapperId($field_name),
              'config' => $this->getFieldMapperConfig($field_name),
            ];
          }
        }
        // Reset current static config to use plugin settings.
        if (!empty($field_mappers)) {
          $this->field_mappers = $field_mappers;
        }
      }
    }
    elseif ('data_aggregator' == $property_name) {
      // Update data aggregator static config if needed.
      if (isset($this->dataAggregatorCollection)) {
        $da_config = $this->dataAggregatorCollection->get(static::DEFAULT_DATA_AGGREGATOR_KEY)->getConfiguration();
        // Remove id field added by the DefaultLazyPluginCollection.
        unset($da_config['id']);
        // Clear debug level as it is managed at the external entity type level.
        unset($da_config['debug_level']);
        $this->data_aggregator = [
          'id' => $this->data_aggregator['id'],
          'config' => $da_config,
        ];
      }
    }
    elseif ('language_settings' == $property_name) {
      // Make sure we got languages to work on.
      $language_manager = $this->languageManager();
      if ((!empty($this->language_settings)
          || isset($this->fieldMapperPluginCollection))
        && $language_manager->isMultilingual()
      ) {
        $languages = $language_manager->getLanguages();
        $default_langcode = $language_manager->getDefaultLanguage()->getId();
        foreach ($languages as $langcode => $lang) {
          if ($langcode === $default_langcode) {
            continue;
          }

          // Update field mapping override static config.
          if (isset($this->fieldMapperPluginCollection)) {
            // Loop on mappable fields and try to get a config override from
            // plugins if available.
            foreach ($this->getMappableFields() as $field_name => $field_def) {
              $plugin_key = $langcode . '-' . $field_name;
              if ($this->fieldMapperPluginCollection->has($plugin_key)) {
                $this->language_settings['overrides'][$langcode]['field_mappers'][$field_name] = [
                  'id' => $this->getFieldMapperId($field_name, $langcode),
                  'config' => $this->getFieldMapperConfig($field_name, $langcode),
                ];
              }
            }
          }

          // Now update data aggregator override static config.
          if (isset($this->dataAggregatorCollection)) {
            if ($this->dataAggregatorCollection->has($langcode)) {
              $this->language_settings['overrides'][$langcode]['data_aggregator'] = [];
              $data_aggregator = $this->dataAggregatorCollection->get($langcode);
              $da_config = $data_aggregator->getConfiguration();
              // Remove id used by the DefaultLazyPluginCollection.
              unset($da_config['id']);
              // Clear debug level.
              unset($da_config['debug_level']);
              $this->language_settings['overrides'][$langcode]['data_aggregator'] = [
                'id' => $data_aggregator->getPluginId(),
                'config' => $da_config,
              ];
            }
          }
        }
      }
    }
    return parent::get($property_name);
  }

  /**
   * {@inheritdoc}
   */
  public function set($property_name, $value) {
    // Update plugin dynamic configs.
    if ('field_mappers' == $property_name) {
      // Clear previous instances.
      if (isset($this->fieldMapperPluginCollection)) {
        foreach ($this->getFieldMapperPluginCollection() as $plugin_key => $field_mapper) {
          // Skip language overrides (collection id (here $field_name) starting
          // by a langcode followed by dash).
          if ((2 < strlen($plugin_key)) && ('-' == $plugin_key[2])) {
            continue;
          }
          $this->fieldMapperPluginCollection->removeInstanceId($plugin_key);
        }
      }
      // Add new instances.
      if (is_array($value)) {
        foreach ($value as $field_name => $field_mapper_settings) {
          $this->setFieldMapperId($field_name, $field_mapper_settings['id'] ?? '');
          if (!empty($field_mapper_settings['id'])) {
            $this->setFieldMapperConfig($field_name, $field_mapper_settings['config'] ?? []);
          }
        }
      }
    }
    elseif ('data_aggregator' == $property_name) {
      // Remove previous instance.
      if (isset($this->dataAggregatorCollection)) {
        $this->dataAggregatorCollection->removeInstanceId(static::DEFAULT_DATA_AGGREGATOR_KEY);
      }
      $this->setDataAggregatorId($value['id'] ?? '');
      if (!empty($value['id'])) {
        $this->setDataAggregatorConfig($value['config'] ?? []);
      }
    }
    elseif ('language_settings' == $property_name) {
      // Make sure we got languages to work on.
      $language_manager = $this->languageManager();
      if ($language_manager->isMultilingual()) {
        // Field mapping overrides, clear previous instances.
        if (isset($this->fieldMapperPluginCollection)) {
          foreach ($this->getFieldMapperPluginCollection() as $plugin_key => $field_mapper) {
            // Skip default language not starting by a langcode followed by
            // dash.
            if ((3 > strlen($plugin_key)) || ('-' !== $plugin_key[2])) {
              continue;
            }
            $this->fieldMapperPluginCollection->removeInstanceId($plugin_key);
          }
        }

        $languages = $language_manager->getLanguages();
        $default_langcode = $language_manager->getDefaultLanguage()->getId();
        foreach ($languages as $langcode => $lang) {
          if ($langcode === $default_langcode) {
            continue;
          }
          // Set new field mappers for overrides.
          $field_mapping_overrides =
            $this->language_settings['overrides'][$langcode]['field_mappers'] =
            $value['overrides'][$langcode]['field_mappers'] ?? [];
          foreach ($field_mapping_overrides as $field_name => $field_mapper_settings) {
            if (!empty($field_mapper_settings['id'])) {
              $plugin_key = $langcode . '-' . $field_name;
              $this->fieldMapperPluginCollection->addInstanceId(
                $plugin_key,
                ['id' => $field_mapper_settings['id']]
                + NestedArray::mergeDeep(
                  $this->getFieldMapperDefaultConfiguration($field_name),
                  $field_mapper_settings['config'] ?? []
                )
              );
            }
          }

          // Remove previous data aggregator override if one.
          if (isset($this->dataAggregatorCollection)) {
            $this->dataAggregatorCollection->removeInstanceId($langcode);
          }
          // Set new data aggregator for overrides.
          $data_aggregator_override = $value['overrides'][$langcode]['data_aggregator'] ?? [];
          if (!empty($data_aggregator_override['id'])) {
            $this->setDataAggregatorId($data_aggregator_override['id'], $langcode);
            $this->setDataAggregatorConfig($data_aggregator_override['config'] ?? [], $langcode);
          }
        }
      }
    }
    elseif (('dataAggregatorCollection' == $property_name)
        || ('fieldMapperPluginCollection' == $property_name)
    ) {
      // Don't set colletcions directly as we manage automatically their
      // synchronization with current instance config.
      return $this;
    }
    return parent::set($property_name, $value);
  }

  /**
   * {@inheritdoc}
   */
  public function __sleep(): array {
    // Prevent some properties from being serialized.
    return array_diff(parent::__sleep(), [
      'fieldMapperPluginCollection',
      'dataAggregatorCollection',
      'annotationField',
    ]);
  }

}

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

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