Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExtraFieldSource
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 7
156
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 defaultSettings
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 settingsSummary
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPropValue
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 settingsForm
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 renderPlaceholder
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getDefinitions
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\Plugin\UiPatterns\Source;
6
7use Drupal\Core\Entity\Entity\EntityViewDisplay;
8use Drupal\Core\Entity\EntityFieldManagerInterface;
9use Drupal\Core\Form\FormStateInterface;
10use Drupal\Core\Plugin\Context\ContextDefinition;
11use Drupal\Core\StringTranslation\TranslatableMarkup;
12use Drupal\display_builder\RenderableBuilderTrait;
13use Drupal\ui_patterns\Attribute\Source;
14use Drupal\ui_patterns\SourcePluginBase;
15use Symfony\Component\DependencyInjection\ContainerInterface;
16
17/**
18 * Plugin implementation of the source.
19 */
20#[Source(
21  id: 'extra_field',
22  label: new TranslatableMarkup('Extra field'),
23  description: new TranslatableMarkup('An entity extra field.'),
24  prop_types: ['slot'],
25  context_definitions: [
26    'entity' => new ContextDefinition('entity', label: new TranslatableMarkup('Entity'), required: TRUE),
27    'view_mode' => new ContextDefinition('string', label: new TranslatableMarkup('View mode'), required: FALSE),
28  ],
29  metadata: ['group' => new TranslatableMarkup('Fields')],
30)]
31class ExtraFieldSource extends SourcePluginBase {
32
33  use RenderableBuilderTrait;
34
35  /**
36   * The entity field manager.
37   */
38  protected EntityFieldManagerInterface $entityFieldManager;
39
40  /**
41   * {@inheritdoc}
42   */
43  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
44    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
45    $instance->entityFieldManager = $container->get('entity_field.manager');
46
47    return $instance;
48  }
49
50  /**
51   * {@inheritdoc}
52   */
53  public function defaultSettings(): array {
54    return [
55      'field' => NULL,
56    ];
57  }
58
59  /**
60   * {@inheritdoc}
61   */
62  public function settingsSummary(): array {
63    return [
64      $this->getDefinitions()[$this->getSetting('field')]['label'] ?? '',
65    ];
66  }
67
68  /**
69   * {@inheritdoc}
70   */
71  public function getPropValue(): mixed {
72    $field = $this->getSetting('field');
73
74    if (!$field) {
75      return [];
76    }
77
78    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
79    $entity = $this->getContextValue('entity');
80    $view_mode = $this->getContextValue('view_mode') ?? 'default';
81
82    if (!$entity->id()) {
83      return $this->renderPlaceholder($field);
84    }
85
86    $entity_type_id = $entity->getEntityTypeId();
87    $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
88
89    // Register the extra field as a component on the display. Implementations
90    // of hook_entity_view() and hook_ENTITY_TYPE_view() often guard rendering
91    // with $display->getComponent($field_name), which would silently skip
92    // the field if it is not enabled in the underlying entity view display.
93    if (!$display->getComponent($field)) {
94      $display->setComponent($field);
95    }
96
97    // Mirror core EntityViewBuilder::buildComponents(): invoke prepare_view
98    // before the view hooks so implementations can set up their internal state.
99    // @see \Drupal\Core\Entity\EntityViewBuilder::buildComponents()
100    $this->moduleHandler->invokeAll('entity_prepare_view', [
101      $entity_type_id,
102      [$entity->id() => $entity],
103      [$entity->bundle() => $display],
104      $view_mode,
105    ]);
106
107    /** @var array<string, mixed> $build */
108    $build = [];
109
110    // Invoke both the entity-type-specific and the generic hook so all
111    // extra-field implementations are reached regardless of which hook they
112    // use. Core EntityViewBuilder does the same.
113    // @see \Drupal\Core\Entity\EntityViewBuilder::view()
114    $this->moduleHandler->invokeAll($entity_type_id . '_view', [&$build, $entity, $display, $view_mode]);
115    $this->moduleHandler->invokeAll('entity_view', [&$build, $entity, $display, $view_mode]);
116
117    return $build[$field] ?? [];
118  }
119
120  /**
121   * {@inheritdoc}
122   */
123  public function settingsForm(array $form, FormStateInterface $form_state): array {
124    $options = [];
125
126    foreach ($this->getDefinitions() as $field_name => $definition) {
127      $options[$field_name] = $definition['label'];
128    }
129    $form['field'] = [
130      '#type' => 'select',
131      '#options' => $options,
132      '#default_value' => $this->getSetting('field'),
133    ];
134
135    return $form;
136  }
137
138  /**
139   * Render placeholder when a proper entity is not loaded.
140   *
141   * @param string $field
142   *   Extra field ID.
143   *
144   * @return array
145   *   A renderable array.
146   */
147  protected function renderPlaceholder(string $field): array {
148    $definition = $this->getDefinitions()[$field];
149
150    if (!$definition) {
151      return [];
152    }
153
154    $label = $definition['label'] ?? '';
155    $build = $this->buildPlaceholderButton($this->t('Extra field: @field', ['@field' => $label]));
156    $build['#attributes']['class'][] = 'db-background';
157
158    return $build;
159  }
160
161  /**
162   * Get extra field definitions.
163   *
164   * Extra fields are not plugins but old-fashioned hooks.
165   *
166   * @throws \Drupal\Component\Plugin\Exception\ContextException
167   *
168   * @return array
169   *   A list of extra field definition.
170   */
171  protected function getDefinitions(): array {
172    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
173    $entity = $this->getContextValue('entity');
174    $entity_type_id = $entity->getEntityTypeId();
175    $bundle = $entity->bundle();
176
177    $extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle);
178
179    return $extra_fields['display'] ?? [];
180  }
181
182}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

