external_entities-8.x-2.x-dev/src/DataAggregator/DataAggregatorBase.php

src/DataAggregator/DataAggregatorBase.php
<?php

namespace Drupal\external_entities\DataAggregator;

use Drupal\Component\Plugin\ConfigurableInterface;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\NestedArray;
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\external_entities\Entity\ExternalEntityTypeInterface;
use Drupal\external_entities\Form\XnttSubformState;
use Drupal\external_entities\Plugin\ExternalEntities\StorageClient\FileClientInterface;
use Drupal\external_entities\Plugin\ExternalEntities\StorageClient\QueryLanguageClientInterface;
use Drupal\external_entities\Plugin\ExternalEntities\StorageClient\RestClientInterface;
use Drupal\external_entities\Plugin\PluginDebugTrait;
use Drupal\external_entities\Plugin\PluginFormTrait;
use Drupal\external_entities\StorageClient\StorageClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Base class for external entity data aggregators.
 */
abstract class DataAggregatorBase extends PluginBase implements DataAggregatorInterface {

  use PluginDependencyTrait;
  use PluginFormTrait;
  use PluginDebugTrait;

  /**
   * Default storage client plugin id.
   */
  const DEFAULT_STORAGE_CLIENT = 'rest';

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

  /**
   * The external storage client manager.
   *
   * @var \Drupal\Component\Plugin\PluginManagerInterface
   */
  protected $storageClientManager;

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

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

  /**
   * Grouped list of available storage clients.
   *
   * @var array
   *
   * @see self::getAvailableStorageClients() for the structure.
   */
  protected $availableStorageClients;

  /**
   * Array of storage client plugin instances.
   *
   * @var \Drupal\external_entities\StorageClient\StorageClientInterface[]
   */
  protected $storageClientPlugins = [];

