external_entity-1.0.x-dev/src/Plugin/views/query/ExternalEntityQuery.php

src/Plugin/views/query/ExternalEntityQuery.php
<?php

declare(strict_types=1);

namespace Drupal\external_entity\Plugin\views\query;

use Drupal\views\ViewExecutable;
use Drupal\Core\Form\FormStateInterface;
use Drupal\external_entity\AjaxFormTrait;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\external_entity\ExternalEntityOptions;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\views\Plugin\views\join\JoinPluginBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\external_entity\Contracts\ExternalEntityInterface;
use Drupal\external_entity\Plugin\views\ExternalEntityResultRow;
use Drupal\external_entity\Contracts\ExternalEntityStorageInterface;
use Drupal\external_entity\Definition\ExternalEntitySearchDefinition;
use Drupal\external_entity\Definition\ExternalEntityDefaultDefinition;

/**
 * Define the external entity views sql query plugin.
 *
 * @ViewsQuery(
 *   id = "external_entity",
 *   title = @Translation("External Entity")
 * )
 */
class ExternalEntityQuery extends QueryPluginBase {

  use AjaxFormTrait;

  /**
   * @var array
   */
  protected $where = [];

  /**
   * @var array
   */
  protected $orderBy = [];

  /**
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * @var \Drupal\external_entity\ExternalEntityOptions
   */
  protected $externalEntityOptions;

  /**
   * Define the class constructor.
   *
   * @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.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    ModuleHandlerInterface $module_handler,
    EntityTypeManagerInterface $entity_type_manager,
    ExternalEntityOptions $external_entity_options,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->moduleHandler = $module_handler;
    $this->entityTypeManager = $entity_type_manager;
    $this->externalEntityOptions = $external_entity_options;
  }

  /**
   * {@inheritDoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('module_handler'),
      $container->get('entity_type.manager'),
      $container->get('external_entity.options')
    );
  }

  /**
   * @param \Drupal\views\ViewExecutable $view
   */
  public function build(ViewExecutable $view): void {
    $this->view = $view;

    $view->initPager();
    $view->pager->query();

    $view->build_info['query'] = $this->query();
  }

  /**
   * {@inheritDoc}
   */
  public function buildOptionsForm(
    &$form,
    FormStateInterface $form_state,
  ): void {
    $parents = ['query', 'options'];
    $options = $this->externalEntityOptions;
    $option_types = $options->types();

    $type = $this->getFormStateValue(
      array_merge($parents, ['type']),
      $form_state,
      $this->externalEntityTypeId()
    );

    $form['type'] = [
      '#type' => 'select',
      '#title' => $this->t('Type'),
      '#description' => $this->t(
        'Select the external entity type.'
      ),
      '#options' => $option_types,
      '#default_value' => $type,
      '#empty_option' => $this->t('- Select -'),
    ];

    views_ui_add_ajax_trigger(
      $form,
      'type',
      array_merge(['options'], $parents)
    );

    if (isset($type) && !empty($type)) {
      $form['resource'] = [
        '#type' => 'select',
        '#title' => $this->t('Resource'),
        '#description' => $this->t(
          'Select the external entity resource.'
        ),
        '#options' => $options->resources($type),
        '#empty_option' => $this->t('- Select -'),
        '#default_value' => $this->getFormStateValue(
          array_merge($parents, ['resource']),
          $form_state,
          $this->externalEntityResource()
        ),
      ];
    }
  }

  /**
   * {@inheritDoc}
   */
  public function query($get_count = FALSE): QueryInterface {
    $query = $this->getResourceQuery();

    if (!empty($this->where)) {
      foreach ($this->where as $where) {
        if (!isset($where['conditions'])) {
          continue;
        }
        foreach ($where['conditions'] as $condition) {
          if (!isset($condition['value'])) {
            continue;
          }
          $this->processCondition($condition);

          $query->condition(
            $condition['field'],
            $condition['value'],
            $condition['operator'] ?? '='
          );
        }
      }
    }

    if (!empty($this->orderBy)) {
      foreach ($this->orderBy as $order) {
        if (!isset($order['field'])) {
          continue;
        }
        $field = ltrim($order['field'], '.');
        $query->sort($field, $order['direction'] ?? 'ASC');
      }
    }

    return $query;
  }