ExtraFieldSource->create
43  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
44    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
45    $instance->entityFieldManager = $container->get('entity_field.manager');
46
47    return $instance;
48  }
ExtraFieldSource->defaultSettings
55      'field' => NULL,
56    ];
57  }
ExtraFieldSource->getDefinitions
173    $entity = $this->getContextValue('entity');
174    $entity_type_id = $entity->getEntityTypeId();
175    $bundle = $entity->bundle();
176
177    $extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle);
178
179    return $extra_fields['display'] ?? [];
180  }
ExtraFieldSource->getPropValue
72    $field = $this->getSetting('field');
73
74    if (!$field) {
 
75      return [];
72    $field = $this->getSetting('field');
73
74    if (!$field) {
 
79    $entity = $this->getContextValue('entity');
80    $view_mode = $this->getContextValue('view_mode') ?? 'default';
81
82    if (!$entity->id()) {
 
83      return $this->renderPlaceholder($field);
72    $field = $this->getSetting('field');
73
74    if (!$field) {
 
79    $entity = $this->getContextValue('entity');
80    $view_mode = $this->getContextValue('view_mode') ?? 'default';
81
82    if (!$entity->id()) {
 
86    $entity_type_id = $entity->getEntityTypeId();
87    $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
88
89    // Register the extra field as a component on the display. Implementations
90    // of hook_entity_view() and hook_ENTITY_TYPE_view() often guard rendering
91    // with $display->getComponent($field_name), which would silently skip
92    // the field if it is not enabled in the underlying entity view display.
93    if (!$display->getComponent($field)) {
 
94      $display->setComponent($field);
95    }
96
97    // Mirror core EntityViewBuilder::buildComponents(): invoke prepare_view
98    // before the view hooks so implementations can set up their internal state.
99    // @see \Drupal\Core\Entity\EntityViewBuilder::buildComponents()
100    $this->moduleHandler->invokeAll('entity_prepare_view', [
 
100    $this->moduleHandler->invokeAll('entity_prepare_view', [
101      $entity_type_id,
102      [$entity->id() => $entity],
103      [$entity->bundle() => $display],
104      $view_mode,
105    ]);
106
107    /** @var array<string, mixed> $build */
108    $build = [];
109
110    // Invoke both the entity-type-specific and the generic hook so all
111    // extra-field implementations are reached regardless of which hook they
112    // use. Core EntityViewBuilder does the same.
113    // @see \Drupal\Core\Entity\EntityViewBuilder::view()
114    $this->moduleHandler->invokeAll($entity_type_id . '_view', [&$build, $entity, $display, $view_mode]);
115    $this->moduleHandler->invokeAll('entity_view', [&$build, $entity, $display, $view_mode]);
116
117    return $build[$field] ?? [];
118  }
72    $field = $this->getSetting('field');
73
74    if (!$field) {
 
79    $entity = $this->getContextValue('entity');
80    $view_mode = $this->getContextValue('view_mode') ?? 'default';
81
82    if (!$entity->id()) {
 
86    $entity_type_id = $entity->getEntityTypeId();
87    $display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
88
89    // Register the extra field as a component on the display. Implementations
90    // of hook_entity_view() and hook_ENTITY_TYPE_view() often guard rendering
91    // with $display->getComponent($field_name), which would silently skip
92    // the field if it is not enabled in the underlying entity view display.
93    if (!$display->getComponent($field)) {
 
100    $this->moduleHandler->invokeAll('entity_prepare_view', [
101      $entity_type_id,
102      [$entity->id() => $entity],
103      [$entity->bundle() => $display],
104      $view_mode,
105    ]);
106
107    /** @var array<string, mixed> $build */
108    $build = [];
109
110    // Invoke both the entity-type-specific and the generic hook so all
111    // extra-field implementations are reached regardless of which hook they
112    // use. Core EntityViewBuilder does the same.
113    // @see \Drupal\Core\Entity\EntityViewBuilder::view()
114    $this->moduleHandler->invokeAll($entity_type_id . '_view', [&$build, $entity, $display, $view_mode]);
115    $this->moduleHandler->invokeAll('entity_view', [&$build, $entity, $display, $view_mode]);
116
117    return $build[$field] ?? [];
118  }
ExtraFieldSource->renderPlaceholder
147  protected function renderPlaceholder(string $field): array {
148    $definition = $this->getDefinitions()[$field];
149
150    if (!$definition) {
 
151      return [];
147  protected function renderPlaceholder(string $field): array {
148    $definition = $this->getDefinitions()[$field];
149
150    if (!$definition) {
 
154    $label = $definition['label'] ?? '';
155    $build = $this->buildPlaceholderButton($this->t('Extra field: @field', ['@field' => $label]));
156    $build['#attributes']['class'][] = 'db-background';
157
158    return $build;
159  }
ExtraFieldSource->settingsForm
123  public function settingsForm(array $form, FormStateInterface $form_state): array {
124    $options = [];
125
126    foreach ($this->getDefinitions() as $field_name => $definition) {
 
126    foreach ($this->getDefinitions() as $field_name => $definition) {
 
126    foreach ($this->getDefinitions() as $field_name => $definition) {
 
126    foreach ($this->getDefinitions() as $field_name => $definition) {
 
126    foreach ($this->getDefinitions() as $field_name => $definition) {
127      $options[$field_name] = $definition['label'];
128    }
129    $form['field'] = [
130      '#type' => 'select',
131      '#options' => $options,
132      '#default_value' => $this->getSetting('field'),
133    ];
134
135    return $form;
136  }
123  public function settingsForm(array $form, FormStateInterface $form_state): array {
124    $options = [];
125
126    foreach ($this->getDefinitions() as $field_name => $definition) {
 
126    foreach ($this->getDefinitions() as $field_name => $definition) {
 
126    foreach ($this->getDefinitions() as $field_name => $definition) {
127      $options[$field_name] = $definition['label'];
128    }
129    $form['field'] = [
130      '#type' => 'select',
131      '#options' => $options,
132      '#default_value' => $this->getSetting('field'),
133    ];
134
135    return $form;
136  }
123  public function settingsForm(array $form, FormStateInterface $form_state): array {
124    $options = [];
125
126    foreach ($this->getDefinitions() as $field_name => $definition) {
 
126    foreach ($this->getDefinitions() as $field_name => $definition) {
127      $options[$field_name] = $definition['label'];
128    }
129    $form['field'] = [
130      '#type' => 'select',
131      '#options' => $options,
132      '#default_value' => $this->getSetting('field'),
133    ];
134
135    return $form;
136  }
ExtraFieldSource->settingsSummary
64      $this->getDefinitions()[$this->getSetting('field')]['label'] ?? '',
65    ];
66  }