  /**
   * Recursively merge 2 arrays.
   *
   * If $deep_merge is FALSE, only the first-level keys are taken into account.
   * In that case, sub-arrays of the first array may be overriden by values of
   * the second array (which may result in a non-array value).
   * Otherwise, when $deep_merge is TRUE, if the values of a given key is an
   * array either in the first or the second array, the resulting value will be
   * an array that merge the values from both sides, with overrides if needed.
   *
   * @param array $a
   *   First array that may be overriden.
   * @param array $b
   *   Second array that overrides existing values.
   * @param bool $deep_merge
   *   If TRUE, merge sub-arrays together.
   * @param bool $override_empty
   *   If TRUE, only first array empty values (WARNING: are considered empty
   *   NULL, '' and [] which differs from PHP empty() function) can be overriden
   *   by a second array non-empty value.
   * @param bool $preserve_integer_keys
   *   If TRUE, integer keys are preserved and values with the same index may be
   *   overriden according to the above rules.
   * @param bool $no_new_keys
   *   Only existing keys can be overriden. No new keys are added. If TRUE,
   *   parameter $preserve_integer_keys will be forced to TRUE.
   *
   * @return array
   *   The merged array.
   */
  public static function mergeArrays(
    array $a,
    array $b,
    bool $deep_merge = FALSE,
    bool $override_empty = FALSE,
    bool $preserve_integer_keys = FALSE,
    bool $no_new_keys = FALSE,
  ) :array {
    // Force $preserve_integer_keys if $no_new_keys is set.
    if ($no_new_keys) {
      $preserve_integer_keys = TRUE;
    }

    // Prepare result array.
    if ($preserve_integer_keys) {
      $result = $a;
    }
    else {
      $result = [];
      foreach ($a as $key => $value) {
        if (is_int($key)) {
          $result[] = $value;
        }
        else {
          $result[$key] = $value;
        }
      }
    }

    // Merge array b on result.
    if ($deep_merge) {
      // Merge sub-arrays.
      if ($override_empty) {
        foreach ($b as $key => $value) {
          // Check for new keys allowed.
          if ($no_new_keys && !array_key_exists($key, $result)) {
            continue;
          }

          if (is_int($key) && !$preserve_integer_keys) {
            // If we don't preserve int keys, we add the value.
            $result[] = $value;
          }
          elseif (((!isset($result[$key])) || ('' === $result[$key]) || ([] === $result[$key]))
            && ((NULL !== $value) && ('' !== $value) && ([] !== $value))
          ) {
            // Either the key is not int or it is but we preserve in keys, only
            // override empty values with non-empty ones.
            $result[$key] = $value;
          }
          elseif (((isset($result[$key])) && ('' !== $result[$key]) && ([] !== $result[$key]))
            && ((NULL !== $value) && ('' !== $value) && ([] !== $value))
          ) {
            if (is_array($result[$key])) {
              if (is_array($value)) {
                $result[$key] = static::mergeArrays(
                  $result[$key],
                  $value,
                  $deep_merge,
                  $override_empty,
                  $preserve_integer_keys,
                  $no_new_keys
                );
              }
              else {
                $result[$key] = static::mergeArrays(
                  $result[$key],
                  [$value],
                  $deep_merge,
                  $override_empty,
                  $preserve_integer_keys,
                  $no_new_keys
                );
              }
            }
            elseif (is_array($value)) {
              $result[$key] = static::mergeArrays(
                [$result[$key]],
                $value,
                $deep_merge,
                $override_empty,
                $preserve_integer_keys,
                $no_new_keys
              );
            }
          }
        }
      }
      else {
        // Full override.
        foreach ($b as $key => $value) {
          // Check for new keys allowed.
          if ($no_new_keys && !array_key_exists($key, $result)) {
            continue;
          }

          if (is_int($key) && !$preserve_integer_keys) {
            $result[] = $value;
          }
          elseif (isset($result[$key]) && is_array($result[$key])) {
            if (isset($value) && is_array($value)) {
              $result[$key] = static::mergeArrays(
                $result[$key],
                $value,
                $deep_merge,
                $override_empty,
                $preserve_integer_keys,
                $no_new_keys
              );
            }
            else {
              $result[$key] = static::mergeArrays(
                $result[$key],
                [$value],
                $deep_merge,
                $override_empty,
                $preserve_integer_keys,
                $no_new_keys
              );
            }
          }
          elseif (isset($result[$key]) && isset($value) && is_array($value)) {
            $result[$key] = static::mergeArrays(
              [$result[$key]],
              $value,
              $deep_merge,
              $override_empty,
              $preserve_integer_keys,
              $no_new_keys
            );
          }
          else {
            $result[$key] = $value;
          }
        }
      }
    }
    else {
      // Not merging sub-arrays.
      if ($override_empty) {
        foreach ($b as $key => $value) {
          // Check for new keys allowed.
          if ($no_new_keys && !array_key_exists($key, $result)) {
            continue;
          }

          if (is_int($key) && !$preserve_integer_keys) {
            // If we don't preserve int keys, we add the value.
            $result[] = $value;
          }
          elseif (((!isset($result[$key])) || ('' === $result[$key]) || ([] === $result[$key]))
            && ((NULL !== $value) && ('' !== $value) && ([] !== $value))
          ) {
            // Either the key is not int or it is but we preserve in keys, only
            // override empty values with non-empty ones.
            $result[$key] = $value;
          }
        }
      }
      else {
        // Full override.
        foreach ($b as $key => $value) {
          // Check for new keys allowed.
          if ($no_new_keys && !array_key_exists($key, $result)) {
            continue;
          }

          if (is_int($key) && !$preserve_integer_keys) {
            $result[] = $value;
          }
          else {
            $result[$key] = $value;
          }
        }
      }
    }
    return $result;
  }