  /**
   * Add a simple WHERE clause to the query.
   *
   * The $field, $value and $operator arguments can also be passed in with a
   * single DatabaseCondition object, like this:
   * @code
   * $this->query->addWhere(
   *   $this->options['group'],
   *   (new Condition('OR'))
   *     ->condition($field, $value, 'NOT IN')
   *     ->condition($field, $value, 'IS NULL')
   * );
   * @endcode
   *
   * @param mixed $group
   *   The WHERE group to add these to; groups are used to create AND/OR
   *   sections. Groups cannot be nested. Use 0 as the default group.
   *   If the group does not yet exist it will be created as an AND group.
   * @param string $field
   *   The name of the field to check.
   * @param mixed $value
   *   The value to test the field against. In most cases, this is a scalar.
   *   For more complex options, it is an array. The meaning of each element in
   *   the array is dependent on the $operator.
   * @param string $operator
   *   The comparison operator, such as =, <, or >=. It also accepts more
   *   complex options such as IN, LIKE, LIKE BINARY, or BETWEEN. Defaults to
   *   =.
   *   If $field is a string you have to use 'formula' here.
   *
   * @see \Drupal\Core\Database\Query\ConditionInterface::condition()
   * @see \Drupal\Core\Database\Query\Condition
   */
  public function addWhere($group, $field, $value = NULL, $operator = NULL): void {
    if (empty($group)) {
      $group = 0;
    }

    if (!isset($this->where[$group])) {
      $this->setWhereGroup('AND', $group);
    }

    $this->where[$group]['conditions'][] = [
      'field' => $field,
      'value' => $value,
      'operator' => $operator,
    ];
  }

  /**
   * Add a complex WHERE clause to the query.
   *
   * @param mixed $group
   *   The WHERE group to add these to; groups are used to create AND/OR
   *   sections. Groups cannot be nested. Use 0 as the default group.
   *   If the group does not yet exist it will be created as an AND group.
   * @param string $snippet
   *   The snippet to check. This can be either a column or
   *   a complex expression like "UPPER(table.field) = 'value'".
   * @param array $args
   *   An associative array of arguments.
   *
   * @see QueryConditionInterface::where()
   */
  public function addWhereExpression(int $group, string $snippet, array $args = []): void {
    if (empty($group)) {
      $group = 0;
    }

    if (!isset($this->where[$group])) {
      $this->setWhereGroup('AND', $group);
    }

    $this->where[$group]['conditions'][] = [
      'field' => $snippet,
      'value' => $args,
      'operator' => 'formula',
    ];
  }

  /**
   * Add an ORDER BY clause to the query.
   *
   * @param string|null $table
   *   The table this field is part of. If a formula, enter NULL.
   *   If you want to orderby random use "rand" as table and nothing else.
   * @param string|null $field
   *   The field or formula to sort on. If already a field, enter NULL
   *   and put in the alias.
   * @param string $order
   *   Either ASC or DESC.
   * @param string $alias
   *   The alias to add the field as. In SQL, all fields in the order by
   *   must also be in the SELECT portion. If an $alias isn't specified
   *   one will be generated for from the $field; however, if the
   *   $field is a formula, this alias will likely fail.
   * @param array $params
   *   Any params that should be passed through to the
   *   addField.
   */
  public function addOrderBy(
    ?string $table,
    ?string $field = NULL,
    string $order = 'ASC',
    string $alias = '',
    array $params = [],
  ): void {
    $this->orderBy[] = [
      'field' => $field,
      'direction' => strtoupper($order),
    ];
  }

