Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
20.00% |
5 / 25 |
CRAP | |
4.11% |
6 / 146 |
| EntityDisplayBase | |
0.00% |
0 / 1 |
|
20.00% |
5 / 25 |
5034.62 | |
4.11% |
6 / 146 |
| __construct | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 13 |
|||
| init | |
0.00% |
0 / 1 |
272 | |
0.00% |
0 / 19 |
|||
| getTargetEntityTypeId | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| getMode | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| getOriginalMode | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| getTargetBundle | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| setTargetBundle | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| id | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| preSave | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
| calculateDependencies | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 14 |
|||
| toArray | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 6 |
|||
| createCopy | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| getComponents | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| getComponent | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 1 |
|||
| setComponent | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 10 |
|||
| removeComponent | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 4 |
|||
| getHighestWeight | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 6 |
|||
| getFieldDefinition | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 2 |
|||
| getFieldDefinitions | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 6 |
|||
| fieldHasDisplayOptions | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| onDependencyRemoval | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 28 |
|||
| getPluginRemovedDependencies | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 9 |
|||
| __sleep | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 6 |
|||
| __wakeup | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 5 |
|||
| getLogger | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Entity\EntityDisplayBase. | |
| */ | |
| namespace Drupal\Core\Entity; | |
| use Drupal\Core\Config\Entity\ConfigEntityBase; | |
| use Drupal\Core\Config\Entity\ConfigEntityInterface; | |
| use Drupal\Core\Field\FieldDefinitionInterface; | |
| use Drupal\Core\Entity\Display\EntityDisplayInterface; | |
| /** | |
| * Provides a common base class for entity view and form displays. | |
| */ | |
| abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDisplayInterface { | |
| /** | |
| * The 'mode' for runtime EntityDisplay objects used to render entities with | |
| * arbitrary display options rather than a configured view mode or form mode. | |
| * | |
| * @todo Prevent creation of a mode with this ID | |
| * https://www.drupal.org/node/2410727 | |
| */ | |
| const CUSTOM_MODE = '_custom'; | |
| /** | |
| * Unique ID for the config entity. | |
| * | |
| * @var string | |
| */ | |
| protected $id; | |
| /** | |
| * Entity type to be displayed. | |
| * | |
| * @var string | |
| */ | |
| protected $targetEntityType; | |
| /** | |
| * Bundle to be displayed. | |
| * | |
| * @var string | |
| */ | |
| protected $bundle; | |
| /** | |
| * A list of field definitions eligible for configuration in this display. | |
| * | |
| * @var \Drupal\Core\Field\FieldDefinitionInterface[] | |
| */ | |
| protected $fieldDefinitions; | |
| /** | |
| * View or form mode to be displayed. | |
| * | |
| * @var string | |
| */ | |
| protected $mode = self::CUSTOM_MODE; | |
| /** | |
| * 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; | |
| /** | |
| * List of component display options, keyed by component name. | |
| * | |
| * @var array | |
| */ | |
| protected $content = array(); | |
| /** | |
| * List of components that are set to be hidden. | |
| * | |
| * @var array | |
| */ | |
| protected $hidden = array(); | |
| /** | |
| * The original view or form mode that was requested (case of view/form modes | |
| * being configured to fall back to the 'default' display). | |
| * | |
| * @var string | |
| */ | |
| protected $originalMode; | |
| /** | |
| * The plugin objects used for this display, keyed by field name. | |
| * | |
| * @var array | |
| */ | |
| protected $plugins = array(); | |
| /** | |
| * Context in which this entity will be used (e.g. 'display', 'form'). | |
| * | |
| * @var string | |
| */ | |
| protected $displayContext; | |
| /** | |
| * The plugin manager used by this entity type. | |
| * | |
| * @var \Drupal\Component\Plugin\PluginManagerBase | |
| */ | |
| protected $pluginManager; | |
| /** | |
| * The renderer. | |
| * | |
| * @var \Drupal\Core\Render\RendererInterface | |
| */ | |
| protected $renderer; | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function __construct(array $values, $entity_type) { | |
| if (!isset($values['targetEntityType']) || !isset($values['bundle'])) { | |
| throw new \InvalidArgumentException('Missing required properties for an EntityDisplay entity.'); | |
| } | |
| if (!$this->entityManager()->getDefinition($values['targetEntityType'])->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) { | |
| throw new \InvalidArgumentException('EntityDisplay entities can only handle fieldable entity types.'); | |
| } | |
| $this->renderer = \Drupal::service('renderer'); | |
| // A plugin manager and a context type needs to be set by extending classes. | |
| if (!isset($this->pluginManager)) { | |
| throw new \RuntimeException('Missing plugin manager.'); | |
| } | |
| if (!isset($this->displayContext)) { | |
| throw new \RuntimeException('Missing display context type.'); | |
| } | |
| parent::__construct($values, $entity_type); | |
| $this->originalMode = $this->mode; | |
| $this->init(); | |
| } | |
| /** | |
| * Initializes the display. | |
| * | |
| * This fills in default options for components: | |
| * - that are not explicitly known as either "visible" or "hidden" in the | |
| * display, | |
| * - or that are not supposed to be configurable. | |
| */ | |
| protected function init() { | |
| // Only populate defaults for "official" view modes and form modes. | |
| if ($this->mode !== static::CUSTOM_MODE) { | |
| // Fill in defaults for extra fields. | |
| $context = $this->displayContext == 'view' ? 'display' : $this->displayContext; | |
| $extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle); | |
| $extra_fields = isset($extra_fields[$context]) ? $extra_fields[$context] : array(); | |
| foreach ($extra_fields as $name => $definition) { | |
| if (!isset($this->content[$name]) && !isset($this->hidden[$name])) { | |
| // Extra fields are visible by default unless they explicitly say so. | |
| if (!isset($definition['visible']) || $definition['visible'] == TRUE) { | |
| $this->content[$name] = array( | |
| 'weight' => $definition['weight'] | |
| ); | |
| } | |
| else { | |
| $this->hidden[$name] = TRUE; | |
| } | |
| } | |
| } | |
| // Fill in defaults for fields. | |
| $fields = $this->getFieldDefinitions(); | |
| foreach ($fields as $name => $definition) { | |
| if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) { | |
| $options = $definition->getDisplayOptions($this->displayContext); | |
| if (!empty($options['type']) && $options['type'] == 'hidden') { | |
| $this->hidden[$name] = TRUE; | |
| } | |
| elseif ($options) { | |
| $this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options); | |
| } | |
| // Note: (base) fields that do not specify display options are not | |
| // tracked in the display at all, in order to avoid cluttering the | |
| // configuration that gets saved back. | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getTargetEntityTypeId() { | |
| return $this->targetEntityType; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getMode() { | |
| return $this->get('mode'); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getOriginalMode() { | |
| return $this->get('originalMode'); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getTargetBundle() { | |
| return $this->bundle; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function setTargetBundle($bundle) { | |
| $this->set('bundle', $bundle); | |
| return $this; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function id() { | |
| return $this->targetEntityType . '.' . $this->bundle . '.' . $this->mode; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function preSave(EntityStorageInterface $storage, $update = TRUE) { | |
| ksort($this->content); | |
| ksort($this->hidden); | |
| parent::preSave($storage, $update); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function calculateDependencies() { | |
| parent::calculateDependencies(); | |
| $target_entity_type = $this->entityManager()->getDefinition($this->targetEntityType); | |
| // Create dependency on the bundle. | |
| $bundle_config_dependency = $target_entity_type->getBundleConfigDependency($this->bundle); | |
| $this->addDependency($bundle_config_dependency['type'], $bundle_config_dependency['name']); | |
| // If field.module is enabled, add dependencies on 'field_config' entities | |
| // for both displayed and hidden fields. We intentionally leave out base | |
| // field overrides, since the field still exists without them. | |
| if (\Drupal::moduleHandler()->moduleExists('field')) { | |
| $components = $this->content + $this->hidden; | |
| $field_definitions = $this->entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle); | |
| foreach (array_intersect_key($field_definitions, $components) as $field_name => $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->entityManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode); | |
| $this->addDependency('config', $mode_entity->getConfigDependencyName()); | |
| } | |
| return $this; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function toArray() { | |
| $properties = parent::toArray(); | |
| // Do not store options for fields whose display is not set to be | |
| // configurable. | |
| foreach ($this->getFieldDefinitions() as $field_name => $definition) { | |
| if (!$definition->isDisplayConfigurable($this->displayContext)) { | |
| unset($properties['content'][$field_name]); | |
| unset($properties['hidden'][$field_name]); | |
| } | |
| } | |
| return $properties; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function createCopy($mode) { | |
| $display = $this->createDuplicate(); | |
| $display->mode = $display->originalMode = $mode; | |
| return $display; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getComponents() { | |
| return $this->content; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getComponent($name) { | |
| return isset($this->content[$name]) ? $this->content[$name] : NULL; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function setComponent($name, array $options = array()) { | |
| // 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; | |
| } | |
| // For a field, fill in default options. | |
| if ($field_definition = $this->getFieldDefinition($name)) { | |
| $options = $this->pluginManager->prepareConfiguration($field_definition->getType(), $options); | |
| } | |
| // Ensure we always have an empty settings and array. | |
| $options += ['settings' => [], 'third_party_settings' => []]; | |
| $this->content[$name] = $options; | |
| unset($this->hidden[$name]); | |
| unset($this->plugins[$name]); | |
| return $this; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function removeComponent($name) { | |
| $this->hidden[$name] = TRUE; | |
| unset($this->content[$name]); | |
| unset($this->plugins[$name]); | |
| return $this; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getHighestWeight() { | |
| $weights = array(); | |
| // Collect weights for the components in the display. | |
| foreach ($this->content as $options) { | |
| if (isset($options['weight'])) { | |
| $weights[] = $options['weight']; | |
| } | |
| } | |
| // Let other modules feedback about their own additions. | |
| $weights = array_merge($weights, \Drupal::moduleHandler()->invokeAll('field_info_max_weight', array($this->targetEntityType, $this->bundle, $this->displayContext, $this->mode))); | |
| return $weights ? max($weights) : NULL; | |
| } | |
| /** | |
| * Gets the field definition of a field. | |
| */ | |
| protected function getFieldDefinition($field_name) { | |
| $definitions = $this->getFieldDefinitions(); | |
| return isset($definitions[$field_name]) ? $definitions[$field_name] : NULL; | |
| } | |
| /** | |
| * Gets the definitions of the fields that are candidate for display. | |
| */ | |
| protected function getFieldDefinitions() { | |
| if (!isset($this->fieldDefinitions)) { | |
| $definitions = \Drupal::entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle); | |
| // For "official" view modes and form modes, ignore fields whose | |
| // definition states they should not be displayed. | |
| if ($this->mode !== static::CUSTOM_MODE) { | |
| $definitions = array_filter($definitions, array($this, 'fieldHasDisplayOptions')); | |
| } | |
| $this->fieldDefinitions = $definitions; | |
| } | |
| return $this->fieldDefinitions; | |
| } | |
| /** | |
| * Determines if a field has options for a given display. | |
| * | |
| * @param FieldDefinitionInterface $definition | |
| * A field definition. | |
| * @return array|null | |
| */ | |
| private function fieldHasDisplayOptions(FieldDefinitionInterface $definition) { | |
| // The display only cares about fields that specify display options. | |
| // Discard base fields that are not rendered through formatters / widgets. | |
| return $definition->getDisplayOptions($this->displayContext); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function onDependencyRemoval(array $dependencies) { | |
| $changed = parent::onDependencyRemoval($dependencies); | |
| foreach ($dependencies['config'] as $entity) { | |
| if ($entity->getEntityTypeId() == 'field_config') { | |
| // Remove components for fields that are being deleted. | |
| $this->removeComponent($entity->getName()); | |
| unset($this->hidden[$entity->getName()]); | |
| $changed = TRUE; | |
| } | |
| } | |
| foreach ($this->getComponents() as $name => $component) { | |
| if ($renderer = $this->getRenderer($name)) { | |
| if (in_array($renderer->getPluginDefinition()['provider'], $dependencies['module'])) { | |
| // Revert to the defaults if the plugin that supplies the widget or | |
| // formatter depends on a module that is being uninstalled. | |
| $this->setComponent($name); | |
| $changed = TRUE; | |
| } | |
| // Give this component the opportunity to react on dependency removal. | |
| $component_removed_dependencies = $this->getPluginRemovedDependencies($renderer->calculateDependencies(), $dependencies); | |
| if ($component_removed_dependencies) { | |
| if ($renderer->onDependencyRemoval($component_removed_dependencies)) { | |
| // Update component settings to reflect changes. | |
| $component['settings'] = $renderer->getSettings(); | |
| $component['third_party_settings'] = []; | |
| foreach ($renderer->getThirdPartyProviders() as $module) { | |
| $component['third_party_settings'][$module] = $renderer->getThirdPartySettings($module); | |
| } | |
| $this->setComponent($name, $component); | |
| $changed = TRUE; | |
| } | |
| // If there are unresolved deleted dependencies left, disable this | |
| // component to avoid the removal of the entire display entity. | |
| if ($this->getPluginRemovedDependencies($renderer->calculateDependencies(), $dependencies)) { | |
| $this->removeComponent($name); | |
| $arguments = [ | |
| '@display' => (string) $this->getEntityType()->getLabel(), | |
| '@id' => $this->id(), | |
| '@name' => $name, | |
| ]; | |
| $this->getLogger()->warning("@display '@id': Component '@name' was disabled because its settings depend on removed dependencies.", $arguments); | |
| $changed = TRUE; | |
| } | |
| } | |
| } | |
| } | |
| return $changed; | |
| } | |
| /** | |
| * Returns the plugin dependencies being removed. | |
| * | |
| * The function recursively computes the intersection between all plugin | |
| * dependencies and all removed dependencies. | |
| * | |
| * Note: The two arguments do not have the same structure. | |
| * | |
| * @param array[] $plugin_dependencies | |
| * A list of dependencies having the same structure as the return value of | |
| * ConfigEntityInterface::calculateDependencies(). | |
| * @param array[] $removed_dependencies | |
| * A list of dependencies having the same structure as the input argument of | |
| * ConfigEntityInterface::onDependencyRemoval(). | |
| * | |
| * @return array | |
| * A recursively computed intersection. | |
| * | |
| * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies() | |
| * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::onDependencyRemoval() | |
| */ | |
| protected function getPluginRemovedDependencies(array $plugin_dependencies, array $removed_dependencies) { | |
| $intersect = []; | |
| foreach ($plugin_dependencies as $type => $dependencies) { | |
| if ($removed_dependencies[$type]) { | |
| // Config and content entities have the dependency names as keys while | |
| // module and theme dependencies are indexed arrays of dependency names. | |
| // @see \Drupal\Core\Config\ConfigManager::callOnDependencyRemoval() | |
| if (in_array($type, ['config', 'content'])) { | |
| $removed = array_intersect_key($removed_dependencies[$type], array_flip($dependencies)); | |
| } | |
| else { | |
| $removed = array_values(array_intersect($removed_dependencies[$type], $dependencies)); | |
| } | |
| if ($removed) { | |
| $intersect[$type] = $removed; | |
| } | |
| } | |
| } | |
| return $intersect; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function __sleep() { | |
| // Only store the definition, not external objects or derived data. | |
| $keys = array_keys($this->toArray()); | |
| // In addition, we need to keep the entity type and the "is new" status. | |
| $keys[] = 'entityTypeId'; | |
| $keys[] = 'enforceIsNew'; | |
| // Keep track of the serialized keys, to avoid calling toArray() again in | |
| // __wakeup(). Because of the way __sleep() works, the data has to be | |
| // present in the object to be included in the serialized values. | |
| $keys[] = '_serializedKeys'; | |
| $this->_serializedKeys = $keys; | |
| return $keys; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function __wakeup() { | |
| // Determine what were the properties from toArray() that were saved in | |
| // __sleep(). | |
| $keys = $this->_serializedKeys; | |
| unset($this->_serializedKeys); | |
| $values = array_intersect_key(get_object_vars($this), array_flip($keys)); | |
| // Run those values through the __construct(), as if they came from a | |
| // regular entity load. | |
| $this->__construct($values, $this->entityTypeId); | |
| } | |
| /** | |
| * Provides the 'system' channel logger service. | |
| * | |
| * @return \Psr\Log\LoggerInterface | |
| * The 'system' channel logger. | |
| */ | |
| protected function getLogger() { | |
| return \Drupal::logger('system'); | |
| } | |
| } |