  /**
   * Constructs a DataAggregatorBase object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id 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\Component\Plugin\PluginManagerInterface $storage_client_manager
   *   The storage client manager.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    $plugin_definition,
    TranslationInterface $string_translation,
    LoggerChannelFactoryInterface $logger_factory,
    PluginManagerInterface $storage_client_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_data_aggregator_' . $plugin_id);
    $this->storageClientManager = $storage_client_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('plugin.manager.external_entities.storage_client')
    );
  }

  /**
   * {@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 getConfiguration() {
    return $this->configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration) {
    $configuration = NestedArray::mergeDeep(
      $this->defaultConfiguration(),
      $configuration
    );
    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]);
    $this->configuration = $configuration;
  }

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

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

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    $dependencies = [];
    foreach ($this->getStorageClients() as $storage_client) {
      $dependencies = NestedArray::mergeDeep(
        $dependencies,
        $this->getPluginDependencies($storage_client)
      );
    }
    return $dependencies;
  }

  /**
   * Returns an array of storage clients grouped by categories.
   *
   * @return array
   *   Storage client instances grouped by categories (key). Categories are:
   *   'rest', 'files', 'ql', and 'others'. A special key '#group' offers the
   *   storage client plugin id to category name association.
   */
  public function getAvailableStorageClients() :array {
    if (empty($this->availableStorageClients)) {
      $storage_client_groups = [];
      $storage_clients = $this->storageClientManager->getDefinitions();
      foreach ($storage_clients as $storage_client_id => $definition) {
        $config = $this->getStorageClientDefaultConfiguration();
        $storage_client = $this
          ->storageClientManager
          ->createInstance($storage_client_id, $config);
        if ($storage_client instanceof RestClientInterface) {
          $storage_client_groups['rest'][$storage_client_id] = $storage_client;
          $storage_client_groups['#group'][$storage_client_id] = 'rest';
        }
        elseif ($storage_client instanceof FileClientInterface) {
          $storage_client_groups['files'][$storage_client_id] = $storage_client;
          $storage_client_groups['#group'][$storage_client_id] = 'files';
        }
        elseif ($storage_client instanceof QueryLanguageClientInterface) {
          $storage_client_groups['ql'][$storage_client_id] = $storage_client;
          $storage_client_groups['#group'][$storage_client_id] = 'ql';
        }
        else {
          $storage_client_groups['others'][$storage_client_id] = $storage_client;
          $storage_client_groups['#group'][$storage_client_id] = 'others';
        }
      }
      $this->availableStorageClients = $storage_client_groups;
    }
    return $this->availableStorageClients;
  }