  /**
   * {@inheritDoc}
   */
  public function loadEntities(&$results): void {
    /** @var \Drupal\external_entity\Plugin\views\ExternalEntityResultRow $result */
    foreach ($results as &$result) {
      if (!$result instanceof ExternalEntityResultRow || !$result->hasDefinition()) {
        continue;
      }
      $definition = $result->definition;
      $result->_entity = $this->createExternalEntityFromDefinition($definition);
    }
  }

  /**
   * {@inheritDoc}
   */
  public function execute(ViewExecutable $view): void {
    $index = 0;
    $start = microtime(TRUE);

    /** @var \Drupal\views\Plugin\views\pager\PagerPluginBase $pager */
    $pager = $view->pager;
    /** @var \Drupal\Core\Entity\Query\QueryInterface $query */
    $query = $view->build_info['query'];

    try {
      if (!empty($this->limit) || !empty($this->offset)) {
        $limit = (int) (!empty($this->limit) ? $this->limit : 999999);
        $offset = (int) (!empty($this->offset) ? $this->offset : 0);
        $query->range($offset, $limit);
      }

      /** @var \Drupal\external_entity\Definition\ExternalEntitySearchDefinition $response */
      if ($response = $query->execute()) {
        $results = $response->getResults();
        $search_info = $response->getInfo();

        $pager->total_items = $search_info['total'] ?? 0;

        $pager->postExecute($results);
        $pager->updatePageInfo();

        foreach ($results as $definition) {
          if ($entity = $this->createExternalEntityFromDefinition($definition)) {
            $row = [
              'index' => $index,
              '_entity' => $entity,
              'definition' => $definition,
            ];
            $view->result[$index] = new ExternalEntityResultRow($row);

            $index++;
          }
        }
      }
    }
    catch (DatabaseExceptionWrapper $exception) {
      $view->result = [];

      if (property_exists($view, 'live_preview') && $view->live_preview) {
        $this->messenger->addError(
          $exception->getMessage()
        );
      }
      else {
        throw new DatabaseExceptionWrapper(
          "Exception in {$view->storage->label()}[{$view->storage->id()}]: {$exception->getMessage()}"
        );
      }
    }

    $view->execute_time = microtime(TRUE) - $start;
  }

  /**
   * Create an external entity from definition.
   *
   * @param \Drupal\external_entity\Definition\ExternalEntityDefaultDefinition $definition
   *   External entity definition.
   *
   * @return \Drupal\external_entity\Contracts\ExternalEntityInterface
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function createExternalEntityFromDefinition(
    ExternalEntityDefaultDefinition $definition,
  ): ExternalEntityInterface {
    return $this->externalEntityStorage()->createEntityFromDefinition(
      $this->externalEntityTypeId(),
      $definition
    );
  }

  /**
   * {@inheritDoc}
   */
  public function addField(
    $table,
    $field,
    $alias = '',
    $params = [],
  ): string {
    return $field;
  }

  /**
   * {@inheritDoc}
   */
  public function ensureTable(
    $table,
    $relationship = NULL,
    ?JoinPluginBase $join = NULL,
  ): ?string {
    return NULL;
  }

  /**
   * Process query condition.
   *
   * @param array $condition
   *   An array of query condition.
   *
   * @return void
   */
  protected function processCondition(array &$condition): void {
    $condition['field'] = ltrim($condition['field'], '.');

    if (isset($condition['operator']) && $condition['operator'] === 'formula') {
      $info = $this->parseFormulaField($condition['field']);

      if (isset($info['field'], $info['operator'], $info['value'])) {
        $condition['field'] = $info['field'];
        $condition['value'] = $info['value'];
        $condition['operator'] = $info['operator'];
      }
    }

    if (isset($condition['value']) && !empty($condition['value'])) {
      foreach ($this->getQuerySubstitutions() as $key => $substitution) {
        $substitution = is_array($substitution)
          ? array_map('strval', $substitution)
          : (string) $substitution;

        $condition['value'] = is_array($condition['value'])
          ? array_map('strval', $condition['value'])
          : (string) $condition['value'];

        $condition['value'] = str_replace($key, $substitution, $condition['value']);
      }
    }

    if (is_string($condition['value'])) {
      $condition['value'] = $this->processArithmeticExpression(
        $condition['value']
      );
    }
  }

