domain_views_display-1.x-dev/src/DomainViewsDisplay.php

src/DomainViewsDisplay.php
<?php

declare(strict_types=1);

namespace Drupal\domain_views_display;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\domain\DomainNegotiatorInterface;
use Drupal\domain_views_display\Plugin\views\display_extender\DomainViewsDisplayExtender;
use Drupal\views\Entity\View;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Overrides a view's display as appropriate.
 *
 * @see \Drupal\domain_views_display\Plugin\views\display_extender\DomainViewsDisplayExtender
 */
class DomainViewsDisplay implements ContainerInjectionInterface, TrustedCallbackInterface {

  use AutowireTrait;

  /**
   * The options keys to get the extender's options from a view display.
   *
   * @var non-empty-list<string>
   */
  protected const array OPTIONS_KEYS = [
    'display_options',
    'display_extenders',
    DomainViewsDisplayExtender::ID,
  ];

  /**
   * Creates the domain views display helper.
   *
   * @param \Drupal\domain\DomainNegotiatorInterface $domainNegotiator
   *   The domain negotiator.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   */
  public function __construct(
    #[Autowire(service: 'domain.negotiator')]
    protected readonly DomainNegotiatorInterface $domainNegotiator,
    protected readonly EntityTypeManagerInterface $entityTypeManager,
  ) {}

  /**
   * Responds to hook_module_implements_alter().
   *
   * You can't use service calls in hook_module_implements_alter() so the method
   * is static.
   *
   * @param array<string, string|false> $implementations
   *   The implementations.
   * @param string $hook
   *   The hook.
   */
  public static function moduleImplementsAlter(array &$implementations, string $hook): void {
    if ($hook === 'views_pre_view') {
      // Have the hook run early so later hook implementations already have the
      // correct display set.
      $group = $implementations['domain_views_display'];
      $implementations = ['domain_views_display' => $group] + $implementations;
    }
    if ($hook === 'element_info_alter') {
      // Have the hook run late so our pre-render is inserted into the front of
      // the list.
      $group = $implementations['domain_views_display'];
      $implementations += ['domain_views_display' => $group];
    }
  }

  /**
   * Responds to hook_element_info_alter().
   *
   * @param array{view: array{'#pre_render': list<callable|string>}} $info
   *   The element info.
   */
  public function elementInfoAlter(array &$info): void {
    array_unshift($info['view']['#pre_render'], [static::class, 'preRender']);
  }

  /**
   * Pre-renders a view element, changing display as appropriate.
   *
   * @param array{'#view'?: ViewExecutable, '#name'?: string, '#display_id': string} $element
   *   The element.
   *
   * @return array<string, mixed>
   *   The element.
   */
  public static function preRender(array $element): array {
    assert(isset($element['#view']) || isset($element['#name']));
    // @phpstan-ignore offsetAccess.notFound
    $view = $element['#view'] ?? Views::getView($element['#name']);
    assert($view !== NULL);
    // View config entities aren't statically cached so store in the element for
    // use by the element's pre-render.
    $element['#view'] = $view;
    if (!$view->access($element['#display_id'])) {
      return $element;
    }
    if (!$view->current_display) {
      $view->setDisplay($element['#display_id']);
    }
    $instance = \Drupal::classResolver(static::class);
    assert($instance instanceof DomainViewsDisplay);
    $cacheability = CacheableMetadata::createFromRenderArray($element);
    $override = $instance->getActiveOverride($view, $cacheability);
    $cacheability->applyTo($element);
    if (!$override) {
      return $element;
    }

    $element['#display_id'] = $override;
    return $element;
  }

  /**
   * Responds to a views_pre_view() hook.
   *
   * @param \Drupal\views\ViewExecutable $view
   *   The view.
   */
  public function viewsPreView(ViewExecutable $view): void {
    $this->ensureDisplay($view);
  }

