From 282a5f7768aa36031bcf86cae7d98246ac145230 Mon Sep 17 00:00:00 2001 From: Roderik Muit <35514-roderik@users.noreply.drupalcode.org> Date: Fri, 27 Sep 2024 07:45:52 +0000 Subject: [PATCH] Issue #3475612 by roderik, fago: Config dependencies are not properly calculated --- src/Entity/EntityCeDisplay.php | 175 ++++++++++++++++++++++-- src/Entity/EntityCeDisplayInterface.php | 12 +- 2 files changed, 174 insertions(+), 13 deletions(-) diff --git a/src/Entity/EntityCeDisplay.php b/src/Entity/EntityCeDisplay.php index 2fdd89a..7c77026 100644 --- a/src/Entity/EntityCeDisplay.php +++ b/src/Entity/EntityCeDisplay.php @@ -3,8 +3,13 @@ namespace Drupal\custom_elements\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Config\Entity\ConfigEntityInterface; 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; @@ -48,9 +53,6 @@ class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterf /** * Whether this display is enabled or not. * - * If the entity (form) display is disabled, we'll fall back to the 'default' - * display. - * * @var bool */ protected $status = TRUE; @@ -60,21 +62,36 @@ class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterf * * @var string */ - protected $customElementName; + protected string $customElementName = ''; /** * Whether to build using layout (if enabled in appropriate display). * * @var bool */ - protected $useLayoutBuilder = FALSE; + protected bool $useLayoutBuilder = FALSE; /** * Whether to build using processors instead of display components. * * @var bool */ - protected $forceAutoProcessing = FALSE; + 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} @@ -126,14 +143,14 @@ class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterf /** * {@inheritdoc} */ - public function getCustomElementName() { + public function getCustomElementName(): string { return $this->customElementName; } /** * {@inheritdoc} */ - public function setCustomElementName($name) { + public function setCustomElementName($name): self { $this->set('customElementName', $name); return $this; } @@ -261,7 +278,7 @@ class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterf if (!$initialized) { // Enable components that are enabled in the regular entity_view_display. /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $entity_view_display */ - $entity_view_display = \Drupal::service('entity_display.repository') + $entity_view_display = $this->getEntityDisplayRepository() ->getViewDisplay($this->targetEntityType, $this->bundle, $this->originalMode); $field_definitions = $this->getFieldDefinitions(); @@ -322,7 +339,8 @@ class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterf public function preSave(EntityStorageInterface $storage) { // Skip over parent method: we don't have regions. Only sort content. ksort($this->content); - // Ensure the hidden property is not NULL, parent code requires it. + // Accommodate for 'hidden' being NULL in config objects created by + // 3.0-alpha versions of this module. if (!isset($this->hidden)) { $this->hidden = []; } @@ -338,6 +356,101 @@ class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterf 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->getConfigDependencyEntityViewDisplays() as $display) { + $this->addDependency('config', $display->getConfigDependencyName()); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getConfigDependencyEntityViewDisplays(): array { + $displays = []; + if ($this->getUseLayoutBuilder()) { + // Building a custom element depends on the "use layout builder" setting + // in the corresponding entity view display. To guarantee consistent + // output, define it as a dependency. + if ($this->mode !== 'default') { + // It's either the display with the same view mode, or the default. + // entityDisplayRepository::>getViewDisplay() cannot check if a display + // actually exists. Do the loading by ourselves. + $displays = $this->entityTypeManager() + ->getStorage('entity_view_display') + ->loadMultiple([ + $this->targetEntityType . '.' . $this->bundle . '.' . $this->mode, + $this->targetEntityType . '.' . $this->bundle . '.default', + ]); + // Disabled displays are not dependencies. (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']); + } + } + 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; + } + /** * {@inheritdoc} */ @@ -345,10 +458,50 @@ class EntityCeDisplay extends EntityDisplayBase implements EntityCeDisplayInterf // Override parent method: do not filter field definitions, all fields' // display is configurable. if (!isset($this->fieldDefinitions)) { - $this->fieldDefinitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->targetEntityType, $this->bundle); + $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; + } + } diff --git a/src/Entity/EntityCeDisplayInterface.php b/src/Entity/EntityCeDisplayInterface.php index e4b3a85..a6dd6df 100644 --- a/src/Entity/EntityCeDisplayInterface.php +++ b/src/Entity/EntityCeDisplayInterface.php @@ -61,7 +61,7 @@ interface EntityCeDisplayInterface extends EntityDisplayInterface { * @return string * The entity type id. */ - public function getCustomElementName(); + public function getCustomElementName(): string; /** * Sets the custom element name to be displayed. @@ -71,7 +71,7 @@ interface EntityCeDisplayInterface extends EntityDisplayInterface { * * @return $this */ - public function setCustomElementName(string $name); + public function setCustomElementName(string $name): self; /** * {@inheritDoc} @@ -81,6 +81,14 @@ interface EntityCeDisplayInterface extends EntityDisplayInterface { */ public function getRenderer($field_name); + /** + * 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. + */ + public function getConfigDependencyEntityViewDisplays(): array; + /** * Sets the originally requested view mode, when building a CE display. */ -- GitLab