date_recur-8.x-2.2/src/DateRecurViewsHooks.php

src/DateRecurViewsHooks.php
<?php

declare(strict_types=1);

namespace Drupal\date_recur;

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\datetime\DateTimeViewsHelper;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Defines Views hooks.
 */
class DateRecurViewsHooks implements ContainerInjectionInterface {

  use StringTranslationTrait;

  /**
   * DateRecurViewsHooks constructor.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   The active database connection.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   Module handler.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
   *   The entity field manager.
   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager
   *   The typed data manager.
   */
  public function __construct(protected Connection $database,
    protected ModuleHandlerInterface $moduleHandler,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected EntityFieldManagerInterface $entityFieldManager,
    protected TypedDataManagerInterface $typedDataManager,
    protected ?DateTimeViewsHelper $dateTimeViewsHelper = NULL,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('database'),
      $container->get('module_handler'),
      $container->get('entity_type.manager'),
      $container->get('entity_field.manager'),
      $container->get('typed_data_manager'),
      $container->has('datetime.views_helper') ? $container->get('datetime.views_helper') : NULL,
    );
  }

  /**
   * Implements hook_views_data().
   *
   * @see \hook_views_data()
   * @see \date_recur_views_data()
   */
  public function viewsData(): array {
    $allFields = $this->getDateRecurFields();
    $data = [];
    foreach ($allFields as $entityTypeId => $fields) {
      $entityType = $this->entityTypeManager->getDefinition($entityTypeId);
      /** @var \Drupal\views\EntityViewsDataInterface $viewsData */
      $viewsData = $this->entityTypeManager->getHandler($entityType->id(), 'views_data');
      $entityViewsTable = $viewsData->getViewsTableForEntityType($entityType);

      /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $fields */
      foreach ($fields as $fieldId => $field) {
        $fieldLabel = $this->getFieldLabel($field->getTargetEntityTypeId(), $fieldId);
        $fieldName = $field->getName();
        $entityIdField = $entityType->getKey('id');
        $entityRevisionField = $entityType->isRevisionable() ? $entityType->getKey('revision') : NULL;
        $tArgs = [
          '@field_name' => $fieldLabel,
          '@entity_type' => $entityType->getLabel(),
        ];

        // Occurrence filter.
        $data[$entityViewsTable][$fieldName . '_occurrences']['filter'] = [
          'id' => 'date_recur_occurrences_filter',
          'title' => $this->t('Occurrences filter for @field_name', $tArgs),
          // Instruct the filter to join the occurrence.entity_id field on
          // base.entityId:
          'field base entity_id' => $entityIdField,
          'date recur field name' => $fieldName,
          'entity_type' => $entityType->id(),
        ];

        // Relationship from entity table to occurrence table.
        $occurrenceTableName = DateRecurOccurrences::getOccurrenceCacheStorageTableName($field);
        $data[$entityViewsTable][$fieldName . '_occurrences']['relationship'] = [
          'id' => 'standard',
          'base' => $occurrenceTableName,
          'base field' => isset($entityRevisionField) ? 'revision_id' : 'entity_id',
          'help' => $this->t('Get all occurrences for recurring date field @field_name', $tArgs),
          'field' => $entityRevisionField ?? $entityIdField,
          // Add new 'title'.
          'title' => $this->t('Occurrences of @field_name', $tArgs),
          // Default label for relationship in the UI.
          'label' => $this->t('Occurrences of @field_name', $tArgs),
        ];

        $dateField = [
          'id' => 'date_recur_date',
          'source date format' => $this->getFieldDateFormat($field),
          'source time zone' => DateTimeItemInterface::STORAGE_TIMEZONE,
        ];
        $data[$occurrenceTableName][$fieldName . '_value']['field'] = $dateField;
        $data[$occurrenceTableName][$fieldName . '_end_value']['field'] = $dateField;

        // Attached fields get automatic functionality provided by
        // hook_field_views_data(). Add features here for base fields.
        if ($field instanceof BaseFieldDefinition) {
          $data[$occurrenceTableName]['table']['group'] = $this->t('Occurrences for @entity_type @field_name', [
            '@entity_type' => $entityType->getLabel(),
            '@field_name' => $fieldLabel,
          ]);

          $startField = $fieldName . '_value';
          $endField = $fieldName . '_end_value';
          $data[$occurrenceTableName][$startField]['title'] = $this->t('Occurrence start date');
          $data[$occurrenceTableName][$endField]['title'] = $this->t('Occurrence end date');
          // Sort.
          // DateTimeViewsHelper::buildViewsData() uses 'datetime', which relies
          // on entity things.
          $data[$occurrenceTableName][$startField]['sort']['id'] = 'date';
          $data[$occurrenceTableName][$endField]['sort']['id'] = 'date';
        }
      }
    }

    return $data;
  }

  /**
   * Implements hook_views_data_alter().
   *
   * @see \hook_views_data_alter()
   * @see \date_recur_views_data_alter()
   */
  public function viewsDataAlter(array &$data): void {
    $removeFieldKeys = $this->getViewsPluginTypes();
    $removeFieldKeys = array_flip($removeFieldKeys);

    // Base fields don't yet have an option to provide defaults for their type,
    // but entity views data still tries to add default views integration for
    // the field primitives in
    // \Drupal\views\EntityViewsData::mapSingleFieldViewsData
    // Feature to add in: https://www.drupal.org/node/2489476.
    // Remove the default plugins from entity views data since they are not
    // something that should be supported. This also means adding plugins for
    // date recur base fields cannot be added in hook_views_data or
    // entity 'views_data' handlers..
    // @todo Bring in all plugins from DateTimeViewsHelper::buildViewsData() if/when
    // it supports base fields.
    $allFields = $this->getDateRecurFields();
    foreach ($allFields as $entityTypeId => $fields) {
      /** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $entityStorage */
      $entityStorage = $this->entityTypeManager->getStorage($entityTypeId);
      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $tableMapping */
      $tableMapping = $entityStorage->getTableMapping($fields);

      /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $fields */
      foreach ($fields as $fieldStorage) {
        if (!$fieldStorage instanceof BaseFieldDefinition) {
          continue;
        }

        if ($tableMapping->requiresDedicatedTableStorage($fieldStorage)) {
          $fieldTable = $tableMapping->getDedicatedDataTableName($fieldStorage);
          $fieldData = &$data[$fieldTable];

          // Remove handler keys within each field. Keys like 'title', 'help'
          // etc are ignored. Whereas 'argument', 'field', etc are removed.
          foreach ($fieldData as &$field) {
            $field = array_diff_key($field, $removeFieldKeys);
          }
        }
      }
    }
  }

  /**
   * Implements hook_field_views_data().
   *
   * @see \hook_field_views_data()
   * @see \date_recur_field_views_data()
   */
  public function fieldViewsData(FieldStorageConfigInterface $fieldDefinition): array {
    $data = [];

    $entityTypeId = $fieldDefinition->getTargetEntityTypeId();
    $entityType = $this->entityTypeManager->getDefinition($entityTypeId);
    /** @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $entityStorage */
    $entityStorage = $this->entityTypeManager->getStorage($entityTypeId);
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $tableMapping */
    $tableMapping = $entityStorage->getTableMapping();

    $fieldName = $fieldDefinition->getName();
    // The field label, see also usage in views.views.inc.
    $fieldLabel = $this->getFieldLabel($fieldDefinition->getTargetEntityTypeId(), $fieldDefinition->getName());
    $fieldTableName = $tableMapping->getDedicatedDataTableName($fieldDefinition);

    $parentData = $this->getParentFieldViewsData($fieldDefinition);
    // If there is no views data then quit (does this happen?).
    if (count($parentData) === 0) {
      return $data;
    }
    $originalTable = $parentData[$fieldTableName];

    $occurrenceTableName = DateRecurOccurrences::getOccurrenceCacheStorageTableName($fieldDefinition);
    if ($this->database->schema()->tableExists($occurrenceTableName)) {
      $occurrenceTable = $originalTable;
      // Remove the automatic join, requires site builders to use relationship
      // plugin.
      unset($occurrenceTable['table']['join']);
      // Unset some irrelevant fields.
      foreach (array_keys($occurrenceTable) as $fieldId) {
        $fieldId = (string) $fieldId;
        if (($fieldId === 'table') || (str_contains($fieldId, $fieldName . '_value')) || (str_contains($fieldId, $fieldName . '_end_value'))) {
          continue;
        }
        unset($occurrenceTable[$fieldId]);
      }

      // Update table name references.
      $handlerTypes = $this->getViewsPluginTypes();
      $recurTableGroup = $this->t('Occurrences for @entity_type @field_name', [
        '@entity_type' => $entityType->getLabel(),
        '@field_name' => $fieldLabel,
      ]);
      foreach ($occurrenceTable as $fieldId => &$field) {
        $field['group'] = $recurTableGroup;

        foreach ($handlerTypes as $handlerType) {
          if (isset($field[$handlerType]['table'])) {
            $field[$handlerType]['table'] = $occurrenceTableName;
            $field[$handlerType]['additional fields'] = [
              $fieldName . '_value',
              $fieldName . '_end_value',
              'delta',
              'field_delta',
            ];
          }
        }
      }

      $data[$occurrenceTableName] = $occurrenceTable;
    }

    $fieldTable = $originalTable;
    // Change the title for all plugins provided by
    // \datetime_range_field_views_data().
    foreach ($fieldTable as $key => &$definitions) {
      /** @var \Drupal\Core\StringTranslation\TranslatableMarkup|string|null $originalTitle */
      $originalTitle = $definitions['title'] ?? '';
      $tArgs = $originalTitle instanceof TranslatableMarkup ? $originalTitle->getArguments() : [];
      $tArgs['@field_label'] = $fieldLabel;

      if ($fieldName === $key) {
        $definitions['title'] = isset($tArgs['@argument']) ?
          $this->t('@field_label (@argument)', $tArgs) :
          $this->t('@field_label', $tArgs);
      }
      elseif (str_contains($key, $fieldName . '_value')) {
        $definitions['title'] = isset($tArgs['@argument']) ?
          $this->t('@field_label: first occurrence start date (@argument)', $tArgs) :
          $this->t('@field_label: first occurrence start date', $tArgs);
      }
      elseif (str_contains($key, $fieldName . '_end_value')) {
        $definitions['title'] = isset($tArgs['@argument']) ?
          $this->t('@field_label: first occurrence end date (@argument)', $tArgs) :
          $this->t('@field_label: first occurrence end date', $tArgs);
      }
      elseif (str_contains($key, $fieldName . '_rrule')) {
        $definitions['title'] = $this->t('@field_label: recurring rule (RRULE)', $tArgs);
      }
      elseif (str_contains($key, $fieldName . '_timezone')) {
        $definitions['title'] = $this->t('@field_label: time zone', $tArgs);
      }
      elseif (str_contains($key, $fieldName . '_infinite')) {
        $definitions['title'] = $this->t('@field_label: is infinite', $tArgs);
      }
      elseif ('delta' === $key) {
        $definitions['title'] = $this->t('@field_label: field delta', $tArgs);
      }
    }

    $data[$fieldTableName] = $fieldTable;
    return $data;
  }

  /**
   * Get date recur fields for entities supporting views.
   *
   * @return array
   *   An array of arrays of date recur fields keyed by entity type ID.
   */
  protected function getDateRecurFields(): array {
    // Date recur fields keyed by entity type id.
    $fields = [];

    // Get all date recur fields as base and attached fields.
    foreach ($this->entityTypeManager->getDefinitions() as $entityType) {
      // \Drupal\views\EntityViewsData class only allows entities with
      // \Drupal\Core\Entity\Sql\SqlEntityStorageInterface.
      // Only fieldable entities have base fields.
      try {
        if (
          $this->entityTypeManager->getStorage($entityType->id()) instanceof SqlEntityStorageInterface &&
          $entityType->hasHandlerClass('views_data') &&
          $entityType->entityClassImplements(FieldableEntityInterface::class)) {
          $fields[$entityType->id()] = array_filter(
            $this->entityFieldManager->getFieldStorageDefinitions($entityType->id()),
            function (FieldStorageDefinitionInterface $field): bool {
              $typeDefinition = $this->typedDataManager->getDefinition('field_item:' . $field->getType());
              // @see \Drupal\date_recur\DateRecurCachedHooks::fieldInfoAlter
              return isset($typeDefinition[DateRecurOccurrences::IS_DATE_RECUR]);
            },
          );
        }
      }
      catch (PluginNotFoundException $e) {
        // This can occur when a content-entity is added during a config
        // import that enables a module.
        continue;
      }
    }

    // Remove entity types with no date recur fields.
    $fields = array_filter($fields);

    return $fields;
  }

  /**
   * Get the most popular label for a field storage.
   *
   * @param string $entityTypeId
   *   The entity type ID.
   * @param string $fieldName
   *   The field.
   *
   * @return string
   *   The most popular label for a field storage.
   */
  protected function getFieldLabel($entityTypeId, $fieldName): string {
    return \views_entity_field_label($entityTypeId, $fieldName)[0];
  }

  /**
   * Get the views definition for the field, as defined by datetime_range.
   *
   * @param \Drupal\field\FieldStorageConfigInterface $fieldDefinition
   *   A field storage definition.
   *
   * @return array
   *   The views data for a field.
   */
  protected function getParentFieldViewsData(FieldStorageConfigInterface $fieldDefinition): array {
    if ($this->dateTimeViewsHelper) {
      $data = $this->dateTimeViewsHelper->buildViewsData($fieldDefinition, [], 'value');
      $data = $this->dateTimeViewsHelper->buildViewsData($fieldDefinition, $data, 'end_value');
    }
    else {
      $this->moduleHandler->loadInclude('datetime_range', 'inc', 'datetime_range.views');
      $data = function_exists('datetime_range_field_views_data')
        ? \datetime_range_field_views_data($fieldDefinition)
        : [];
    }

    // Expose rrule value to the views fields.
    foreach ($data as $table_name => $table_data) {
      $data[$table_name][$fieldDefinition->getName() . '_rrule']['field']['id'] = 'standard';
    }

    return $data;
  }

  /**
   * Get an array of all views plugin types.
   *
   * @return array
   *   An array of all views plugin types.
   */
  protected function getViewsPluginTypes(): array {
    return Views::getPluginTypes();
  }

  /**
   * Get date format of field storage.
   *
   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $fieldDefinition
   *   A field definition.
   *
   * @return string
   *   A date format.
   */
  protected function getFieldDateFormat(FieldStorageDefinitionInterface $fieldDefinition): string {
    return $fieldDefinition->getSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE
      ? DateTimeItemInterface::DATE_STORAGE_FORMAT
      : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
  }

}

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

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