  /**
   * Get view query substitutions.
   *
   * @return array
   *   An array of views query substitutions.
   */
  protected function getQuerySubstitutions(): array {
    return $this->moduleHandler->invokeAll(
      'views_query_substitutions',
      [$this->view]
    );
  }

  /**
   * Process simple string arithmetic expression.
   *
   * @param string $expression
   *   An arithmetic expression.
   *
   * @return mixed
   *   The arithmetic expression value; otherwise the original expression.
   */
  protected function processArithmeticExpression(string $expression) {
    $matches = [];
    $pattern = '/^(\d+)\s?([\+\-\*\/])\s?(\d+)$/';

    if (preg_match($pattern, $expression, $matches)) {
      array_shift($matches);
      [$number1, $operator, $number2] = $matches;

      switch ($operator) {
        case '+':
          $expression = $number1 + $number2;
          break;

        case '-':
          $expression = $number1 - $number2;
          break;

        case '*':
          $expression = $number1 * $number2;
          break;

        case '/':
          $expression = $number1 / $number2;
          break;
      }
    }

    return $expression;
  }

  /**
   * Parse formula field.
   *
   * @param string $field
   *   The field that contains the formula.
   * @param string $delimiter
   *   The field statement delimiter.
   *
   * @return array
   *   An array of the formula information broken out into the following:
   *    - field
   *    - value
   *    - operator
   *    - conjunction
   */
  protected function parseFormulaField(string $field, string $delimiter = ' '): array {
    $info = [];
    $keywords = $this->queryOperatorKeywords();

    foreach (explode($delimiter, $field) as $segment) {
      $key = 'value';

      if (strpos($segment, 'field_') !== FALSE) {
        $key = 'field';
      }
      elseif (in_array($segment, $keywords, TRUE)) {
        $key = 'operator';
      }
      elseif (in_array($segment, ['AND', 'OR'], TRUE)) {
        $key = 'conjunction';
      }

      $info[$key][] = $segment;
    }

    array_walk($info, static function (&$value) {
      if (count($value) === 1) {
        $value = reset($value);
      }
    });

    return $info;
  }

  /**
   * Define the query operator keywords.
   *
   * @return string[]
   *   An array of the query operator keywords.
   */
  protected function queryOperatorKeywords(): array {
    return [
      '=',
      '<>',
      '>',
      '>=',
      '<',
      '<=',
      'IN',
      'BETWEEN',
      'CONTAINS',
      'ENDS_WITH',
      'STARTS_WITH',
    ];
  }

  /**
   * @param \Drupal\Core\Entity\Query\QueryInterface $query
   * @param int $limit
   *
   * @return \Drupal\external_entity\Definition\ExternalEntitySearchDefinition
   */
  protected function searchResourceDefinitions(
    QueryInterface $query,
    int $limit = 10,
  ): ExternalEntitySearchDefinition {
    return $query->pager($limit)->execute();
  }

  /**
   * Get the external entity resource query instance.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function getResourceQuery(): QueryInterface {
    return $this->externalEntityStorage()->getResourceQuery(
      $this->externalEntityResource(),
      $this->externalEntityTypeId()
    );
  }

  /**
   * Get external entity type.
   *
   * @return string|null
   *   The external entity type ID.
   */
  public function externalEntityTypeId(): ?string {
    return $this->options['type_of'] ?? NULL;
  }

  /**
   * Get the external entity resource.
   *
   * @return string|null
   *   The external entity resource.
   */
  public function externalEntityResource(): ?string {
    return $this->options['resource'] ?? NULL;
  }

  /**
   * External entity storage instance.
   *
   * @return \Drupal\external_entity\Contracts\ExternalEntityStorageInterface
   *   The external entity storage instance.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function externalEntityStorage(): ExternalEntityStorageInterface {
    return $this->entityTypeManager->getStorage('external_entity');
  }

}

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

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