1<?php
2
3namespace Drupal\layout_builder\Plugin\Block;
4
5use Drupal\Core\Block\BlockBase;
6use Drupal\Core\Cache\CacheableMetadata;
7use Drupal\Core\Entity\EntityFieldManagerInterface;
8use Drupal\Core\Entity\EntityTypeManagerInterface;
9use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
10use Drupal\Core\Plugin\ContextAwarePluginInterface;
11use Drupal\Core\Render\Element;
12use Drupal\Core\Session\AccountInterface;
13use Drupal\Core\StringTranslation\TranslatableMarkup;
14use Symfony\Component\DependencyInjection\ContainerInterface;
15
16/**
17 * Provides a block that renders an extra field from an entity.
18 *
19 * This block handles fields that are provided by implementations of
20 * hook_entity_extra_field_info().
21 *
22 * @see \Drupal\layout_builder\Plugin\Block\FieldBlock
23 *   This block plugin handles all other field entities not provided by
24 *   hook_entity_extra_field_info().
25 *
26 * @Block(
27 *   id = "extra_field_block",
28 *   deriver = "\Drupal\layout_builder\Plugin\Derivative\ExtraFieldBlockDeriver",
29 * )
30 *
31 * @internal
32 *   Plugin classes are internal.
33 */
34class ExtraFieldBlock extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
35
36  /**
37   * The entity field manager.
38   *
39   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
40   */
41  protected $entityFieldManager;
42
43  /**
44   * The field name.
45   *
46   * @var string
47   */
48  protected $fieldName;
49
50  /**
51   * The entity type manager.
52   *
53   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
54   */
55  protected $entityTypeManager;
56
57  /**
58   * Constructs a new ExtraFieldBlock.
59   *
60   * @param array $configuration
61   *   A configuration array containing information about the plugin instance.
62   * @param string $plugin_id
63   *   The plugin ID for the plugin instance.
64   * @param mixed $plugin_definition
65   *   The plugin implementation definition.
66   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
67   *   The entity type manager.
68   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
69   *   The entity field manager.
70   */
71  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
72    $this->entityTypeManager = $entity_type_manager;
73    $this->entityFieldManager = $entity_field_manager;
74    // Get field name from the plugin ID.
75    list (, , , $field_name) = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4);
76    assert(!empty($field_name));
77    $this->fieldName = $field_name;
78
79    parent::__construct($configuration, $plugin_id, $plugin_definition);
80  }
81
82  /**
83   * {@inheritdoc}
84   */
85  public function defaultConfiguration() {
86    return [
87      'label_display' => FALSE,
88      'formatter' => [
89        'settings' => [],
90        'third_party_settings' => [],
91      ],
92    ];
93  }
94
95  /**
96   * {@inheritdoc}
97   */
98  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
99    return new static(
100      $configuration,
101      $plugin_id,
102      $plugin_definition,
103      $container->get('entity_type.manager'),
104      $container->get('entity_field.manager')
105    );
106  }
107
108  /**
109   * Gets the entity that has the field.
110   *
111   * @return \Drupal\Core\Entity\FieldableEntityInterface
112   *   The entity.
113   */
114  protected function getEntity() {
115    return $this->getContextValue('entity');
116  }
117
118  /**
119   * {@inheritdoc}
120   */
121  public function build() {
122    $entity = $this->getEntity();
123    // Add a placeholder to replace after the entity view is built.
124    // @see layout_builder_entity_view_alter().
125    $extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
126    if (!isset($extra_fields['display'][$this->fieldName])) {
127      $build = [];
128    }
129    else {
130      $build = [
131        '#extra_field_placeholder_field_name' => $this->fieldName,
132        // Always provide a placeholder. The Layout Builder will NOT invoke
133        // hook_entity_view_alter() so extra fields will not be added to the
134        // render array. If the hook is invoked the placeholder will be
135        // replaced.
136        // @see ::replaceFieldPlaceholder()
137        '#markup' => $this->t('Placeholder for the @preview_fallback', ['@preview_fallback' => $this->getPreviewFallbackString()]),
138      ];
139    }
140    CacheableMetadata::createFromObject($this)->applyTo($build);
141    return $build;
142  }
143
144  /**
145   * {@inheritdoc}
146   */
147  public function getPreviewFallbackString() {
148    $entity = $this->getEntity();
149    $extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
150    return new TranslatableMarkup('"@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]);
151  }
152
153  /**
154   * Replaces all placeholders for a given field.
155   *
156   * @param array $build
157   *   The built render array for the elements.
158   * @param array $built_field
159   *   The render array to replace the placeholder.
160   * @param string $field_name
161   *   The field name.
162   *
163   * @see ::build()
164   */
165  public static function replaceFieldPlaceholder(array &$build, array $built_field, $field_name) {
166    foreach (Element::children($build) as $child) {
167      if (isset($build[$child]['#extra_field_placeholder_field_name']) && $build[$child]['#extra_field_placeholder_field_name'] === $field_name) {
168        $placeholder_cache = CacheableMetadata::createFromRenderArray($build[$child]);
169        $built_cache = CacheableMetadata::createFromRenderArray($built_field);
170        $merged_cache = $placeholder_cache->merge($built_cache);
171        $build[$child] = $built_field;
172        $merged_cache->applyTo($build);
173      }
174      else {
175        static::replaceFieldPlaceholder($build[$child], $built_field, $field_name);
176      }
177    }
178  }
179
180  /**
181   * {@inheritdoc}
182   */
183  protected function blockAccess(AccountInterface $account) {
184    return $this->getEntity()->access('view', $account, TRUE);
185  }
186
187}
188