custom_elements-8.x-2.x-dev/src/Entity/EntityCeDisplay.php

src/Entity/EntityCeDisplay.php
<?php

namespace Drupal\custom_elements\Entity;

use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityDisplayBase;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultLazyPluginCollection as DefaultLazyPluginCollectionAlias;
use Drupal\custom_elements\CustomElementGeneratorTrait;

/**
 * Custom element display configuration entity.
 *
 * Contains custom element display options for all components of a rendered
 * entity in a given view mode.
 *
 * @ConfigEntityType(
 *   id = "entity_ce_display",
 *   label = @Translation("Entity custom element display"),
 *   entity_keys = {
 *     "id" = "id",
 *     "status" = "status"
 *   },
 *   handlers = {
 *     "access" = "\Drupal\custom_elements\Entity\Access\EntityCeDisplayAccessControlHandler",
 *   },
 *   config_export = {
 *     "id",
 *     "targetEntityType",
 *     "bundle",
 *     "mode",
 *     "customElementName",
 *     "useLayoutBuilder",
 *     "forceAutoProcessing",
 *     "content",
 *   }
 * )
 */
class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterface {

  use CustomElementGeneratorTrait;

  /**
   * {@inheritdoc}
   */
  protected $displayContext = 'view';

  /**
   * Whether this display is enabled or not.
   *
   * @var bool
   */
  protected $status = TRUE;

  /**
   * Custom element name to be displayed.
   *
   * @var string
   */
  protected string $customElementName = '';

  /**
   * Whether to build using layout (if enabled in appropriate display).
   *
   * @var bool
   */
  protected bool $useLayoutBuilder = FALSE;

  /**
   * Whether to build using processors instead of display components.
   *
   * @var bool
   */
  protected bool $forceAutoProcessing = FALSE;

  /**
   * The entity display repository.
   */
  protected EntityDisplayRepositoryInterface $entityDisplayRepository;

  /**
   * The entity field manager.
   */
  protected EntityFieldManagerInterface $entityFieldManager;