  /**
   * Ensures a view has the correct display ID for its domain.
   *
   * It will throw an exception if the current display isn't set.
   *
   * @param \Drupal\views\ViewExecutable $view
   *   A view with the display set.
   *
   * @throws \LogicException
   *   If the view doesn't have a current display set.
   */
  public function ensureDisplay(ViewExecutable $view): void {
    $cacheability = CacheableMetadata::createFromRenderArray($view->element);
    $override = $this->getActiveOverride($view, $cacheability);
    $cacheability->applyTo($view->element);
    if (!$override) {
      return;
    }

    $view->setDisplay($override);
  }

  /**
   * Returns the active override for a view with its display set.
   *
   * @param \Drupal\views\ViewExecutable $view
   *   A view with the display set.
   * @param \Drupal\Core\Cache\CacheableMetadata|null $cacheability
   *   (optional) An object to track cacheability metadata.
   *
   * @return string|null
   *   The display ID to use as an override if present; NULL otherwise.
   *
   * @throws \LogicException
   *   If the view doesn't have a current display set.
   */
  public function getActiveOverride(ViewExecutable $view, ?CacheableMetadata $cacheability = NULL): ?string {
    if ($view->current_display === NULL) {
      throw new \LogicException("Can't get active override from view without display set.");
    }
    $overrides = $this->getOption($view, 'override_displays');
    if (!$overrides) {
      return NULL;
    }
    /** @var array<string, string> $overrides */
    $cacheability?->addCacheContexts(['url.site']);
    $override = $overrides[$this->domainNegotiator->getActiveId()] ?? NULL;
    if (!$override || $override === $view->current_display || !$view->access($override)) {
      return NULL;
    }
    return $override;
  }

  /**
   * Returns a list of IDs of views that have domain overrides configured.
   *
   * @return array<string, string>
   *   An associative array of view IDs keyed by ID.
   */
  public function getViewIdsWithOverrides(): array {
    return $this->entityTypeManager->getStorage('view')
      ->getQuery()
      ->exists('display.*.display_options.display_extenders.' . DomainViewsDisplayExtender::ID . '.override_displays')
      ->execute();
  }

  /**
   * Returns a map of view ID to its overridden displays.
   *
   * @return array<string, array<string, string>>
   *   An associative array keyed by view ID of associative arrays of display
   *   IDs keyed by display ID.
   */
  public function getViewDisplaysWithOverrides(): array {
    $out = [];
    foreach (View::loadMultiple($this->getViewIdsWithOverrides()) as $view) {
      $option_keys = [...static::OPTIONS_KEYS, 'override_displays'];
      $has_overrides = fn(array $display): bool => NestedArray::keyExists($display, $option_keys);
      /** @var array<string, array{id: string}> $displays */
      $displays = $view->get('display');
      $displays = array_filter($displays, $has_overrides);
      $out[$view->id()] = array_map(fn (array $display): string => $display['id'], $displays);
    }
    // @phpstan-ignore return.type
    return $out;
  }

  /**
   * Returns the domain views display extender for a view.
   *
   * @param \Drupal\views\ViewExecutable $view
   *   The view.
   *
   * @return \Drupal\domain_views_display\Plugin\views\display_extender\DomainViewsDisplayExtender|null
   *   The display extender if present; NULL otherwise.
   */
  protected function getExtender(ViewExecutable $view): ?DomainViewsDisplayExtender {
    $extender = $view->getDisplay()
      ->getExtenders()[DomainViewsDisplayExtender::ID] ?? NULL;
    assert($extender instanceof DomainViewsDisplayExtender);
    return $extender;
  }

  /**
   * Gets a domain views extender option.
   *
   * @param \Drupal\views\ViewExecutable $view
   *   The view.
   * @param string $option
   *   The option name.
   *
   * @return mixed
   *   The views extender option if there is an extender and the option's set;
   *   NULL otherwise.
   */
  protected function getOption(ViewExecutable $view, string $option): mixed {
    return $this->getExtender($view)?->options[$option] ?? NULL;
  }

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks(): array {
    return [
      'process',
      'preRender',
    ];
  }

}

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

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