d32e71baa28f48c1c39daa93e23e186f108a4f08
[yaffs-website] / web / core / modules / layout_builder / src / Plugin / Block / FieldBlock.php
1 <?php
2
3 namespace Drupal\layout_builder\Plugin\Block;
4
5 use Drupal\Component\Plugin\Factory\DefaultFactory;
6 use Drupal\Component\Utility\NestedArray;
7 use Drupal\Core\Access\AccessResult;
8 use Drupal\Core\Block\BlockBase;
9 use Drupal\Core\Cache\CacheableMetadata;
10 use Drupal\Core\Entity\EntityDisplayBase;
11 use Drupal\Core\Entity\EntityFieldManagerInterface;
12 use Drupal\Core\Entity\FieldableEntityInterface;
13 use Drupal\Core\Extension\ModuleHandlerInterface;
14 use Drupal\Core\Field\FieldDefinitionInterface;
15 use Drupal\Core\Field\FormatterInterface;
16 use Drupal\Core\Field\FormatterPluginManager;
17 use Drupal\Core\Form\FormStateInterface;
18 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
19 use Drupal\Core\Plugin\ContextAwarePluginInterface;
20 use Drupal\Core\Render\Element;
21 use Drupal\Core\Session\AccountInterface;
22 use Drupal\Core\StringTranslation\TranslatableMarkup;
23 use Symfony\Component\DependencyInjection\ContainerInterface;
24
25 /**
26  * Provides a block that renders a field from an entity.
27  *
28  * @Block(
29  *   id = "field_block",
30  *   deriver = "\Drupal\layout_builder\Plugin\Derivative\FieldBlockDeriver",
31  * )
32  */
33 class FieldBlock extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
34
35   /**
36    * The entity field manager.
37    *
38    * @var \Drupal\Core\Entity\EntityFieldManagerInterface
39    */
40   protected $entityFieldManager;
41
42   /**
43    * The formatter manager.
44    *
45    * @var \Drupal\Core\Field\FormatterPluginManager
46    */
47   protected $formatterManager;
48
49   /**
50    * The entity type ID.
51    *
52    * @var string
53    */
54   protected $entityTypeId;
55
56   /**
57    * The bundle ID.
58    *
59    * @var string
60    */
61   protected $bundle;
62
63   /**
64    * The field name.
65    *
66    * @var string
67    */
68   protected $fieldName;
69
70   /**
71    * The field definition.
72    *
73    * @var \Drupal\Core\Field\FieldDefinitionInterface
74    */
75   protected $fieldDefinition;
76
77   /**
78    * The module handler.
79    *
80    * @var \Drupal\Core\Extension\ModuleHandlerInterface
81    */
82   protected $moduleHandler;
83
84   /**
85    * Constructs a new FieldBlock.
86    *
87    * @param array $configuration
88    *   A configuration array containing information about the plugin instance.
89    * @param string $plugin_id
90    *   The plugin ID for the plugin instance.
91    * @param mixed $plugin_definition
92    *   The plugin implementation definition.
93    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
94    *   The entity field manager.
95    * @param \Drupal\Core\Field\FormatterPluginManager $formatter_manager
96    *   The formatter manager.
97    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
98    *   The module handler.
99    */
100   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FormatterPluginManager $formatter_manager, ModuleHandlerInterface $module_handler) {
101     $this->entityFieldManager = $entity_field_manager;
102     $this->formatterManager = $formatter_manager;
103     $this->moduleHandler = $module_handler;
104
105     // Get the entity type and field name from the plugin ID.
106     list (, $entity_type_id, $bundle, $field_name) = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4);
107     $this->entityTypeId = $entity_type_id;
108     $this->bundle = $bundle;
109     $this->fieldName = $field_name;
110
111     parent::__construct($configuration, $plugin_id, $plugin_definition);
112   }
113
114   /**
115    * {@inheritdoc}
116    */
117   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
118     return new static(
119       $configuration,
120       $plugin_id,
121       $plugin_definition,
122       $container->get('entity_field.manager'),
123       $container->get('plugin.manager.field.formatter'),
124       $container->get('module_handler')
125     );
126   }
127
128   /**
129    * Gets the entity that has the field.
130    *
131    * @return \Drupal\Core\Entity\FieldableEntityInterface
132    *   The entity.
133    */
134   protected function getEntity() {
135     return $this->getContextValue('entity');
136   }
137
138   /**
139    * {@inheritdoc}
140    */
141   public function build() {
142     $display_settings = $this->getConfiguration()['formatter'];
143     $entity = $this->getEntity();
144     $build = $entity->get($this->fieldName)->view($display_settings);
145     if (!empty($entity->in_preview) && !Element::getVisibleChildren($build)) {
146       $build['content']['#markup'] = new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]);
147     }
148     CacheableMetadata::createFromObject($this)->applyTo($build);
149     return $build;
150   }
151
152   /**
153    * {@inheritdoc}
154    */
155   protected function blockAccess(AccountInterface $account) {
156     $entity = $this->getEntity();
157
158     // First consult the entity.
159     $access = $entity->access('view', $account, TRUE);
160     if (!$access->isAllowed()) {
161       return $access;
162     }
163
164     // Check that the entity in question has this field.
165     if (!$entity instanceof FieldableEntityInterface || !$entity->hasField($this->fieldName)) {
166       return $access->andIf(AccessResult::forbidden());
167     }
168
169     // Check field access.
170     $field = $entity->get($this->fieldName);
171     $access = $access->andIf($field->access('view', $account, TRUE));
172     if (!$access->isAllowed()) {
173       return $access;
174     }
175
176     // Check to see if the field has any values.
177     if ($field->isEmpty()) {
178       return $access->andIf(AccessResult::forbidden());
179     }
180     return $access;
181   }
182
183   /**
184    * {@inheritdoc}
185    */
186   public function defaultConfiguration() {
187     return [
188       'label_display' => FALSE,
189       'formatter' => [
190         'label' => 'above',
191         'type' => $this->pluginDefinition['default_formatter'],
192         'settings' => [],
193         'third_party_settings' => [],
194       ],
195     ];
196   }
197
198   /**
199    * {@inheritdoc}
200    */
201   public function blockForm($form, FormStateInterface $form_state) {
202     $config = $this->getConfiguration();
203
204     $form['formatter'] = [
205       '#tree' => TRUE,
206       '#process' => [
207         [$this, 'formatterSettingsProcessCallback'],
208       ],
209     ];
210     $form['formatter']['label'] = [
211       '#type' => 'select',
212       '#title' => $this->t('Label'),
213       // @todo This is directly copied from
214       //   \Drupal\field_ui\Form\EntityViewDisplayEditForm::getFieldLabelOptions(),
215       //   resolve this in https://www.drupal.org/project/drupal/issues/2933924.
216       '#options' => [
217         'above' => $this->t('Above'),
218         'inline' => $this->t('Inline'),
219         'hidden' => '- ' . $this->t('Hidden') . ' -',
220         'visually_hidden' => '- ' . $this->t('Visually Hidden') . ' -',
221       ],
222       '#default_value' => $config['formatter']['label'],
223     ];
224
225     $form['formatter']['type'] = [
226       '#type' => 'select',
227       '#title' => $this->t('Formatter'),
228       '#options' => $this->getApplicablePluginOptions($this->getFieldDefinition()),
229       '#required' => TRUE,
230       '#default_value' => $config['formatter']['type'],
231       '#ajax' => [
232         'callback' => [static::class, 'formatterSettingsAjaxCallback'],
233         'wrapper' => 'formatter-settings-wrapper',
234       ],
235     ];
236
237     // Add the formatter settings to the form via AJAX.
238     $form['formatter']['settings_wrapper'] = [
239       '#prefix' => '<div id="formatter-settings-wrapper">',
240       '#suffix' => '</div>',
241     ];
242
243     return $form;
244   }
245
246   /**
247    * Render API callback: builds the formatter settings elements.
248    */
249   public function formatterSettingsProcessCallback(array &$element, FormStateInterface $form_state, array &$complete_form) {
250     if ($formatter = $this->getFormatter($element['#parents'], $form_state)) {
251       $element['settings_wrapper']['settings'] = $formatter->settingsForm($complete_form, $form_state);
252       $element['settings_wrapper']['settings']['#parents'] = array_merge($element['#parents'], ['settings']);
253       $element['settings_wrapper']['third_party_settings'] = $this->thirdPartySettingsForm($formatter, $this->getFieldDefinition(), $complete_form, $form_state);
254       $element['settings_wrapper']['third_party_settings']['#parents'] = array_merge($element['#parents'], ['third_party_settings']);
255
256       // Store the array parents for our element so that we can retrieve the
257       // formatter settings in our AJAX callback.
258       $form_state->set('field_block_array_parents', $element['#array_parents']);
259     }
260     return $element;
261   }
262
263   /**
264    * Adds the formatter third party settings forms.
265    *
266    * @param \Drupal\Core\Field\FormatterInterface $plugin
267    *   The formatter.
268    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
269    *   The field definition.
270    * @param array $form
271    *   The (entire) configuration form array.
272    * @param \Drupal\Core\Form\FormStateInterface $form_state
273    *   The form state.
274    *
275    * @return array
276    *   The formatter third party settings form.
277    */
278   protected function thirdPartySettingsForm(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) {
279     $settings_form = [];
280     // Invoke hook_field_formatter_third_party_settings_form(), keying resulting
281     // subforms by module name.
282     foreach ($this->moduleHandler->getImplementations('field_formatter_third_party_settings_form') as $module) {
283       $settings_form[$module] = $this->moduleHandler->invoke($module, 'field_formatter_third_party_settings_form', [
284         $plugin,
285         $field_definition,
286         EntityDisplayBase::CUSTOM_MODE,
287         $form,
288         $form_state,
289       ]);
290     }
291     return $settings_form;
292   }
293
294   /**
295    * Render API callback: gets the layout settings elements.
296    */
297   public static function formatterSettingsAjaxCallback(array $form, FormStateInterface $form_state) {
298     $formatter_array_parents = $form_state->get('field_block_array_parents');
299     return NestedArray::getValue($form, array_merge($formatter_array_parents, ['settings_wrapper']));
300   }
301
302   /**
303    * {@inheritdoc}
304    */
305   public function blockSubmit($form, FormStateInterface $form_state) {
306     $this->configuration['formatter'] = $form_state->getValue('formatter');
307   }
308
309   /**
310    * Gets the field definition.
311    *
312    * @return \Drupal\Core\Field\FieldDefinitionInterface
313    *   The field definition.
314    */
315   protected function getFieldDefinition() {
316     if (empty($this->fieldDefinition)) {
317       $field_definitions = $this->entityFieldManager->getFieldDefinitions($this->entityTypeId, $this->bundle);
318       $this->fieldDefinition = $field_definitions[$this->fieldName];
319     }
320     return $this->fieldDefinition;
321   }
322
323   /**
324    * Returns an array of applicable formatter options for a field.
325    *
326    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
327    *   The field definition.
328    *
329    * @return array
330    *   An array of applicable formatter options.
331    *
332    * @see \Drupal\field_ui\Form\EntityDisplayFormBase::getApplicablePluginOptions()
333    */
334   protected function getApplicablePluginOptions(FieldDefinitionInterface $field_definition) {
335     $options = $this->formatterManager->getOptions($field_definition->getType());
336     $applicable_options = [];
337     foreach ($options as $option => $label) {
338       $plugin_class = DefaultFactory::getPluginClass($option, $this->formatterManager->getDefinition($option));
339       if ($plugin_class::isApplicable($field_definition)) {
340         $applicable_options[$option] = $label;
341       }
342     }
343     return $applicable_options;
344   }
345
346   /**
347    * Gets the formatter object.
348    *
349    * @param array $parents
350    *   The #parents of the element representing the formatter.
351    * @param \Drupal\Core\Form\FormStateInterface $form_state
352    *   The current state of the form.
353    *
354    * @return \Drupal\Core\Field\FormatterInterface
355    *   The formatter object.
356    */
357   protected function getFormatter(array $parents, FormStateInterface $form_state) {
358     // Use the processed values, if available.
359     $configuration = NestedArray::getValue($form_state->getValues(), $parents);
360     if (!$configuration) {
361       // Next check the raw user input.
362       $configuration = NestedArray::getValue($form_state->getUserInput(), $parents);
363       if (!$configuration) {
364         // If no user input exists, use the default values.
365         $configuration = $this->getConfiguration()['formatter'];
366       }
367     }
368
369     return $this->formatterManager->getInstance([
370       'configuration' => $configuration,
371       'field_definition' => $this->getFieldDefinition(),
372       'view_mode' => EntityDisplayBase::CUSTOM_MODE,
373       'prepare' => TRUE,
374     ]);
375   }
376
377 }