  /**
   * {@inheritdoc}
   */
  public function getStorageClientId(int|string $client_key) :string {
    if (!empty($this->storageClientPlugins[$client_key])) {
      $this->configuration['storage_clients'][$client_key]['id'] =
        $this->storageClientPlugins[$client_key]->getPluginId();
    }
    return $this->configuration['storage_clients'][$client_key]['id'] ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function clearStorageClient(
    int|string $client_key,
  ) :self {
    unset($this->configuration['storage_clients'][$client_key]);
    unset($this->storageClientPlugins[$client_key]);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setStorageClientId(
    string $storage_client_id,
    int|string $client_key,
  ) :self {
    if (empty($storage_client_id)) {
      $this->clearStorageClient($client_key);
    }
    else {
      if ($storage_client_id != ($this->configuration['storage_clients'][$client_key]['id'] ?? '')) {
        $this->configuration['storage_clients'][$client_key] = [
          'id' => $storage_client_id,
          'config' => [],
        ];
        $this->storageClientPlugins[$client_key] = NULL;
      }
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getStorageClient(int|string $client_key)
  :StorageClientInterface {
    if (empty($this->configuration['storage_clients'][$client_key]['id'])) {
      throw new PluginException(
        sprintf("Invalid storage client plugin key '%d'.", $client_key)
      );
    }
    if (empty($this->storageClientPlugins[$client_key])) {
      $config = NestedArray::mergeDeep(
        $this->getStorageClientDefaultConfiguration(),
        $this->getStorageClientConfig($client_key)
      );
      $this->storageClientPlugins[$client_key] =
        $this->storageClientManager->createInstance(
          $this->getStorageClientId($client_key),
          $config
        );
    }
    return $this->storageClientPlugins[$client_key];
  }

  /**
   * {@inheritdoc}
   */
  public function getStorageClients() :array {
    foreach ($this->configuration['storage_clients'] as $sckey => $storage_client_setting) {
      if (!empty($storage_client_setting['id'])) {
        // This will fill storageClientPlugins member.
        $this->getStorageClient($sckey);
      }
    }
    // Remove unused keys.
    $this->storageClientPlugins = array_intersect_key(
      $this->storageClientPlugins,
      $this->configuration['storage_clients']
    );
    return $this->storageClientPlugins;
  }

  /**
   * {@inheritdoc}
   */
  public function getStorageClientConfig(int|string $client_key) :array {
    if (!empty($this->storageClientPlugins[$client_key])) {
      $this->configuration['storage_clients'][$client_key]['config'] =
        $this->storageClientPlugins[$client_key]->getConfiguration();
    }
    return $this->configuration['storage_clients'][$client_key]['config'] ?? [];
  }

  /**
   * {@inheritdoc}
   */
  public function setStorageClientConfig(
    array $storage_client_config,
    int|string $client_key,
  ) :self {
    if (!empty($this->configuration['storage_clients'][$client_key]['id'])) {
      if (!empty($this->storageClientPlugins[$client_key])) {
        // Update plugin and local config.
        $this
          ->storageClientPlugins[$client_key]
          ->setConfiguration(
            NestedArray::mergeDeep(
              $this->getStorageClientDefaultConfiguration(),
              $storage_client_config
            )
          );
        $this->configuration['storage_clients'][$client_key]['config'] = $this
          ->storageClientPlugins[$client_key]
          ->getConfiguration();
      }
      else {
        $this->configuration['storage_clients'][$client_key]['config'] =
          $storage_client_config;
      }
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getStorageClientNotes(int|string $client_key) :string {
    return $this->configuration['storage_clients'][$client_key]['notes'] ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function setStorageClientNotes(
    string $storage_client_notes,
    int|string $client_key,
  ) :self {
    if (!empty($this->configuration['storage_clients'][$client_key])) {
      $this->configuration['storage_clients'][$client_key]['notes'] = $storage_client_notes;
    }
    elseif ($client_key) {
      $this->logger->warning(
        'Trying to set notes for a storage client not set (@client_key).',
        [
          '@client_key' => $client_key,
        ]
      );
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function load(string|int $id) :array|null {
    $entities = $this->loadMultiple([$id]);
    return array_key_exists($id, $entities)
      ? $entities[$id]
      : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(
    array &$form,
    FormStateInterface $form_state,
  ) {
    // Check for Ajax events.
    if (($trigger = $form_state->getTriggeringElement())
      && (preg_match('/^(storages_tab\[config\]\[storage_clients|language_settings\[overrides\]\[\w{2}\]\[storages\]\[config\]\[storage_clients)\]\[(\w+)\]\[id\]$/', $trigger['#name'], $matches))
    ) {
      // Remove current user input on storage client config since form content
      // has changed. Otherwise, if left as is, we would have strange behaviors
      // such as new checkboxes being checked (because their keys are there)
      // while they should not (because their associated values are empty). It
      // usualy comes from hidden fields set with an empty/FALSE value that are
      // turned into non-hidden fields such as checkboxes which are testing if
      // their key exists in the user input to tell if they are checked.
      $ui = $form_state->getUserInput();
      $parents = array_merge(preg_split('/\]?\[/', $matches[1]), [$matches[2], 'config']);
      NestedArray::unsetValue($ui, $parents);
      $form_state->setUserInput($ui);
      $form_state->setRebuild(TRUE);
    }

    $storage_client_configs = $form_state->getValue('storage_clients');
    foreach ($storage_client_configs as $client_key => $client_config) {
      // Skip non-storage client config elements (ie. like 'add_storage').
      if (is_array($client_config)
          && !empty($client_config['id'])
        && !empty($client_config['config'])
      ) {
        // Submit new storage client settings.
        $storage_client_config = $this->getStorageClientDefaultConfiguration();
        $storage_client = $this->storageClientManager->createInstance(
          $client_config['id'],
          $storage_client_config
        );
        if ($storage_client instanceof PluginFormInterface) {
          $storage_client_form_state = XnttSubformState::createForSubform(
            ['storage_clients', $client_key, 'config'],
            $form,
            $form_state
          );
          $storage_client->validateConfigurationForm(
            $form['storage_clients'][$client_key]['config'],
            $storage_client_form_state
          );
        }
      }
    }

    // If rebuild needed, ignore validation.
    if ($form_state->isRebuilding()) {
      $form_state->clearErrors();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(
    array &$form,
    FormStateInterface $form_state,
  ) {
    $storage_clients = [];
    $storage_client_configs = $form_state->getValue('storage_clients');
    foreach ($storage_client_configs as $client_key => $client_config) {
      if (!empty($client_config['id'])) {
        $client_config['config'] ??= [];
        // Submit new storage client settings.
        $storage_client_config = $this->getStorageClientDefaultConfiguration();
        $storage_client = $this->storageClientManager->createInstance(
          $client_config['id'],
          $storage_client_config
        );
        if ($storage_client instanceof PluginFormInterface) {
          $storage_client_form_state = XnttSubformState::createForSubform(
            ['storage_clients', $client_key, 'config'],
            $form,
            $form_state
          );
          $storage_client->submitConfigurationForm(
            $form['storage_clients'][$client_key]['config'],
            $storage_client_form_state
          );
          $storage_client_config = $storage_client->getConfiguration();
        }
        else {
          // Clear config.
          $storage_client_config = [];
        }
        $storage_clients[$client_key] = [
          'id' => $client_config['id'],
          'config' => $storage_client_config,
        ];
      }
    }

    // Cleanup form values (ie. only use the specified ones).
    $form_state->setValues([
      'storage_clients' => $storage_clients,
    ]);

    if ($this instanceof ConfigurableInterface) {
      $this->setConfiguration($form_state->getValues());
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getRequestedDrupalFields() :array {
    // Loop on storage clients and build field request.
    $field_requests = [];
    $clients = $this->getStorageClients();
    foreach ($clients as $client) {
      $client_fields = $client->getRequestedDrupalFields();
      foreach ($client_fields as $field_name => $field_request) {
        if (empty($field_requests[$field_name])
          || !empty($field_request['required'])
          && (
              empty($field_requests[$field_name]['required'])
              || (($field_request['required'] == 'required')
                  && ($field_requests[$field_name]['required'] != 'required'))
              || (($field_request['required'] != 'optional')
                  && ($field_requests[$field_name]['required'] == 'optional')))
        ) {
          $field_requests[$field_name] = $field_request;
        }
      }
    }
    return $field_requests;
  }

  /**
   * {@inheritdoc}
   */
  public function getRequestedMapping(string $field_name, string $field_type) :array {
    // Loop on storage clients and get mapping requests.
    $mapping = [];
    $clients = $this->getStorageClients();
    foreach ($clients as $client) {
      $client_mapping = $client->getRequestedMapping($field_name, $field_type);
      if (empty($mapping)
        || !empty($client_mapping['required'])
        && (
            empty($mapping['required'])
            || (($mapping['required'] != 'required')
                && ($client_mapping['required'] == 'required')))
      ) {
        $mapping = $client_mapping;
      }
      if (!empty($mapping['required'])
          && ($mapping['required'] == 'required')
      ) {
        break;
      }
    }
    return $mapping;
  }

}

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

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