  /**
   * The module handler.
   */
  protected ModuleHandlerInterface $moduleHandler;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $values, $entity_type) {
    // Parent constructor requires plugin manager, so don't use 'lazy' getter.
    $this->pluginManager = \Drupal::service('custom_elements.plugin.manager.field.custom_element_formatter');
    parent::__construct($values, $entity_type);
  }

  /**
   * Sets the originally requested view mode, when building a CE display.
   *
   * @deprecated in custom_elements:3.0.1 and is removed from
   *   custom_elements:4.0.0. use set('originalMode', $viewMode).
   * @see https://www.drupal.org/project/custom_elements/issues/3484476
   */
  public function setOriginalMode(string $viewMode): self {
    $this->originalMode = $viewMode;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getUseLayoutBuilder(): bool {
    return !empty($this->useLayoutBuilder);
  }

  /**
   * {@inheritdoc}
   */
  public function setUseLayoutBuilder(bool $status): self {
    $this->useLayoutBuilder = $status;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getForceAutoProcessing(): bool {
    return !empty($this->forceAutoProcessing);
  }

  /**
   * {@inheritdoc}
   */
  public function setForceAutoProcessing(bool $status): self {
    $this->forceAutoProcessing = $status;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getCustomElementName(): string {
    return $this->customElementName;
  }

  /**
   * {@inheritdoc}
   */
  public function setCustomElementName($name): self {
    $this->set('customElementName', $name);
    return $this;
  }

  /**
   * Gets the formatter for a CE display component.
   *
   * @param string $field_name
   *   The name for the CE display component. NOTE this not the field name;
   *   it's only called $field_name because of the existing
   *   EntityDisplayInterfaceinterface. (Other implementations also effectively
   *   pass the 'component name', because the only components are fields.)
   * @param bool $get_actual_field_name
   *   (Optional) Actually DO treat $field_name as a field name. DO NOT USE
   *   THIS; it's a temporary measure to support 'hidden'/new field rows in the
   *   UI, until they are removed / redone as component rows, in #3446485.
   *
   * @return \Drupal\custom_elements\CustomElementsFieldFormatterInterface|null
   *   A formatter plugin or NULL if the component does not exist.
   *
   * @todo remove $get_actual_field_name in #3446485.
   *
   * @internal
   *    This method's return value may still be widened as other (non-field)
   *    plugin types are added.
   */
  public function getRenderer($field_name, bool $get_actual_field_name = FALSE) {
    if (isset($this->plugins[$field_name])) {
      return $this->plugins[$field_name];
    }
    $component = NULL;
    if (!$get_actual_field_name) {
      $component_name = $field_name;
      $component = $this->getComponent($component_name);
    }

    // Instantiate the formatter object from the stored display properties.
    $formatter = NULL;
    if ($component && isset($component['formatter'])) {
      // @todo When implementing static properties per #3446287, this code
      //   should know not to get a field name/definition, and the plugin
      //   manager should return something that is not a
      //   CustomElementsFieldFormatterInterface. Distinguish by the
      //   'formatter' property having a prefix + colon.
      // This is the actual field name.
      if (!isset($component['field_name'])) {
        // @todo Improve logging?
        return NULL;
      }
      $definition = $this->getFieldDefinition($component['field_name']);
      if ($definition) {
        $component += ['configuration' => [], 'name' => $component_name, 'is_slot' => FALSE];
        $formatter = $this->pluginManager->createInstance($component['formatter'], [
          'field_definition' => $definition,
          'view_mode' => $this->originalMode,
          'name' => $component['name'],
          'is_slot' => $component['is_slot'],
        ] + $component['configuration']
        );
      }
    }

    // Persist the formatter object.
    $this->plugins[$field_name] = $formatter;
    return $formatter;
  }

  /**
   * {@inheritdoc}
   */
  public function getPluginCollections() {
    $configurations = [];
    foreach ($this->getComponents() as $component_name => $component) {
      // @todo see getRenderer() when implementing #3446287; if() may need to
      //   change.
      if (isset($component['field_name'])) {
        $field_name = $component['field_name'];
        if (!empty($component['formatter']) && ($field_definition = $this->getFieldDefinition($field_name))) {
          $component += ['configuration' => [], 'name' => $component_name, 'is_slot' => FALSE];
          $configurations[$field_name] = [
            'id' => $component['formatter'],
            'field_definition' => $field_definition,
            'view_mode' => $this->originalMode,
            'name' => $component['name'],
            'is_slot' => $component['is_slot'],
          ] + $component['configuration'];
        }
      }
    }
    return [
      'formatters' => new DefaultLazyPluginCollectionAlias($this->pluginManager, $configurations),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTagsToInvalidate() {
    $tags = parent::getCacheTagsToInvalidate();
    // $tags is a single tag, ending in the view mode. When using the default
    // display to build a specific view mode, also add a tag for that view mode.
    $tag = current($tags);
    if ($this->originalMode !== 'default' && substr($tag, -8) === '.default') {
      $tags[] = substr($tag, 0, strlen($tag) - 7) . $this->originalMode;
    }

    return $tags;
  }

  /**
   * {@inheritdoc}
   */
  protected function init() {
    // Override parent method: we don't need its display defaults.
    if (!$this->targetEntityType) {
      return;
    }

    if (!$this->getCustomElementName()) {
      $custom_element = $this->getCustomElementGenerator()->getViewModeDefaults($this->targetEntityType, $this->bundle, $this->mode);
      $this->setCustomElementName($custom_element->getPrefixedTag());
    }

    $initialized = !empty($this->content) || $this->forceAutoProcessing || $this->getUseLayoutBuilder();
    if (!$initialized) {
      // Enable components that are enabled in the corresponding entity view
      // display, which can be a display for our own view mode or if that isn't
      // enabled, for the default view mode. This is necessary for proper
      // building/generating of custom elements. (When copying a new non-default
      // display from a default one, this is overwritten by createCopy().)
      $entity_view_display = $this->loadEntityViewDisplay($this->mode, TRUE);
      $field_definitions = $this->getFieldDefinitions();

      // @todo Add support for statically set values.
      // Enable every component with "auto" that is enabled in the display.
      foreach ($entity_view_display->getComponents() as $field_name => $component) {
        // Ignore extra-fields.
        if (isset($field_definitions[$field_name])) {
          $component_name = str_starts_with($field_name, 'field_') ? substr($field_name, strlen('field_')) : $field_name;
          // @todo Generalize the solution over at issue #3550120.
          $is_canvas = $field_definitions[$field_name]->getType() == 'component_tree';
          $this->setComponent($component_name, [
            'field_name' => $field_name,
            'formatter' => $is_canvas ? 'canvas' : 'auto',
            'weight' => $component['weight'],
            'is_slot' => str_starts_with($field_definitions[$field_name]->getType(), 'text') || $is_canvas ? 1 : 0,
          ]);
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public function createCopy($mode) {
    $copy = parent::createCopy($mode);
    $custom_element = $this->getCustomElementGenerator()->getViewModeDefaults($this->targetEntityType, $this->bundle, $mode);
    $copy->setCustomElementName($custom_element->getPrefixedTag());
    if ($copy->getUseLayoutBuilder()) {
      // Disable LB if the corresponding entity view display does not have it
      // enabled. The 'enabled' value is ignored, but is still confusing.
      $display = $this->loadEntityViewDisplay($mode);
      if (!$display || !$display->getThirdPartySetting('layout_builder', 'enabled')) {
        $copy->setUseLayoutBuilder(FALSE);
      }
    }
    return $copy;
  }

  /**
   * {@inheritdoc}
   */
  public function setComponent($name, array $options = []) {
    // If no weight specified, make sure the field sinks at the bottom.
    if (!isset($options['weight'])) {
      $max = $this->getHighestWeight();
      $options['weight'] = isset($max) ? $max + 1 : 0;
    }
    $this->content[$name] = $options;
    unset($this->plugins[$name]);
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function removeComponent($name) {
    // Skip over parent method, to ignore $this->hidden.
    unset($this->content[$name]);
    unset($this->plugins[$name]);

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function preSave(EntityStorageInterface $storage) {
    // Skip over parent method: we don't have regions. Only sort content.
    ksort($this->content);
    ConfigEntityBase::preSave($storage);
  }

  /**
   * {@inheritdoc}
   */
  public function toArray() {
    // Skip over parent method: all fields' display is configurable, no field
    // must be disallowed because of its definitions.
    return ConfigEntityBase::toArray();
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    // Skip over parent method: fields need to be calculated differently.
    ConfigEntityBase::calculateDependencies();

    // Depend on the bundle.
    $target_entity_type = $this->entityTypeManager()->getDefinition($this->targetEntityType);
    $bundle_config_dependency = $target_entity_type->getBundleConfigDependency($this->bundle);
    $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']);

    // Depend on fields: names are in 'field_name' properties instead of keys.
    if ($this->getModuleHandler()->moduleExists('field')) {
      $fieldnames_as_keys = array_flip(array_filter(array_map(
        fn($component) => $component['field_name'] ?? NULL,
        $this->getComponents()
      )));
      $field_definitions = $this->getEntityFieldManager()->getFieldDefinitions($this->targetEntityType, $this->bundle);
      foreach (array_intersect_key($field_definitions, $fieldnames_as_keys) as $field_definition) {
        if ($field_definition instanceof ConfigEntityInterface && $field_definition->getEntityTypeId() == 'field_config') {
          $this->addDependency('config', $field_definition->getConfigDependencyName());
        }
      }
    }

    // Depend on configured modes.
    if ($this->mode != 'default') {
      $mode_entity = $this->entityTypeManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode);
      $this->addDependency('config', $mode_entity->getConfigDependencyName());
    }

    // Depend on related entity view displays.
    foreach ($this->getConfigDependencyEntityViewDisplaysInternal() as $display) {
      $this->addDependency('config', $display->getConfigDependencyName());
    }

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function onDependencyRemoval(array $dependencies) {
    $changed = FALSE;
    foreach ($dependencies['config'] as $entity) {
      if ($entity->getEntityTypeId() == 'field_config') {
        // Find and remove components that use this field.
        $field_name = $entity->getName();
        foreach ($this->getComponents() as $component_name => $component) {
          if (isset($component['field_name']) && $component['field_name'] === $field_name) {
            $this->removeComponent($component_name);
            $changed = TRUE;
          }
        }
      }
    }
    // Let parent handle other dependencies (modules, plugins, etc.).
    return parent::onDependencyRemoval($dependencies) || $changed;
  }

  /**
   * Gets the entity view displays that are this entity's config dependencies.
   *
   * @return \Drupal\Core\Entity\Display\EntityDisplayInterface[]
   *   Entity views displays which this CE display depends on.
   *
   * @deprecated in custom_elements:3.0.1 and is removed from
   *   custom_elements:4.0.0. There's no public replacement.
   * @see https://www.drupal.org/project/custom_elements/issues/3475342
   */
  public function getConfigDependencyEntityViewDisplays(): array {
    return $this->getConfigDependencyEntityViewDisplaysInternal();
  }

  /**
   * Gets the entity view displays that are this entity's config dependencies.
   *
   * @return \Drupal\Core\Entity\Display\EntityDisplayInterface[]
   *   Entity views displays which this CE display depends on.
   */
  private function getConfigDependencyEntityViewDisplaysInternal(): array {
    $displays = [];
    if ($this->getUseLayoutBuilder()) {
      // Building a custom element depends on the "use layout builder" setting
      // in corresponding entity view displays. Note that, if that setting is
      // disabled, this CE display's useLayoutBuilder property is ignored.
      // There can be two ways of reasoning about this:
      // - This situation is exactly the same as if the entity view display
      //   didn't exist, therefore such an entity view display is not a
      //   dependency. (Just like we cannot declare a dependency on a
      //   nonexistent entity view display object.)
      // - If the setting is re-enabled, this influences the output (because
      //   useLayoutBuilder property is not ignored anymore), therefore such an
      //   entity view display IS a dependency.
      // We choose the latter.
      if ($this->mode !== 'default') {
        // This entity is used when building a custom element using our own
        // view mode; this uses either the entity view display with our view
        // mode (preferred), or the default. Depend only on that display.
        // Disabled displays are ignored. (If no displays are enabled, Core
        // auto-generates the default ones, so we have 0 dependencies.)
        $display = $this->loadEntityViewDisplay();
        $displays = $display ? [$display] : [];
      }
      else {
        // This entity is used for all view modes that have no own CE display,
        // so all corresponding entity view displays are also dependencies, if
        // enabled.
        $query = $this->getEntityQuery('entity_view_display')
          ->condition('id', $this->targetEntityType . '.' . $this->bundle . '.', 'STARTS_WITH')
          ->condition('status', TRUE)
          ->condition('id', $this->targetEntityType . '.' . $this->bundle . '.default', '<>');
        $other_active_display_ids = $query->execute();
        if ($other_active_display_ids) {
          // Filter out view modes with their own CE display.
          $active_ce_display_ids = $this->getEntityQuery('entity_ce_display')
            ->condition('id', $this->targetEntityType . '.' . $this->bundle . '.', 'STARTS_WITH')
            ->condition('status', TRUE)
            ->execute();
          $other_active_display_ids = array_diff($other_active_display_ids, $active_ce_display_ids);
          if ($other_active_display_ids) {
            $storage = $this->entityTypeManager()->getStorage('entity_view_display');
            $displays += $storage->loadMultiple($other_active_display_ids);
          }
        }
      }
    }
    return $displays;
  }

  /**
   * Loads an entity view display containing settings used by this CE display.
   *
   * @param string $mode
   *   (Optional) view mode. Default is the current view mode (note, not the
   *   'requested' view mode during the build/render phase).
   * @param bool $default_if_not_exists
   *   (Optional) If the display is not enabled (for either the view mode or
   *   "default"), return a default display instead of NULL.
   *
   * @return \Drupal\Core\Entity\Entity\EntityViewDisplay|null
   *   The display, or NULL if none exist in active config / none are enabled,
   *   and $default_if_not_exist is false.
   */
  protected function loadEntityViewDisplay(string $mode = '', bool $default_if_not_exists = FALSE): ?EntityViewDisplay {
    // entityDisplayRepository::>getViewDisplay() cannot check if a display
    // actually exists, so do 'generic' loading.
    // Optimization: possibly load two at once, instead of doing two loads.
    $displays = $this->entityTypeManager()
      ->getStorage('entity_view_display')
      ->loadMultiple([
        $this->targetEntityType . '.' . $this->bundle . '.' . ($mode ?: $this->mode),
        $this->targetEntityType . '.' . $this->bundle . '.default',
      ]);
    // Disabled displays are ignored. (If no displays are enabled, Core
    // auto-generates the default ones, so we have 0 dependencies.)
    $displays = array_filter(
      $displays,
      fn($display) => $display->status()
    );
    if (count($displays) > 1) {
      unset($displays[$this->targetEntityType . '.' . $this->bundle . '.default']);
    }
    if (!$displays && $default_if_not_exists) {
      // Return a dynamically created default display. (There is no difference
      // with a '$mode' display, in terms of the fields that are initialized.
      // Do default, only because it feels a bit strange to be able to create
      // displays for nonexistent view modes.)
      $displays = [
        $this->getEntityDisplayRepository()
          ->getViewDisplay($this->targetEntityType, $this->bundle, 'default'),
      ];
    }

    return reset($displays) ?: NULL;
  }

  /**
   * {@inheritdoc}
   */
  protected function getFieldDefinitions() {
    // Override parent method: do not filter field definitions, all fields'
    // display is configurable.
    if (!isset($this->fieldDefinitions)) {
      $this->fieldDefinitions = $this->getEntityFieldManager()->getFieldDefinitions($this->targetEntityType, $this->bundle);
    }

    return $this->fieldDefinitions;
  }

  /**
   * Gets an entity query.
   *
   * @param string $entity_type
   *   The entity type for which the query object should be returned.
   */
  protected function getEntityQuery($entity_type): QueryInterface {
    return $this->entityTypeManager()->getStorage($entity_type)->getQuery();
  }

  /**
   * Gets the entity field manager.
   */
  protected function getEntityFieldManager(): EntityFieldManagerInterface {
    if (!isset($this->entityFieldManager)) {
      $this->entityFieldManager = \Drupal::service('entity_field.manager');
    }
    return $this->entityFieldManager;
  }

  /**
   * Gets the entity display repository.
   */
  protected function getEntityDisplayRepository(): EntityDisplayRepositoryInterface {
    if (!isset($this->entityDisplayRepository)) {
      $this->entityDisplayRepository = \Drupal::service('entity_display.repository');
    }
    return $this->entityDisplayRepository;
  }

  /**
   * Gets the module handler.
   */
  protected function getModuleHandler(): ModuleHandlerInterface {
    if (!isset($this->moduleHandler)) {
      $this->moduleHandler = \Drupal::moduleHandler();
    }
    return $this->moduleHandler;
  }

}

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

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