Mercurial > hg > isophonics-drupal-site
comparison core/modules/layout_builder/src/QuickEditIntegration.php @ 18:af1871eacc83
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:33:08 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
17:129ea1e6d783 | 18:af1871eacc83 |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\layout_builder; | |
4 | |
5 use Drupal\Component\Utility\NestedArray; | |
6 use Drupal\Core\Cache\CacheableMetadata; | |
7 use Drupal\Core\DependencyInjection\ContainerInjectionInterface; | |
8 use Drupal\Core\Entity\Display\EntityViewDisplayInterface; | |
9 use Drupal\Core\Entity\EntityInterface; | |
10 use Drupal\Core\Entity\EntityTypeManagerInterface; | |
11 use Drupal\Core\Entity\FieldableEntityInterface; | |
12 use Drupal\Core\Logger\LoggerChannelTrait; | |
13 use Drupal\Core\Plugin\Context\Context; | |
14 use Drupal\Core\Plugin\Context\ContextDefinition; | |
15 use Drupal\Core\Plugin\Context\EntityContext; | |
16 use Drupal\Core\Render\Element; | |
17 use Drupal\Core\Session\AccountInterface; | |
18 use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface; | |
19 use Symfony\Component\DependencyInjection\ContainerInterface; | |
20 | |
21 /** | |
22 * Helper methods for Quick Edit module integration. | |
23 * | |
24 * @internal | |
25 * This is an internal utility class wrapping hook implementations. | |
26 */ | |
27 class QuickEditIntegration implements ContainerInjectionInterface { | |
28 | |
29 use LoggerChannelTrait; | |
30 | |
31 /** | |
32 * The section storage manager. | |
33 * | |
34 * @var \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface | |
35 */ | |
36 protected $sectionStorageManager; | |
37 | |
38 /** | |
39 * The current user. | |
40 * | |
41 * @var \Drupal\Core\Session\AccountInterface | |
42 */ | |
43 protected $currentUser; | |
44 | |
45 /** | |
46 * The entity type manager. | |
47 * | |
48 * @var \Drupal\Core\Entity\EntityTypeManagerInterface | |
49 */ | |
50 protected $entityTypeManager; | |
51 | |
52 /** | |
53 * Constructs a new QuickEditIntegration object. | |
54 * | |
55 * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager | |
56 * The section storage manager. | |
57 * @param \Drupal\Core\Session\AccountInterface $current_user | |
58 * The current user. | |
59 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager | |
60 * The entity type manager. | |
61 */ | |
62 public function __construct(SectionStorageManagerInterface $section_storage_manager, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) { | |
63 $this->sectionStorageManager = $section_storage_manager; | |
64 $this->currentUser = $current_user; | |
65 $this->entityTypeManager = $entity_type_manager; | |
66 } | |
67 | |
68 /** | |
69 * {@inheritdoc} | |
70 */ | |
71 public static function create(ContainerInterface $container) { | |
72 return new static( | |
73 $container->get('plugin.manager.layout_builder.section_storage'), | |
74 $container->get('current_user'), | |
75 $container->get('entity_type.manager') | |
76 ); | |
77 } | |
78 | |
79 /** | |
80 * Alters the entity view build for Quick Edit compatibility. | |
81 * | |
82 * When rendering fields outside of normal view modes, Quick Edit requires | |
83 * that modules identify themselves with a view mode ID in the format | |
84 * [module_name]-[information the module needs to rerender], as prescribed by | |
85 * hook_quickedit_render_field(). | |
86 * | |
87 * @param array $build | |
88 * The built entity render array. | |
89 * @param \Drupal\Core\Entity\EntityInterface $entity | |
90 * The entity. | |
91 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display | |
92 * The entity view display. | |
93 * | |
94 * @see hook_quickedit_render_field() | |
95 * @see layout_builder_quickedit_render_field() | |
96 */ | |
97 public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) { | |
98 if (!$entity instanceof FieldableEntityInterface || !isset($build['_layout_builder'])) { | |
99 return; | |
100 } | |
101 | |
102 $build['#cache']['contexts'][] = 'user.permissions'; | |
103 if (!$this->currentUser->hasPermission('access in-place editing')) { | |
104 return; | |
105 } | |
106 | |
107 $cacheable_metadata = CacheableMetadata::createFromRenderArray($build); | |
108 $section_list = $this->sectionStorageManager->findByContext( | |
109 [ | |
110 'display' => EntityContext::fromEntity($display), | |
111 'entity' => EntityContext::fromEntity($entity), | |
112 'view_mode' => new Context(new ContextDefinition('string'), $display->getMode()), | |
113 ], | |
114 $cacheable_metadata | |
115 ); | |
116 $cacheable_metadata->applyTo($build); | |
117 | |
118 if (empty($section_list)) { | |
119 return; | |
120 } | |
121 | |
122 // Create a hash of the sections and use it in the unique Quick Edit view | |
123 // mode ID. Any changes to the sections will result in a different hash, | |
124 // forcing Quick Edit's JavaScript to recognize any changes and retrieve | |
125 // up-to-date metadata. | |
126 $sections_hash = hash('sha256', serialize($section_list->getSections())); | |
127 | |
128 // Track each component by their plugin ID, delta, region, and UUID. | |
129 $plugin_ids_to_update = []; | |
130 foreach (Element::children($build['_layout_builder']) as $delta) { | |
131 $section = $build['_layout_builder'][$delta]; | |
132 /** @var \Drupal\Core\Layout\LayoutDefinition $layout */ | |
133 $layout = $section['#layout']; | |
134 $regions = $layout->getRegionNames(); | |
135 | |
136 foreach ($regions as $region) { | |
137 if (isset($section[$region])) { | |
138 foreach ($section[$region] as $uuid => $component) { | |
139 if (isset($component['#plugin_id']) && $this->supportQuickEditOnComponent($component, $entity)) { | |
140 $plugin_ids_to_update[$component['#plugin_id']][$delta][$region][$uuid] = $uuid; | |
141 } | |
142 } | |
143 } | |
144 } | |
145 } | |
146 | |
147 // @todo Remove when https://www.drupal.org/node/3041850 is resolved. | |
148 $plugin_ids_to_update = array_filter($plugin_ids_to_update, function ($info) { | |
149 // Delta, region, and UUID each count as one. | |
150 return count($info, COUNT_RECURSIVE) === 3; | |
151 }); | |
152 | |
153 $plugin_ids_to_update = NestedArray::mergeDeepArray($plugin_ids_to_update, TRUE); | |
154 foreach ($plugin_ids_to_update as $delta => $regions) { | |
155 foreach ($regions as $region => $uuids) { | |
156 foreach ($uuids as $uuid => $component) { | |
157 $build['_layout_builder'][$delta][$region][$uuid]['content']['#view_mode'] = static::getViewModeId($entity, $display, $delta, $uuid, $sections_hash); | |
158 } | |
159 } | |
160 } | |
161 // Alter the Quick Edit view mode ID of all fields outside of the Layout | |
162 // Builder sections to force Quick Edit to request to the field metadata. | |
163 // @todo Remove this logic in https://www.drupal.org/project/node/2966136. | |
164 foreach (Element::children($build) as $field_name) { | |
165 if ($field_name !== '_layout_builder') { | |
166 $field_build = &$build[$field_name]; | |
167 if (isset($field_build['#view_mode'])) { | |
168 $field_build['#view_mode'] = "layout_builder-{$display->getMode()}-non_component-$sections_hash"; | |
169 } | |
170 } | |
171 } | |
172 } | |
173 | |
174 /** | |
175 * Generates a Quick Edit view mode ID. | |
176 * | |
177 * @param \Drupal\Core\Entity\EntityInterface $entity | |
178 * The entity. | |
179 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display | |
180 * The entity view display. | |
181 * @param int $delta | |
182 * The delta. | |
183 * @param string $component_uuid | |
184 * The component UUID. | |
185 * @param string $sections_hash | |
186 * The hash of the sections; must change whenever the sections change. | |
187 * | |
188 * @return string | |
189 * The Quick Edit view mode ID. | |
190 * | |
191 * @see \Drupal\layout_builder\QuickEditIntegration::deconstructViewModeId() | |
192 */ | |
193 private static function getViewModeId(EntityInterface $entity, EntityViewDisplayInterface $display, $delta, $component_uuid, $sections_hash) { | |
194 return implode('-', [ | |
195 'layout_builder', | |
196 $display->getMode(), | |
197 $delta, | |
198 // Replace the dashes in the component UUID because we need to | |
199 // use dashes to join the parts. | |
200 str_replace('-', '_', $component_uuid), | |
201 $entity->id(), | |
202 $sections_hash, | |
203 ]); | |
204 } | |
205 | |
206 /** | |
207 * Deconstructs the Quick Edit view mode ID into its constituent parts. | |
208 * | |
209 * @param string $quick_edit_view_mode_id | |
210 * The Quick Edit view mode ID. | |
211 * | |
212 * @return array | |
213 * An array containing the entity view mode ID, the delta, the component | |
214 * UUID, and the entity ID. | |
215 * | |
216 * @see \Drupal\layout_builder\QuickEditIntegration::getViewModeId() | |
217 */ | |
218 public static function deconstructViewModeId($quick_edit_view_mode_id) { | |
219 list(, $entity_view_mode_id, $delta, $component_uuid, $entity_id) = explode('-', $quick_edit_view_mode_id, 7); | |
220 return [ | |
221 $entity_view_mode_id, | |
222 // @todo Explicitly cast delta to an integer, remove this in | |
223 // https://www.drupal.org/project/drupal/issues/2984509. | |
224 (int) $delta, | |
225 // Replace the underscores with dash to get back the component UUID. | |
226 str_replace('_', '-', $component_uuid), | |
227 $entity_id, | |
228 ]; | |
229 } | |
230 | |
231 /** | |
232 * Re-renders a field rendered by Layout Builder, edited with Quick Edit. | |
233 * | |
234 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity | |
235 * The entity. | |
236 * @param string $field_name | |
237 * The field name. | |
238 * @param string $quick_edit_view_mode_id | |
239 * The Quick Edit view mode ID. | |
240 * @param string $langcode | |
241 * The language code. | |
242 * | |
243 * @return array | |
244 * The re-rendered field. | |
245 */ | |
246 public function quickEditRenderField(FieldableEntityInterface $entity, $field_name, $quick_edit_view_mode_id, $langcode) { | |
247 list($entity_view_mode, $delta, $component_uuid) = static::deconstructViewModeId($quick_edit_view_mode_id); | |
248 | |
249 $entity_build = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->view($entity, $entity_view_mode, $langcode); | |
250 $this->buildEntityView($entity_build); | |
251 | |
252 if (isset($entity_build['_layout_builder'][$delta])) { | |
253 foreach (Element::children($entity_build['_layout_builder'][$delta]) as $region) { | |
254 if (isset($entity_build['_layout_builder'][$delta][$region][$component_uuid])) { | |
255 return $entity_build['_layout_builder'][$delta][$region][$component_uuid]['content']; | |
256 } | |
257 } | |
258 } | |
259 | |
260 $this->getLogger('layout_builder')->warning('The field "%field" failed to render.', ['%field' => $field_name]); | |
261 return []; | |
262 } | |
263 | |
264 /** | |
265 * {@inheritdoc} | |
266 * | |
267 * @todo Replace this hardcoded processing when | |
268 * https://www.drupal.org/project/drupal/issues/3041635 is resolved. | |
269 * | |
270 * @see \Drupal\Tests\EntityViewTrait::buildEntityView() | |
271 */ | |
272 private function buildEntityView(array &$elements) { | |
273 // If the default values for this element have not been loaded yet, | |
274 // populate them. | |
275 if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) { | |
276 $elements += \Drupal::service('element_info')->getInfo($elements['#type']); | |
277 } | |
278 | |
279 // Make any final changes to the element before it is rendered. This means | |
280 // that the $element or the children can be altered or corrected before | |
281 // the element is rendered into the final text. | |
282 if (isset($elements['#pre_render'])) { | |
283 foreach ($elements['#pre_render'] as $callable) { | |
284 $elements = call_user_func($callable, $elements); | |
285 } | |
286 } | |
287 | |
288 // And recurse. | |
289 $children = Element::children($elements, TRUE); | |
290 foreach ($children as $key) { | |
291 $this->buildEntityView($elements[$key]); | |
292 } | |
293 } | |
294 | |
295 /** | |
296 * Determines whether a component has Quick Edit support. | |
297 * | |
298 * Only field_block components for display configurable fields should be | |
299 * supported. | |
300 * | |
301 * @param array $component | |
302 * The component render array. | |
303 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity | |
304 * The entity being displayed. | |
305 * | |
306 * @return bool | |
307 * Whether Quick Edit is supported on the component. | |
308 * | |
309 * @see \Drupal\layout_builder\Plugin\Block\FieldBlock | |
310 */ | |
311 private function supportQuickEditOnComponent(array $component, FieldableEntityInterface $entity) { | |
312 if (isset($component['content']['#field_name'], $component['#base_plugin_id']) && $component['#base_plugin_id'] === 'field_block' && $entity->hasField($component['content']['#field_name'])) { | |
313 return $entity->getFieldDefinition($component['content']['#field_name'])->isDisplayConfigurable('view'); | |
314 } | |
315 return FALSE; | |
316 } | |
317 | |
318 } |