Chris@14
|
1 <?php
|
Chris@14
|
2
|
Chris@14
|
3 namespace Drupal\layout_builder\Entity;
|
Chris@14
|
4
|
Chris@18
|
5 use Drupal\Component\Plugin\ConfigurableInterface;
|
Chris@18
|
6 use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
Chris@18
|
7 use Drupal\Component\Plugin\PluginBase;
|
Chris@18
|
8 use Drupal\Core\Cache\CacheableMetadata;
|
Chris@14
|
9 use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
|
Chris@14
|
10 use Drupal\Core\Entity\EntityStorageInterface;
|
Chris@14
|
11 use Drupal\Core\Entity\FieldableEntityInterface;
|
Chris@18
|
12 use Drupal\Core\Plugin\Context\Context;
|
Chris@18
|
13 use Drupal\Core\Plugin\Context\ContextDefinition;
|
Chris@17
|
14 use Drupal\Core\Plugin\Context\EntityContext;
|
Chris@18
|
15 use Drupal\Core\Render\Element;
|
Chris@14
|
16 use Drupal\Core\StringTranslation\TranslatableMarkup;
|
Chris@14
|
17 use Drupal\field\Entity\FieldConfig;
|
Chris@14
|
18 use Drupal\field\Entity\FieldStorageConfig;
|
Chris@18
|
19 use Drupal\layout_builder\LayoutEntityHelperTrait;
|
Chris@17
|
20 use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
|
Chris@18
|
21 use Drupal\layout_builder\QuickEditIntegration;
|
Chris@14
|
22 use Drupal\layout_builder\Section;
|
Chris@14
|
23 use Drupal\layout_builder\SectionComponent;
|
Chris@14
|
24 use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
|
Chris@14
|
25
|
Chris@14
|
26 /**
|
Chris@14
|
27 * Provides an entity view display entity that has a layout.
|
Chris@14
|
28 */
|
Chris@14
|
29 class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface {
|
Chris@14
|
30
|
Chris@14
|
31 use SectionStorageTrait;
|
Chris@18
|
32 use LayoutEntityHelperTrait;
|
Chris@14
|
33
|
Chris@14
|
34 /**
|
Chris@17
|
35 * The entity field manager.
|
Chris@17
|
36 *
|
Chris@17
|
37 * @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
Chris@17
|
38 */
|
Chris@17
|
39 protected $entityFieldManager;
|
Chris@17
|
40
|
Chris@17
|
41 /**
|
Chris@17
|
42 * {@inheritdoc}
|
Chris@17
|
43 */
|
Chris@17
|
44 public function __construct(array $values, $entity_type) {
|
Chris@17
|
45 // Set $entityFieldManager before calling the parent constructor because the
|
Chris@17
|
46 // constructor will call init() which then calls setComponent() which needs
|
Chris@17
|
47 // $entityFieldManager.
|
Chris@17
|
48 $this->entityFieldManager = \Drupal::service('entity_field.manager');
|
Chris@17
|
49 parent::__construct($values, $entity_type);
|
Chris@17
|
50 }
|
Chris@17
|
51
|
Chris@17
|
52 /**
|
Chris@14
|
53 * {@inheritdoc}
|
Chris@14
|
54 */
|
Chris@14
|
55 public function isOverridable() {
|
Chris@18
|
56 return $this->isLayoutBuilderEnabled() && $this->getThirdPartySetting('layout_builder', 'allow_custom', FALSE);
|
Chris@14
|
57 }
|
Chris@14
|
58
|
Chris@14
|
59 /**
|
Chris@14
|
60 * {@inheritdoc}
|
Chris@14
|
61 */
|
Chris@14
|
62 public function setOverridable($overridable = TRUE) {
|
Chris@14
|
63 $this->setThirdPartySetting('layout_builder', 'allow_custom', $overridable);
|
Chris@18
|
64 // Enable Layout Builder if it's not already enabled and overriding.
|
Chris@18
|
65 if ($overridable && !$this->isLayoutBuilderEnabled()) {
|
Chris@18
|
66 $this->enableLayoutBuilder();
|
Chris@18
|
67 }
|
Chris@14
|
68 return $this;
|
Chris@14
|
69 }
|
Chris@14
|
70
|
Chris@14
|
71 /**
|
Chris@14
|
72 * {@inheritdoc}
|
Chris@14
|
73 */
|
Chris@17
|
74 public function isLayoutBuilderEnabled() {
|
Chris@18
|
75 // To prevent infinite recursion, Layout Builder must not be enabled for the
|
Chris@18
|
76 // '_custom' view mode that is used for on-the-fly rendering of fields in
|
Chris@18
|
77 // isolation from the entity.
|
Chris@18
|
78 if ($this->getOriginalMode() === static::CUSTOM_MODE) {
|
Chris@18
|
79 return FALSE;
|
Chris@18
|
80 }
|
Chris@17
|
81 return (bool) $this->getThirdPartySetting('layout_builder', 'enabled');
|
Chris@17
|
82 }
|
Chris@17
|
83
|
Chris@17
|
84 /**
|
Chris@17
|
85 * {@inheritdoc}
|
Chris@17
|
86 */
|
Chris@17
|
87 public function enableLayoutBuilder() {
|
Chris@17
|
88 $this->setThirdPartySetting('layout_builder', 'enabled', TRUE);
|
Chris@17
|
89 return $this;
|
Chris@17
|
90 }
|
Chris@17
|
91
|
Chris@17
|
92 /**
|
Chris@17
|
93 * {@inheritdoc}
|
Chris@17
|
94 */
|
Chris@17
|
95 public function disableLayoutBuilder() {
|
Chris@17
|
96 $this->setOverridable(FALSE);
|
Chris@17
|
97 $this->setThirdPartySetting('layout_builder', 'enabled', FALSE);
|
Chris@17
|
98 return $this;
|
Chris@17
|
99 }
|
Chris@17
|
100
|
Chris@17
|
101 /**
|
Chris@17
|
102 * {@inheritdoc}
|
Chris@17
|
103 */
|
Chris@14
|
104 public function getSections() {
|
Chris@14
|
105 return $this->getThirdPartySetting('layout_builder', 'sections', []);
|
Chris@14
|
106 }
|
Chris@14
|
107
|
Chris@14
|
108 /**
|
Chris@14
|
109 * {@inheritdoc}
|
Chris@14
|
110 */
|
Chris@14
|
111 protected function setSections(array $sections) {
|
Chris@18
|
112 // Third-party settings must be completely unset instead of stored as an
|
Chris@18
|
113 // empty array.
|
Chris@18
|
114 if (!$sections) {
|
Chris@18
|
115 $this->unsetThirdPartySetting('layout_builder', 'sections');
|
Chris@18
|
116 }
|
Chris@18
|
117 else {
|
Chris@18
|
118 $this->setThirdPartySetting('layout_builder', 'sections', array_values($sections));
|
Chris@18
|
119 }
|
Chris@14
|
120 return $this;
|
Chris@14
|
121 }
|
Chris@14
|
122
|
Chris@14
|
123 /**
|
Chris@14
|
124 * {@inheritdoc}
|
Chris@14
|
125 */
|
Chris@14
|
126 public function preSave(EntityStorageInterface $storage) {
|
Chris@14
|
127 parent::preSave($storage);
|
Chris@14
|
128
|
Chris@14
|
129 $original_value = isset($this->original) ? $this->original->isOverridable() : FALSE;
|
Chris@14
|
130 $new_value = $this->isOverridable();
|
Chris@14
|
131 if ($original_value !== $new_value) {
|
Chris@14
|
132 $entity_type_id = $this->getTargetEntityTypeId();
|
Chris@14
|
133 $bundle = $this->getTargetBundle();
|
Chris@14
|
134
|
Chris@14
|
135 if ($new_value) {
|
Chris@17
|
136 $this->addSectionField($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME);
|
Chris@14
|
137 }
|
Chris@17
|
138 else {
|
Chris@17
|
139 $this->removeSectionField($entity_type_id, $bundle, OverridesSectionStorage::FIELD_NAME);
|
Chris@14
|
140 }
|
Chris@14
|
141 }
|
Chris@17
|
142
|
Chris@17
|
143 $already_enabled = isset($this->original) ? $this->original->isLayoutBuilderEnabled() : FALSE;
|
Chris@17
|
144 $set_enabled = $this->isLayoutBuilderEnabled();
|
Chris@17
|
145 if ($already_enabled !== $set_enabled) {
|
Chris@17
|
146 if ($set_enabled) {
|
Chris@17
|
147 // Loop through all existing field-based components and add them as
|
Chris@17
|
148 // section-based components.
|
Chris@17
|
149 $components = $this->getComponents();
|
Chris@17
|
150 // Sort the components by weight.
|
Chris@17
|
151 uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
|
Chris@17
|
152 foreach ($components as $name => $component) {
|
Chris@17
|
153 $this->setComponent($name, $component);
|
Chris@17
|
154 }
|
Chris@17
|
155 }
|
Chris@17
|
156 else {
|
Chris@17
|
157 // When being disabled, remove all existing section data.
|
Chris@18
|
158 $this->removeAllSections();
|
Chris@17
|
159 }
|
Chris@17
|
160 }
|
Chris@17
|
161 }
|
Chris@17
|
162
|
Chris@17
|
163 /**
|
Chris@17
|
164 * Removes a layout section field if it is no longer needed.
|
Chris@17
|
165 *
|
Chris@17
|
166 * Because the field is shared across all view modes, the field will only be
|
Chris@17
|
167 * removed if no other view modes are using it.
|
Chris@17
|
168 *
|
Chris@17
|
169 * @param string $entity_type_id
|
Chris@17
|
170 * The entity type ID.
|
Chris@17
|
171 * @param string $bundle
|
Chris@17
|
172 * The bundle.
|
Chris@17
|
173 * @param string $field_name
|
Chris@17
|
174 * The name for the layout section field.
|
Chris@17
|
175 */
|
Chris@17
|
176 protected function removeSectionField($entity_type_id, $bundle, $field_name) {
|
Chris@17
|
177 $query = $this->entityTypeManager()->getStorage($this->getEntityTypeId())->getQuery()
|
Chris@17
|
178 ->condition('targetEntityType', $this->getTargetEntityTypeId())
|
Chris@17
|
179 ->condition('bundle', $this->getTargetBundle())
|
Chris@17
|
180 ->condition('mode', $this->getMode(), '<>')
|
Chris@17
|
181 ->condition('third_party_settings.layout_builder.allow_custom', TRUE);
|
Chris@17
|
182 $enabled = (bool) $query->count()->execute();
|
Chris@17
|
183 if (!$enabled && $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name)) {
|
Chris@17
|
184 $field->delete();
|
Chris@17
|
185 }
|
Chris@14
|
186 }
|
Chris@14
|
187
|
Chris@14
|
188 /**
|
Chris@14
|
189 * Adds a layout section field to a given bundle.
|
Chris@14
|
190 *
|
Chris@14
|
191 * @param string $entity_type_id
|
Chris@14
|
192 * The entity type ID.
|
Chris@14
|
193 * @param string $bundle
|
Chris@14
|
194 * The bundle.
|
Chris@14
|
195 * @param string $field_name
|
Chris@14
|
196 * The name for the layout section field.
|
Chris@14
|
197 */
|
Chris@14
|
198 protected function addSectionField($entity_type_id, $bundle, $field_name) {
|
Chris@14
|
199 $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
|
Chris@14
|
200 if (!$field) {
|
Chris@14
|
201 $field_storage = FieldStorageConfig::loadByName($entity_type_id, $field_name);
|
Chris@14
|
202 if (!$field_storage) {
|
Chris@14
|
203 $field_storage = FieldStorageConfig::create([
|
Chris@14
|
204 'entity_type' => $entity_type_id,
|
Chris@14
|
205 'field_name' => $field_name,
|
Chris@14
|
206 'type' => 'layout_section',
|
Chris@14
|
207 'locked' => TRUE,
|
Chris@14
|
208 ]);
|
Chris@18
|
209 $field_storage->setTranslatable(FALSE);
|
Chris@14
|
210 $field_storage->save();
|
Chris@14
|
211 }
|
Chris@14
|
212
|
Chris@14
|
213 $field = FieldConfig::create([
|
Chris@14
|
214 'field_storage' => $field_storage,
|
Chris@14
|
215 'bundle' => $bundle,
|
Chris@14
|
216 'label' => t('Layout'),
|
Chris@14
|
217 ]);
|
Chris@18
|
218 $field->setTranslatable(FALSE);
|
Chris@14
|
219 $field->save();
|
Chris@14
|
220 }
|
Chris@14
|
221 }
|
Chris@14
|
222
|
Chris@14
|
223 /**
|
Chris@14
|
224 * {@inheritdoc}
|
Chris@14
|
225 */
|
Chris@17
|
226 public function createCopy($mode) {
|
Chris@17
|
227 // Disable Layout Builder and remove any sections copied from the original.
|
Chris@17
|
228 return parent::createCopy($mode)
|
Chris@17
|
229 ->setSections([])
|
Chris@17
|
230 ->disableLayoutBuilder();
|
Chris@17
|
231 }
|
Chris@17
|
232
|
Chris@17
|
233 /**
|
Chris@17
|
234 * {@inheritdoc}
|
Chris@17
|
235 */
|
Chris@14
|
236 protected function getDefaultRegion() {
|
Chris@14
|
237 if ($this->hasSection(0)) {
|
Chris@14
|
238 return $this->getSection(0)->getDefaultRegion();
|
Chris@14
|
239 }
|
Chris@14
|
240
|
Chris@14
|
241 return parent::getDefaultRegion();
|
Chris@14
|
242 }
|
Chris@14
|
243
|
Chris@14
|
244 /**
|
Chris@14
|
245 * Wraps the context repository service.
|
Chris@14
|
246 *
|
Chris@14
|
247 * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
|
Chris@14
|
248 * The context repository service.
|
Chris@14
|
249 */
|
Chris@14
|
250 protected function contextRepository() {
|
Chris@14
|
251 return \Drupal::service('context.repository');
|
Chris@14
|
252 }
|
Chris@14
|
253
|
Chris@14
|
254 /**
|
Chris@14
|
255 * {@inheritdoc}
|
Chris@14
|
256 */
|
Chris@14
|
257 public function buildMultiple(array $entities) {
|
Chris@14
|
258 $build_list = parent::buildMultiple($entities);
|
Chris@14
|
259
|
Chris@14
|
260 foreach ($entities as $id => $entity) {
|
Chris@18
|
261 $build_list[$id]['_layout_builder'] = $this->buildSections($entity);
|
Chris@18
|
262
|
Chris@18
|
263 // If there are any sections, remove all fields with configurable display
|
Chris@18
|
264 // from the existing build. These fields are replicated within sections as
|
Chris@18
|
265 // field blocks by ::setComponent().
|
Chris@18
|
266 if (!Element::isEmpty($build_list[$id]['_layout_builder'])) {
|
Chris@14
|
267 foreach ($build_list[$id] as $name => $build_part) {
|
Chris@14
|
268 $field_definition = $this->getFieldDefinition($name);
|
Chris@14
|
269 if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) {
|
Chris@14
|
270 unset($build_list[$id][$name]);
|
Chris@14
|
271 }
|
Chris@14
|
272 }
|
Chris@14
|
273 }
|
Chris@14
|
274 }
|
Chris@14
|
275
|
Chris@14
|
276 return $build_list;
|
Chris@14
|
277 }
|
Chris@14
|
278
|
Chris@14
|
279 /**
|
Chris@18
|
280 * Builds the render array for the sections of a given entity.
|
Chris@18
|
281 *
|
Chris@18
|
282 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
Chris@18
|
283 * The entity.
|
Chris@18
|
284 *
|
Chris@18
|
285 * @return array
|
Chris@18
|
286 * The render array representing the sections of the entity.
|
Chris@18
|
287 */
|
Chris@18
|
288 protected function buildSections(FieldableEntityInterface $entity) {
|
Chris@18
|
289 $contexts = $this->getContextsForEntity($entity);
|
Chris@18
|
290 // @todo Remove in https://www.drupal.org/project/drupal/issues/3018782.
|
Chris@18
|
291 $label = new TranslatableMarkup('@entity being viewed', [
|
Chris@18
|
292 '@entity' => $entity->getEntityType()->getSingularLabel(),
|
Chris@18
|
293 ]);
|
Chris@18
|
294 $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label);
|
Chris@18
|
295
|
Chris@18
|
296 $cacheability = new CacheableMetadata();
|
Chris@18
|
297 $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
|
Chris@18
|
298
|
Chris@18
|
299 $build = [];
|
Chris@18
|
300 if ($storage) {
|
Chris@18
|
301 foreach ($storage->getSections() as $delta => $section) {
|
Chris@18
|
302 $build[$delta] = $section->toRenderArray($contexts);
|
Chris@18
|
303 }
|
Chris@18
|
304 }
|
Chris@18
|
305 // The render array is built based on decisions made by @SectionStorage
|
Chris@18
|
306 // plugins and therefore it needs to depend on the accumulated
|
Chris@18
|
307 // cacheability of those decisions.
|
Chris@18
|
308 $cacheability->applyTo($build);
|
Chris@18
|
309 return $build;
|
Chris@18
|
310 }
|
Chris@18
|
311
|
Chris@18
|
312 /**
|
Chris@18
|
313 * Gets the available contexts for a given entity.
|
Chris@18
|
314 *
|
Chris@18
|
315 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
Chris@18
|
316 * The entity.
|
Chris@18
|
317 *
|
Chris@18
|
318 * @return \Drupal\Core\Plugin\Context\ContextInterface[]
|
Chris@18
|
319 * An array of context objects for a given entity.
|
Chris@18
|
320 */
|
Chris@18
|
321 protected function getContextsForEntity(FieldableEntityInterface $entity) {
|
Chris@18
|
322 return [
|
Chris@18
|
323 'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()),
|
Chris@18
|
324 'entity' => EntityContext::fromEntity($entity),
|
Chris@18
|
325 'display' => EntityContext::fromEntity($this),
|
Chris@18
|
326 ] + $this->contextRepository()->getAvailableContexts();
|
Chris@18
|
327 }
|
Chris@18
|
328
|
Chris@18
|
329 /**
|
Chris@14
|
330 * Gets the runtime sections for a given entity.
|
Chris@14
|
331 *
|
Chris@14
|
332 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
Chris@14
|
333 * The entity.
|
Chris@14
|
334 *
|
Chris@14
|
335 * @return \Drupal\layout_builder\Section[]
|
Chris@14
|
336 * The sections.
|
Chris@18
|
337 *
|
Chris@18
|
338 * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0.
|
Chris@18
|
339 * \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext()
|
Chris@18
|
340 * should be used instead. See https://www.drupal.org/node/3022574.
|
Chris@14
|
341 */
|
Chris@14
|
342 protected function getRuntimeSections(FieldableEntityInterface $entity) {
|
Chris@18
|
343 @trigger_error('\Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::getRuntimeSections() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext() should be used instead. See https://www.drupal.org/node/3022574.', E_USER_DEPRECATED);
|
Chris@18
|
344 // For backwards compatibility, mimic the functionality of ::buildSections()
|
Chris@18
|
345 // by constructing a cacheable metadata object and retrieving the
|
Chris@18
|
346 // entity-based contexts.
|
Chris@18
|
347 $cacheability = new CacheableMetadata();
|
Chris@18
|
348 $contexts = $this->getContextsForEntity($entity);
|
Chris@18
|
349 $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
|
Chris@18
|
350 return $storage ? $storage->getSections() : [];
|
Chris@14
|
351 }
|
Chris@14
|
352
|
Chris@14
|
353 /**
|
Chris@14
|
354 * {@inheritdoc}
|
Chris@14
|
355 *
|
Chris@14
|
356 * @todo Move this upstream in https://www.drupal.org/node/2939931.
|
Chris@14
|
357 */
|
Chris@14
|
358 public function label() {
|
Chris@14
|
359 $bundle_info = \Drupal::service('entity_type.bundle.info')->getBundleInfo($this->getTargetEntityTypeId());
|
Chris@14
|
360 $bundle_label = $bundle_info[$this->getTargetBundle()]['label'];
|
Chris@14
|
361 $target_entity_type = $this->entityTypeManager()->getDefinition($this->getTargetEntityTypeId());
|
Chris@14
|
362 return new TranslatableMarkup('@bundle @label', ['@bundle' => $bundle_label, '@label' => $target_entity_type->getPluralLabel()]);
|
Chris@14
|
363 }
|
Chris@14
|
364
|
Chris@14
|
365 /**
|
Chris@14
|
366 * {@inheritdoc}
|
Chris@14
|
367 */
|
Chris@14
|
368 public function calculateDependencies() {
|
Chris@14
|
369 parent::calculateDependencies();
|
Chris@14
|
370
|
Chris@14
|
371 foreach ($this->getSections() as $delta => $section) {
|
Chris@14
|
372 $this->calculatePluginDependencies($section->getLayout());
|
Chris@14
|
373 foreach ($section->getComponents() as $uuid => $component) {
|
Chris@14
|
374 $this->calculatePluginDependencies($component->getPlugin());
|
Chris@14
|
375 }
|
Chris@14
|
376 }
|
Chris@14
|
377
|
Chris@14
|
378 return $this;
|
Chris@14
|
379 }
|
Chris@14
|
380
|
Chris@14
|
381 /**
|
Chris@14
|
382 * {@inheritdoc}
|
Chris@14
|
383 */
|
Chris@14
|
384 public function onDependencyRemoval(array $dependencies) {
|
Chris@14
|
385 $changed = parent::onDependencyRemoval($dependencies);
|
Chris@14
|
386
|
Chris@14
|
387 // Loop through all sections and determine if the removed dependencies are
|
Chris@14
|
388 // used by their layout plugins.
|
Chris@14
|
389 foreach ($this->getSections() as $delta => $section) {
|
Chris@14
|
390 $layout_dependencies = $this->getPluginDependencies($section->getLayout());
|
Chris@14
|
391 $layout_removed_dependencies = $this->getPluginRemovedDependencies($layout_dependencies, $dependencies);
|
Chris@14
|
392 if ($layout_removed_dependencies) {
|
Chris@14
|
393 // @todo Allow the plugins to react to their dependency removal in
|
Chris@14
|
394 // https://www.drupal.org/project/drupal/issues/2579743.
|
Chris@14
|
395 $this->removeSection($delta);
|
Chris@14
|
396 $changed = TRUE;
|
Chris@14
|
397 }
|
Chris@14
|
398 // If the section is not removed, loop through all components.
|
Chris@14
|
399 else {
|
Chris@14
|
400 foreach ($section->getComponents() as $uuid => $component) {
|
Chris@14
|
401 $plugin_dependencies = $this->getPluginDependencies($component->getPlugin());
|
Chris@14
|
402 $component_removed_dependencies = $this->getPluginRemovedDependencies($plugin_dependencies, $dependencies);
|
Chris@14
|
403 if ($component_removed_dependencies) {
|
Chris@14
|
404 // @todo Allow the plugins to react to their dependency removal in
|
Chris@14
|
405 // https://www.drupal.org/project/drupal/issues/2579743.
|
Chris@14
|
406 $section->removeComponent($uuid);
|
Chris@14
|
407 $changed = TRUE;
|
Chris@14
|
408 }
|
Chris@14
|
409 }
|
Chris@14
|
410 }
|
Chris@14
|
411 }
|
Chris@14
|
412 return $changed;
|
Chris@14
|
413 }
|
Chris@14
|
414
|
Chris@14
|
415 /**
|
Chris@14
|
416 * {@inheritdoc}
|
Chris@14
|
417 */
|
Chris@14
|
418 public function setComponent($name, array $options = []) {
|
Chris@14
|
419 parent::setComponent($name, $options);
|
Chris@14
|
420
|
Chris@17
|
421 // Only continue if Layout Builder is enabled.
|
Chris@17
|
422 if (!$this->isLayoutBuilderEnabled()) {
|
Chris@14
|
423 return $this;
|
Chris@14
|
424 }
|
Chris@14
|
425
|
Chris@14
|
426 // Retrieve the updated options after the parent:: call.
|
Chris@14
|
427 $options = $this->content[$name];
|
Chris@14
|
428 // Provide backwards compatibility by converting to a section component.
|
Chris@14
|
429 $field_definition = $this->getFieldDefinition($name);
|
Chris@17
|
430 $extra_fields = $this->entityFieldManager->getExtraFields($this->getTargetEntityTypeId(), $this->getTargetBundle());
|
Chris@17
|
431 $is_view_configurable_non_extra_field = $field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type']);
|
Chris@17
|
432 if ($is_view_configurable_non_extra_field || isset($extra_fields['display'][$name])) {
|
Chris@17
|
433 $configuration = [
|
Chris@17
|
434 'label_display' => '0',
|
Chris@17
|
435 'context_mapping' => ['entity' => 'layout_builder.entity'],
|
Chris@17
|
436 ];
|
Chris@17
|
437 if ($is_view_configurable_non_extra_field) {
|
Chris@17
|
438 $configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
|
Chris@17
|
439 $keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
|
Chris@17
|
440 $configuration['formatter'] = array_intersect_key($options, $keys);
|
Chris@17
|
441 }
|
Chris@17
|
442 else {
|
Chris@17
|
443 $configuration['id'] = 'extra_field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
|
Chris@17
|
444 }
|
Chris@14
|
445
|
Chris@14
|
446 $section = $this->getDefaultSection();
|
Chris@14
|
447 $region = isset($options['region']) ? $options['region'] : $section->getDefaultRegion();
|
Chris@14
|
448 $new_component = (new SectionComponent(\Drupal::service('uuid')->generate(), $region, $configuration));
|
Chris@14
|
449 $section->appendComponent($new_component);
|
Chris@14
|
450 }
|
Chris@14
|
451 return $this;
|
Chris@14
|
452 }
|
Chris@14
|
453
|
Chris@14
|
454 /**
|
Chris@14
|
455 * Gets a default section.
|
Chris@14
|
456 *
|
Chris@14
|
457 * @return \Drupal\layout_builder\Section
|
Chris@14
|
458 * The default section.
|
Chris@14
|
459 */
|
Chris@14
|
460 protected function getDefaultSection() {
|
Chris@14
|
461 // If no section exists, append a new one.
|
Chris@14
|
462 if (!$this->hasSection(0)) {
|
Chris@14
|
463 $this->appendSection(new Section('layout_onecol'));
|
Chris@14
|
464 }
|
Chris@14
|
465
|
Chris@14
|
466 // Return the first section.
|
Chris@14
|
467 return $this->getSection(0);
|
Chris@14
|
468 }
|
Chris@14
|
469
|
Chris@18
|
470 /**
|
Chris@18
|
471 * Gets the section storage manager.
|
Chris@18
|
472 *
|
Chris@18
|
473 * @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
|
Chris@18
|
474 * The section storage manager.
|
Chris@18
|
475 */
|
Chris@18
|
476 private function sectionStorageManager() {
|
Chris@18
|
477 return \Drupal::service('plugin.manager.layout_builder.section_storage');
|
Chris@18
|
478 }
|
Chris@18
|
479
|
Chris@18
|
480 /**
|
Chris@18
|
481 * {@inheritdoc}
|
Chris@18
|
482 */
|
Chris@18
|
483 public function getComponent($name) {
|
Chris@18
|
484 if ($this->isLayoutBuilderEnabled() && $section_component = $this->getQuickEditSectionComponent() ?: $this->getSectionComponentForFieldName($name)) {
|
Chris@18
|
485 $plugin = $section_component->getPlugin();
|
Chris@18
|
486 if ($plugin instanceof ConfigurableInterface) {
|
Chris@18
|
487 $configuration = $plugin->getConfiguration();
|
Chris@18
|
488 if (isset($configuration['formatter'])) {
|
Chris@18
|
489 return $configuration['formatter'];
|
Chris@18
|
490 }
|
Chris@18
|
491 }
|
Chris@18
|
492 }
|
Chris@18
|
493 return parent::getComponent($name);
|
Chris@18
|
494 }
|
Chris@18
|
495
|
Chris@18
|
496 /**
|
Chris@18
|
497 * Returns the Quick Edit formatter settings.
|
Chris@18
|
498 *
|
Chris@18
|
499 * @return \Drupal\layout_builder\SectionComponent|null
|
Chris@18
|
500 * The section component if it is available.
|
Chris@18
|
501 *
|
Chris@18
|
502 * @see \Drupal\layout_builder\QuickEditIntegration::entityViewAlter()
|
Chris@18
|
503 * @see \Drupal\quickedit\MetadataGenerator::generateFieldMetadata()
|
Chris@18
|
504 */
|
Chris@18
|
505 private function getQuickEditSectionComponent() {
|
Chris@18
|
506 // To determine the Quick Edit view_mode ID we need an originalMode set.
|
Chris@18
|
507 if ($original_mode = $this->getOriginalMode()) {
|
Chris@18
|
508 $parts = explode('-', $original_mode);
|
Chris@18
|
509 // The Quick Edit view mode ID is created by
|
Chris@18
|
510 // \Drupal\layout_builder\QuickEditIntegration::entityViewAlter()
|
Chris@18
|
511 // concatenating together the information we need to retrieve the Layout
|
Chris@18
|
512 // Builder component. It follows the structure prescribed by the
|
Chris@18
|
513 // documentation of hook_quickedit_render_field().
|
Chris@18
|
514 if (count($parts) === 6 && $parts[0] === 'layout_builder') {
|
Chris@18
|
515 list(, $delta, $component_uuid, $entity_id) = QuickEditIntegration::deconstructViewModeId($original_mode);
|
Chris@18
|
516 $entity = $this->entityTypeManager()->getStorage($this->getTargetEntityTypeId())->load($entity_id);
|
Chris@18
|
517 $sections = $this->getEntitySections($entity);
|
Chris@18
|
518 if (isset($sections[$delta])) {
|
Chris@18
|
519 $component = $sections[$delta]->getComponent($component_uuid);
|
Chris@18
|
520 $plugin = $component->getPlugin();
|
Chris@18
|
521 // We only care about FieldBlock because these are only components
|
Chris@18
|
522 // that provide Quick Edit integration: Quick Edit enables in-place
|
Chris@18
|
523 // editing of fields of entities, not of anything else.
|
Chris@18
|
524 if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') {
|
Chris@18
|
525 return $component;
|
Chris@18
|
526 }
|
Chris@18
|
527 }
|
Chris@18
|
528 }
|
Chris@18
|
529 }
|
Chris@18
|
530 return NULL;
|
Chris@18
|
531 }
|
Chris@18
|
532
|
Chris@18
|
533 /**
|
Chris@18
|
534 * Gets the component for a given field name if any.
|
Chris@18
|
535 *
|
Chris@18
|
536 * @param string $field_name
|
Chris@18
|
537 * The field name.
|
Chris@18
|
538 *
|
Chris@18
|
539 * @return \Drupal\layout_builder\SectionComponent|null
|
Chris@18
|
540 * The section component if it is available.
|
Chris@18
|
541 */
|
Chris@18
|
542 private function getSectionComponentForFieldName($field_name) {
|
Chris@18
|
543 // Loop through every component until the first match is found.
|
Chris@18
|
544 foreach ($this->getSections() as $section) {
|
Chris@18
|
545 foreach ($section->getComponents() as $component) {
|
Chris@18
|
546 $plugin = $component->getPlugin();
|
Chris@18
|
547 if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'field_block') {
|
Chris@18
|
548 // FieldBlock derivative IDs are in the format
|
Chris@18
|
549 // [entity_type]:[bundle]:[field].
|
Chris@18
|
550 list(, , $field_block_field_name) = explode(PluginBase::DERIVATIVE_SEPARATOR, $plugin->getDerivativeId());
|
Chris@18
|
551 if ($field_block_field_name === $field_name) {
|
Chris@18
|
552 return $component;
|
Chris@18
|
553 }
|
Chris@18
|
554 }
|
Chris@18
|
555 }
|
Chris@18
|
556 }
|
Chris@18
|
557 return NULL;
|
Chris@18
|
558 }
|
Chris@18
|
559
|
Chris@14
|
560 }
|