"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/lib/Drupal/Core/Entity/EntityDisplayBase.php" (26 Nov 2020, 20018 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "EntityDisplayBase.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\Core\Entity;
    4 
    5 use Drupal\Core\Config\Entity\ConfigEntityBase;
    6 use Drupal\Core\Config\Entity\ConfigEntityInterface;
    7 use Drupal\Core\Field\FieldDefinitionInterface;
    8 use Drupal\Core\Entity\Display\EntityDisplayInterface;
    9 
   10 /**
   11  * Provides a common base class for entity view and form displays.
   12  */
   13 abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDisplayInterface {
   14 
   15   /**
   16    * The 'mode' for runtime EntityDisplay objects used to render entities with
   17    * arbitrary display options rather than a configured view mode or form mode.
   18    *
   19    * @todo Prevent creation of a mode with this ID
   20    *   https://www.drupal.org/node/2410727
   21    */
   22   const CUSTOM_MODE = '_custom';
   23 
   24   /**
   25    * Unique ID for the config entity.
   26    *
   27    * @var string
   28    */
   29   protected $id;
   30 
   31   /**
   32    * Entity type to be displayed.
   33    *
   34    * @var string
   35    */
   36   protected $targetEntityType;
   37 
   38   /**
   39    * Bundle to be displayed.
   40    *
   41    * @var string
   42    */
   43   protected $bundle;
   44 
   45   /**
   46    * A list of field definitions eligible for configuration in this display.
   47    *
   48    * @var \Drupal\Core\Field\FieldDefinitionInterface[]
   49    */
   50   protected $fieldDefinitions;
   51 
   52   /**
   53    * View or form mode to be displayed.
   54    *
   55    * @var string
   56    */
   57   protected $mode = self::CUSTOM_MODE;
   58 
   59   /**
   60    * Whether this display is enabled or not. If the entity (form) display
   61    * is disabled, we'll fall back to the 'default' display.
   62    *
   63    * @var bool
   64    */
   65   protected $status;
   66 
   67   /**
   68    * List of component display options, keyed by component name.
   69    *
   70    * @var array
   71    */
   72   protected $content = [];
   73 
   74   /**
   75    * List of components that are set to be hidden.
   76    *
   77    * @var array
   78    */
   79   protected $hidden = [];
   80 
   81   /**
   82    * The original view or form mode that was requested (case of view/form modes
   83    * being configured to fall back to the 'default' display).
   84    *
   85    * @var string
   86    */
   87   protected $originalMode;
   88 
   89   /**
   90    * The plugin objects used for this display, keyed by field name.
   91    *
   92    * @var array
   93    */
   94   protected $plugins = [];
   95 
   96   /**
   97    * Context in which this entity will be used (e.g. 'view', 'form').
   98    *
   99    * @var string
  100    */
  101   protected $displayContext;
  102 
  103   /**
  104    * The plugin manager used by this entity type.
  105    *
  106    * @var \Drupal\Component\Plugin\PluginManagerBase
  107    */
  108   protected $pluginManager;
  109 
  110   /**
  111    * The renderer.
  112    *
  113    * @var \Drupal\Core\Render\RendererInterface
  114    */
  115   protected $renderer;
  116 
  117   /**
  118    * {@inheritdoc}
  119    */
  120   public function __construct(array $values, $entity_type) {
  121     if (!isset($values['targetEntityType']) || !isset($values['bundle'])) {
  122       throw new \InvalidArgumentException('Missing required properties for an EntityDisplay entity.');
  123     }
  124 
  125     if (!$this->entityTypeManager()->getDefinition($values['targetEntityType'])->entityClassImplements(FieldableEntityInterface::class)) {
  126       throw new \InvalidArgumentException('EntityDisplay entities can only handle fieldable entity types.');
  127     }
  128 
  129     $this->renderer = \Drupal::service('renderer');
  130 
  131     // A plugin manager and a context type needs to be set by extending classes.
  132     if (!isset($this->pluginManager)) {
  133       throw new \RuntimeException('Missing plugin manager.');
  134     }
  135     if (!isset($this->displayContext)) {
  136       throw new \RuntimeException('Missing display context type.');
  137     }
  138 
  139     parent::__construct($values, $entity_type);
  140 
  141     $this->originalMode = $this->mode;
  142 
  143     $this->init();
  144   }
  145 
  146   /**
  147    * Initializes the display.
  148    *
  149    * This fills in default options for components:
  150    * - that are not explicitly known as either "visible" or "hidden" in the
  151    *   display,
  152    * - or that are not supposed to be configurable.
  153    */
  154   protected function init() {
  155     // Only populate defaults for "official" view modes and form modes.
  156     if ($this->mode !== static::CUSTOM_MODE) {
  157       $default_region = $this->getDefaultRegion();
  158       // Fill in defaults for extra fields.
  159       $context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
  160       $extra_fields = \Drupal::service('entity_field.manager')->getExtraFields($this->targetEntityType, $this->bundle);
  161       $extra_fields = isset($extra_fields[$context]) ? $extra_fields[$context] : [];
  162       foreach ($extra_fields as $name => $definition) {
  163         if (!isset($this->content[$name]) && !isset($this->hidden[$name])) {
  164           // Extra fields are visible by default unless they explicitly say so.
  165           if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
  166             $this->setComponent($name, [
  167               'weight' => $definition['weight'],
  168             ]);
  169           }
  170           else {
  171             $this->removeComponent($name);
  172           }
  173         }
  174         // Ensure extra fields have a 'region'.
  175         if (isset($this->content[$name])) {
  176           $this->content[$name] += ['region' => $default_region];
  177         }
  178       }
  179 
  180       // Fill in defaults for fields.
  181       $fields = $this->getFieldDefinitions();
  182       foreach ($fields as $name => $definition) {
  183         if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
  184           $options = $definition->getDisplayOptions($this->displayContext);
  185 
  186           // @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
  187           if (!isset($options['region']) && !empty($options['type']) && $options['type'] === 'hidden') {
  188             $options['region'] = 'hidden';
  189             @trigger_error("Support for using 'type' => 'hidden' in a component is deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use 'region' => 'hidden' instead. See https://www.drupal.org/node/2801513", E_USER_DEPRECATED);
  190           }
  191 
  192           if (!empty($options['region']) && $options['region'] === 'hidden') {
  193             $this->removeComponent($name);
  194           }
  195           elseif ($options) {
  196             $options += ['region' => $default_region];
  197             $this->setComponent($name, $options);
  198           }
  199           // Note: (base) fields that do not specify display options are not
  200           // tracked in the display at all, in order to avoid cluttering the
  201           // configuration that gets saved back.
  202         }
  203       }
  204     }
  205   }
  206 
  207   /**
  208    * {@inheritdoc}
  209    */
  210   public function getTargetEntityTypeId() {
  211     return $this->targetEntityType;
  212   }
  213 
  214   /**
  215    * {@inheritdoc}
  216    */
  217   public function getMode() {
  218     return $this->get('mode');
  219   }
  220 
  221   /**
  222    * {@inheritdoc}
  223    */
  224   public function getOriginalMode() {
  225     return $this->get('originalMode');
  226   }
  227 
  228   /**
  229    * {@inheritdoc}
  230    */
  231   public function getTargetBundle() {
  232     return $this->bundle;
  233   }
  234 
  235   /**
  236    * {@inheritdoc}
  237    */
  238   public function setTargetBundle($bundle) {
  239     $this->set('bundle', $bundle);
  240     return $this;
  241   }
  242 
  243   /**
  244    * {@inheritdoc}
  245    */
  246   public function id() {
  247     return $this->targetEntityType . '.' . $this->bundle . '.' . $this->mode;
  248   }
  249 
  250   /**
  251    * {@inheritdoc}
  252    */
  253   public function preSave(EntityStorageInterface $storage) {
  254     // Ensure that a region is set on each component.
  255     foreach ($this->getComponents() as $name => $component) {
  256       // @todo Remove this BC layer in Drupal 9.
  257       // @see https://www.drupal.org/project/drupal/issues/2799641
  258       if (!isset($component['region']) && isset($component['type']) && $component['type'] === 'hidden') {
  259         @trigger_error("Support for using 'type' => 'hidden' in a component is deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use 'region' => 'hidden' instead. See https://www.drupal.org/node/2801513", E_USER_DEPRECATED);
  260         $this->removeComponent($name);
  261       }
  262 
  263       // Ensure that a region is set.
  264       if (isset($this->content[$name]) && !isset($component['region'])) {
  265         // Directly set the component to bypass other changes in setComponent().
  266         $this->content[$name]['region'] = $this->getDefaultRegion();
  267       }
  268     }
  269 
  270     ksort($this->content);
  271     ksort($this->hidden);
  272     parent::preSave($storage);
  273   }
  274 
  275   /**
  276    * Handles a component type of 'hidden'.
  277    *
  278    * The logic of this method has been duplicated inline in the preSave()
  279    * method so that this method may remain deprecated and trigger an error.
  280    *
  281    * @param string $name
  282    *   The name of the component.
  283    * @param array $component
  284    *   The component array.
  285    *
  286    * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. No
  287    *   replacement is provided.
  288    *
  289    * @see https://www.drupal.org/node/2801513
  290    */
  291   protected function handleHiddenType($name, array $component) {
  292     @trigger_error(__METHOD__ . ' is deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. No replacement is provided. See https://www.drupal.org/node/2801513', E_USER_DEPRECATED);
  293     if (!isset($component['region']) && isset($component['type']) && $component['type'] === 'hidden') {
  294       $this->removeComponent($name);
  295     }
  296   }
  297 
  298   /**
  299    * {@inheritdoc}
  300    */
  301   public function calculateDependencies() {
  302     parent::calculateDependencies();
  303     $target_entity_type = $this->entityTypeManager()->getDefinition($this->targetEntityType);
  304 
  305     // Create dependency on the bundle.
  306     $bundle_config_dependency = $target_entity_type->getBundleConfigDependency($this->bundle);
  307     $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']);
  308 
  309     // If field.module is enabled, add dependencies on 'field_config' entities
  310     // for both displayed and hidden fields. We intentionally leave out base
  311     // field overrides, since the field still exists without them.
  312     if (\Drupal::moduleHandler()->moduleExists('field')) {
  313       $components = $this->content + $this->hidden;
  314       $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->targetEntityType, $this->bundle);
  315       foreach (array_intersect_key($field_definitions, $components) as $field_name => $field_definition) {
  316         if ($field_definition instanceof ConfigEntityInterface && $field_definition->getEntityTypeId() == 'field_config') {
  317           $this->addDependency('config', $field_definition->getConfigDependencyName());
  318         }
  319       }
  320     }
  321 
  322     // Depend on configured modes.
  323     if ($this->mode != 'default') {
  324       $mode_entity = $this->entityTypeManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode);
  325       $this->addDependency('config', $mode_entity->getConfigDependencyName());
  326     }
  327     return $this;
  328   }
  329 
  330   /**
  331    * {@inheritdoc}
  332    */
  333   public function toArray() {
  334     $properties = parent::toArray();
  335     // Do not store options for fields whose display is not set to be
  336     // configurable.
  337     foreach ($this->getFieldDefinitions() as $field_name => $definition) {
  338       if (!$definition->isDisplayConfigurable($this->displayContext)) {
  339         unset($properties['content'][$field_name]);
  340         unset($properties['hidden'][$field_name]);
  341       }
  342     }
  343 
  344     return $properties;
  345   }
  346 
  347   /**
  348    * {@inheritdoc}
  349    */
  350   public function createCopy($mode) {
  351     $display = $this->createDuplicate();
  352     $display->mode = $display->originalMode = $mode;
  353     return $display;
  354   }
  355 
  356   /**
  357    * {@inheritdoc}
  358    */
  359   public function getComponents() {
  360     return $this->content;
  361   }
  362 
  363   /**
  364    * {@inheritdoc}
  365    */
  366   public function getComponent($name) {
  367     return isset($this->content[$name]) ? $this->content[$name] : NULL;
  368   }
  369 
  370   /**
  371    * {@inheritdoc}
  372    */
  373   public function setComponent($name, array $options = []) {
  374     // If no weight specified, make sure the field sinks at the bottom.
  375     if (!isset($options['weight'])) {
  376       $max = $this->getHighestWeight();
  377       $options['weight'] = isset($max) ? $max + 1 : 0;
  378     }
  379 
  380     // For a field, fill in default options.
  381     if ($field_definition = $this->getFieldDefinition($name)) {
  382       $options = $this->pluginManager->prepareConfiguration($field_definition->getType(), $options);
  383     }
  384 
  385     // Ensure we always have an empty settings and array.
  386     $options += ['settings' => [], 'third_party_settings' => []];
  387 
  388     $this->content[$name] = $options;
  389     unset($this->hidden[$name]);
  390     unset($this->plugins[$name]);
  391 
  392     return $this;
  393   }
  394 
  395   /**
  396    * {@inheritdoc}
  397    */
  398   public function removeComponent($name) {
  399     $this->hidden[$name] = TRUE;
  400     unset($this->content[$name]);
  401     unset($this->plugins[$name]);
  402 
  403     return $this;
  404   }
  405 
  406   /**
  407    * {@inheritdoc}
  408    */
  409   public function getHighestWeight() {
  410     $weights = [];
  411 
  412     // Collect weights for the components in the display.
  413     foreach ($this->content as $options) {
  414       if (isset($options['weight'])) {
  415         $weights[] = $options['weight'];
  416       }
  417     }
  418 
  419     // Let other modules feedback about their own additions.
  420     $weights = array_merge($weights, \Drupal::moduleHandler()->invokeAll('field_info_max_weight', [$this->targetEntityType, $this->bundle, $this->displayContext, $this->mode]));
  421 
  422     return $weights ? max($weights) : NULL;
  423   }
  424 
  425   /**
  426    * Gets the field definition of a field.
  427    */
  428   protected function getFieldDefinition($field_name) {
  429     $definitions = $this->getFieldDefinitions();
  430     return isset($definitions[$field_name]) ? $definitions[$field_name] : NULL;
  431   }
  432 
  433   /**
  434    * Gets the definitions of the fields that are candidate for display.
  435    */
  436   protected function getFieldDefinitions() {
  437     if (!isset($this->fieldDefinitions)) {
  438       $definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->targetEntityType, $this->bundle);
  439       // For "official" view modes and form modes, ignore fields whose
  440       // definition states they should not be displayed.
  441       if ($this->mode !== static::CUSTOM_MODE) {
  442         $definitions = array_filter($definitions, [$this, 'fieldHasDisplayOptions']);
  443       }
  444       $this->fieldDefinitions = $definitions;
  445     }
  446 
  447     return $this->fieldDefinitions;
  448   }
  449 
  450   /**
  451    * Determines if a field has options for a given display.
  452    *
  453    * @param \Drupal\Core\Field\FieldDefinitionInterface $definition
  454    *   A field definition.
  455    *
  456    * @return array|null
  457    */
  458   private function fieldHasDisplayOptions(FieldDefinitionInterface $definition) {
  459     // The display only cares about fields that specify display options.
  460     // Discard base fields that are not rendered through formatters / widgets.
  461     return $definition->getDisplayOptions($this->displayContext);
  462   }
  463 
  464   /**
  465    * {@inheritdoc}
  466    */
  467   public function onDependencyRemoval(array $dependencies) {
  468     $changed = parent::onDependencyRemoval($dependencies);
  469     foreach ($dependencies['config'] as $entity) {
  470       if ($entity->getEntityTypeId() == 'field_config') {
  471         // Remove components for fields that are being deleted.
  472         $this->removeComponent($entity->getName());
  473         unset($this->hidden[$entity->getName()]);
  474         $changed = TRUE;
  475       }
  476     }
  477     foreach ($this->getComponents() as $name => $component) {
  478       if ($renderer = $this->getRenderer($name)) {
  479         if (in_array($renderer->getPluginDefinition()['provider'], $dependencies['module'])) {
  480           // Revert to the defaults if the plugin that supplies the widget or
  481           // formatter depends on a module that is being uninstalled.
  482           $this->setComponent($name);
  483           $changed = TRUE;
  484         }
  485 
  486         // Give this component the opportunity to react on dependency removal.
  487         $component_removed_dependencies = $this->getPluginRemovedDependencies($renderer->calculateDependencies(), $dependencies);
  488         if ($component_removed_dependencies) {
  489           if ($renderer->onDependencyRemoval($component_removed_dependencies)) {
  490             // Update component settings to reflect changes.
  491             $component['settings'] = $renderer->getSettings();
  492             $component['third_party_settings'] = [];
  493             foreach ($renderer->getThirdPartyProviders() as $module) {
  494               $component['third_party_settings'][$module] = $renderer->getThirdPartySettings($module);
  495             }
  496             $this->setComponent($name, $component);
  497             $changed = TRUE;
  498           }
  499           // If there are unresolved deleted dependencies left, disable this
  500           // component to avoid the removal of the entire display entity.
  501           if ($this->getPluginRemovedDependencies($renderer->calculateDependencies(), $dependencies)) {
  502             $this->removeComponent($name);
  503             $arguments = [
  504               '@display' => (string) $this->getEntityType()->getLabel(),
  505               '@id' => $this->id(),
  506               '@name' => $name,
  507             ];
  508             $this->getLogger()->warning("@display '@id': Component '@name' was disabled because its settings depend on removed dependencies.", $arguments);
  509             $changed = TRUE;
  510           }
  511         }
  512       }
  513     }
  514     return $changed;
  515   }
  516 
  517   /**
  518    * Returns the plugin dependencies being removed.
  519    *
  520    * The function recursively computes the intersection between all plugin
  521    * dependencies and all removed dependencies.
  522    *
  523    * Note: The two arguments do not have the same structure.
  524    *
  525    * @param array[] $plugin_dependencies
  526    *   A list of dependencies having the same structure as the return value of
  527    *   ConfigEntityInterface::calculateDependencies().
  528    * @param array[] $removed_dependencies
  529    *   A list of dependencies having the same structure as the input argument of
  530    *   ConfigEntityInterface::onDependencyRemoval().
  531    *
  532    * @return array
  533    *   A recursively computed intersection.
  534    *
  535    * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
  536    * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval()
  537    */
  538   protected function getPluginRemovedDependencies(array $plugin_dependencies, array $removed_dependencies) {
  539     $intersect = [];
  540     foreach ($plugin_dependencies as $type => $dependencies) {
  541       if ($removed_dependencies[$type]) {
  542         // Config and content entities have the dependency names as keys while
  543         // module and theme dependencies are indexed arrays of dependency names.
  544         // @see \Drupal\Core\Config\ConfigManager::callOnDependencyRemoval()
  545         if (in_array($type, ['config', 'content'])) {
  546           $removed = array_intersect_key($removed_dependencies[$type], array_flip($dependencies));
  547         }
  548         else {
  549           $removed = array_values(array_intersect($removed_dependencies[$type], $dependencies));
  550         }
  551         if ($removed) {
  552           $intersect[$type] = $removed;
  553         }
  554       }
  555     }
  556     return $intersect;
  557   }
  558 
  559   /**
  560    * Gets the default region.
  561    *
  562    * @return string
  563    *   The default region for this display.
  564    */
  565   protected function getDefaultRegion() {
  566     return 'content';
  567   }
  568 
  569   /**
  570    * {@inheritdoc}
  571    */
  572   public function __sleep() {
  573     // Only store the definition, not external objects or derived data.
  574     $keys = array_keys($this->toArray());
  575     // In addition, we need to keep the entity type and the "is new" status.
  576     $keys[] = 'entityTypeId';
  577     $keys[] = 'enforceIsNew';
  578     // Keep track of the serialized keys, to avoid calling toArray() again in
  579     // __wakeup(). Because of the way __sleep() works, the data has to be
  580     // present in the object to be included in the serialized values.
  581     $keys[] = '_serializedKeys';
  582     $this->_serializedKeys = $keys;
  583     return $keys;
  584   }
  585 
  586   /**
  587    * {@inheritdoc}
  588    */
  589   public function __wakeup() {
  590     // Determine what were the properties from toArray() that were saved in
  591     // __sleep().
  592     $keys = $this->_serializedKeys;
  593     unset($this->_serializedKeys);
  594     $values = array_intersect_key(get_object_vars($this), array_flip($keys));
  595     // Run those values through the __construct(), as if they came from a
  596     // regular entity load.
  597     $this->__construct($values, $this->entityTypeId);
  598   }
  599 
  600   /**
  601    * Provides the 'system' channel logger service.
  602    *
  603    * @return \Psr\Log\LoggerInterface
  604    *   The 'system' channel logger.
  605    */
  606   protected function getLogger() {
  607     return \Drupal::logger('system');
  608   }
  609 
  610 }