Chris@14
|
1 <?php
|
Chris@14
|
2
|
Chris@14
|
3 namespace Drupal\layout_builder;
|
Chris@14
|
4
|
Chris@18
|
5 use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
|
Chris@18
|
6
|
Chris@14
|
7 /**
|
Chris@14
|
8 * Provides a domain object for layout sections.
|
Chris@14
|
9 *
|
Chris@14
|
10 * A section consists of three parts:
|
Chris@14
|
11 * - The layout plugin ID for the layout applied to the section (for example,
|
Chris@14
|
12 * 'layout_onecol').
|
Chris@14
|
13 * - An array of settings for the layout plugin.
|
Chris@14
|
14 * - An array of components that can be rendered in the section.
|
Chris@14
|
15 *
|
Chris@14
|
16 * @see \Drupal\Core\Layout\LayoutDefinition
|
Chris@14
|
17 * @see \Drupal\layout_builder\SectionComponent
|
Chris@14
|
18 */
|
Chris@18
|
19 class Section implements ThirdPartySettingsInterface {
|
Chris@14
|
20
|
Chris@14
|
21 /**
|
Chris@14
|
22 * The layout plugin ID.
|
Chris@14
|
23 *
|
Chris@14
|
24 * @var string
|
Chris@14
|
25 */
|
Chris@14
|
26 protected $layoutId;
|
Chris@14
|
27
|
Chris@14
|
28 /**
|
Chris@14
|
29 * The layout plugin settings.
|
Chris@14
|
30 *
|
Chris@14
|
31 * @var array
|
Chris@14
|
32 */
|
Chris@14
|
33 protected $layoutSettings = [];
|
Chris@14
|
34
|
Chris@14
|
35 /**
|
Chris@14
|
36 * An array of components, keyed by UUID.
|
Chris@14
|
37 *
|
Chris@14
|
38 * @var \Drupal\layout_builder\SectionComponent[]
|
Chris@14
|
39 */
|
Chris@14
|
40 protected $components = [];
|
Chris@14
|
41
|
Chris@14
|
42 /**
|
Chris@18
|
43 * Third party settings.
|
Chris@18
|
44 *
|
Chris@18
|
45 * An array of key/value pairs keyed by provider.
|
Chris@18
|
46 *
|
Chris@18
|
47 * @var array[]
|
Chris@18
|
48 */
|
Chris@18
|
49 protected $thirdPartySettings = [];
|
Chris@18
|
50
|
Chris@18
|
51 /**
|
Chris@14
|
52 * Constructs a new Section.
|
Chris@14
|
53 *
|
Chris@14
|
54 * @param string $layout_id
|
Chris@14
|
55 * The layout plugin ID.
|
Chris@14
|
56 * @param array $layout_settings
|
Chris@14
|
57 * (optional) The layout plugin settings.
|
Chris@14
|
58 * @param \Drupal\layout_builder\SectionComponent[] $components
|
Chris@14
|
59 * (optional) The components.
|
Chris@18
|
60 * @param array[] $third_party_settings
|
Chris@18
|
61 * (optional) Any third party settings.
|
Chris@14
|
62 */
|
Chris@18
|
63 public function __construct($layout_id, array $layout_settings = [], array $components = [], array $third_party_settings = []) {
|
Chris@14
|
64 $this->layoutId = $layout_id;
|
Chris@14
|
65 $this->layoutSettings = $layout_settings;
|
Chris@14
|
66 foreach ($components as $component) {
|
Chris@14
|
67 $this->setComponent($component);
|
Chris@14
|
68 }
|
Chris@18
|
69 $this->thirdPartySettings = $third_party_settings;
|
Chris@14
|
70 }
|
Chris@14
|
71
|
Chris@14
|
72 /**
|
Chris@14
|
73 * Returns the renderable array for this section.
|
Chris@14
|
74 *
|
Chris@14
|
75 * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
|
Chris@14
|
76 * An array of available contexts.
|
Chris@14
|
77 * @param bool $in_preview
|
Chris@14
|
78 * TRUE if the section is being previewed, FALSE otherwise.
|
Chris@14
|
79 *
|
Chris@14
|
80 * @return array
|
Chris@14
|
81 * A renderable array representing the content of the section.
|
Chris@14
|
82 */
|
Chris@14
|
83 public function toRenderArray(array $contexts = [], $in_preview = FALSE) {
|
Chris@14
|
84 $regions = [];
|
Chris@14
|
85 foreach ($this->getComponents() as $component) {
|
Chris@14
|
86 if ($output = $component->toRenderArray($contexts, $in_preview)) {
|
Chris@14
|
87 $regions[$component->getRegion()][$component->getUuid()] = $output;
|
Chris@14
|
88 }
|
Chris@14
|
89 }
|
Chris@14
|
90
|
Chris@14
|
91 return $this->getLayout()->build($regions);
|
Chris@14
|
92 }
|
Chris@14
|
93
|
Chris@14
|
94 /**
|
Chris@14
|
95 * Gets the layout plugin for this section.
|
Chris@14
|
96 *
|
Chris@14
|
97 * @return \Drupal\Core\Layout\LayoutInterface
|
Chris@14
|
98 * The layout plugin.
|
Chris@14
|
99 */
|
Chris@14
|
100 public function getLayout() {
|
Chris@14
|
101 return $this->layoutPluginManager()->createInstance($this->getLayoutId(), $this->getLayoutSettings());
|
Chris@14
|
102 }
|
Chris@14
|
103
|
Chris@14
|
104 /**
|
Chris@14
|
105 * Gets the layout plugin ID for this section.
|
Chris@14
|
106 *
|
Chris@14
|
107 * @return string
|
Chris@14
|
108 * The layout plugin ID.
|
Chris@14
|
109 *
|
Chris@14
|
110 * @internal
|
Chris@14
|
111 * This method should only be used by code responsible for storing the data.
|
Chris@14
|
112 */
|
Chris@14
|
113 public function getLayoutId() {
|
Chris@14
|
114 return $this->layoutId;
|
Chris@14
|
115 }
|
Chris@14
|
116
|
Chris@14
|
117 /**
|
Chris@14
|
118 * Gets the layout plugin settings for this section.
|
Chris@14
|
119 *
|
Chris@14
|
120 * @return mixed[]
|
Chris@14
|
121 * The layout plugin settings.
|
Chris@14
|
122 *
|
Chris@14
|
123 * @internal
|
Chris@14
|
124 * This method should only be used by code responsible for storing the data.
|
Chris@14
|
125 */
|
Chris@14
|
126 public function getLayoutSettings() {
|
Chris@14
|
127 return $this->layoutSettings;
|
Chris@14
|
128 }
|
Chris@14
|
129
|
Chris@14
|
130 /**
|
Chris@14
|
131 * Sets the layout plugin settings for this section.
|
Chris@14
|
132 *
|
Chris@14
|
133 * @param mixed[] $layout_settings
|
Chris@14
|
134 * The layout plugin settings.
|
Chris@14
|
135 *
|
Chris@14
|
136 * @return $this
|
Chris@14
|
137 */
|
Chris@14
|
138 public function setLayoutSettings(array $layout_settings) {
|
Chris@14
|
139 $this->layoutSettings = $layout_settings;
|
Chris@14
|
140 return $this;
|
Chris@14
|
141 }
|
Chris@14
|
142
|
Chris@14
|
143 /**
|
Chris@14
|
144 * Gets the default region.
|
Chris@14
|
145 *
|
Chris@14
|
146 * @return string
|
Chris@14
|
147 * The machine-readable name of the default region.
|
Chris@14
|
148 */
|
Chris@14
|
149 public function getDefaultRegion() {
|
Chris@14
|
150 return $this->layoutPluginManager()->getDefinition($this->getLayoutId())->getDefaultRegion();
|
Chris@14
|
151 }
|
Chris@14
|
152
|
Chris@14
|
153 /**
|
Chris@14
|
154 * Returns the components of the section.
|
Chris@14
|
155 *
|
Chris@14
|
156 * @return \Drupal\layout_builder\SectionComponent[]
|
Chris@14
|
157 * The components.
|
Chris@14
|
158 */
|
Chris@14
|
159 public function getComponents() {
|
Chris@14
|
160 return $this->components;
|
Chris@14
|
161 }
|
Chris@14
|
162
|
Chris@14
|
163 /**
|
Chris@14
|
164 * Gets the component for a given UUID.
|
Chris@14
|
165 *
|
Chris@14
|
166 * @param string $uuid
|
Chris@14
|
167 * The UUID of the component to retrieve.
|
Chris@14
|
168 *
|
Chris@14
|
169 * @return \Drupal\layout_builder\SectionComponent
|
Chris@14
|
170 * The component.
|
Chris@14
|
171 *
|
Chris@14
|
172 * @throws \InvalidArgumentException
|
Chris@14
|
173 * Thrown when the expected UUID does not exist.
|
Chris@14
|
174 */
|
Chris@14
|
175 public function getComponent($uuid) {
|
Chris@14
|
176 if (!isset($this->components[$uuid])) {
|
Chris@14
|
177 throw new \InvalidArgumentException(sprintf('Invalid UUID "%s"', $uuid));
|
Chris@14
|
178 }
|
Chris@14
|
179
|
Chris@14
|
180 return $this->components[$uuid];
|
Chris@14
|
181 }
|
Chris@14
|
182
|
Chris@14
|
183 /**
|
Chris@14
|
184 * Helper method to set a component.
|
Chris@14
|
185 *
|
Chris@14
|
186 * @param \Drupal\layout_builder\SectionComponent $component
|
Chris@14
|
187 * The component.
|
Chris@14
|
188 *
|
Chris@14
|
189 * @return $this
|
Chris@14
|
190 */
|
Chris@14
|
191 protected function setComponent(SectionComponent $component) {
|
Chris@14
|
192 $this->components[$component->getUuid()] = $component;
|
Chris@14
|
193 return $this;
|
Chris@14
|
194 }
|
Chris@14
|
195
|
Chris@14
|
196 /**
|
Chris@14
|
197 * Removes a given component from a region.
|
Chris@14
|
198 *
|
Chris@14
|
199 * @param string $uuid
|
Chris@14
|
200 * The UUID of the component to remove.
|
Chris@14
|
201 *
|
Chris@14
|
202 * @return $this
|
Chris@14
|
203 */
|
Chris@14
|
204 public function removeComponent($uuid) {
|
Chris@14
|
205 unset($this->components[$uuid]);
|
Chris@14
|
206 return $this;
|
Chris@14
|
207 }
|
Chris@14
|
208
|
Chris@14
|
209 /**
|
Chris@14
|
210 * Appends a component to the end of a region.
|
Chris@14
|
211 *
|
Chris@14
|
212 * @param \Drupal\layout_builder\SectionComponent $component
|
Chris@14
|
213 * The component being appended.
|
Chris@14
|
214 *
|
Chris@14
|
215 * @return $this
|
Chris@14
|
216 */
|
Chris@14
|
217 public function appendComponent(SectionComponent $component) {
|
Chris@14
|
218 $component->setWeight($this->getNextHighestWeight($component->getRegion()));
|
Chris@14
|
219 $this->setComponent($component);
|
Chris@14
|
220 return $this;
|
Chris@14
|
221 }
|
Chris@14
|
222
|
Chris@14
|
223 /**
|
Chris@14
|
224 * Returns the next highest weight of the component in a region.
|
Chris@14
|
225 *
|
Chris@14
|
226 * @param string $region
|
Chris@14
|
227 * The region name.
|
Chris@14
|
228 *
|
Chris@14
|
229 * @return int
|
Chris@14
|
230 * A number higher than the highest weight of the component in the region.
|
Chris@14
|
231 */
|
Chris@14
|
232 protected function getNextHighestWeight($region) {
|
Chris@14
|
233 $components = $this->getComponentsByRegion($region);
|
Chris@14
|
234 $weights = array_map(function (SectionComponent $component) {
|
Chris@14
|
235 return $component->getWeight();
|
Chris@14
|
236 }, $components);
|
Chris@14
|
237 return $weights ? max($weights) + 1 : 0;
|
Chris@14
|
238 }
|
Chris@14
|
239
|
Chris@14
|
240 /**
|
Chris@14
|
241 * Gets the components for a specific region.
|
Chris@14
|
242 *
|
Chris@14
|
243 * @param string $region
|
Chris@14
|
244 * The region name.
|
Chris@14
|
245 *
|
Chris@14
|
246 * @return \Drupal\layout_builder\SectionComponent[]
|
Chris@14
|
247 * An array of components in the specified region, sorted by weight.
|
Chris@14
|
248 */
|
Chris@18
|
249 public function getComponentsByRegion($region) {
|
Chris@14
|
250 $components = array_filter($this->getComponents(), function (SectionComponent $component) use ($region) {
|
Chris@14
|
251 return $component->getRegion() === $region;
|
Chris@14
|
252 });
|
Chris@14
|
253 uasort($components, function (SectionComponent $a, SectionComponent $b) {
|
Chris@14
|
254 return $a->getWeight() > $b->getWeight() ? 1 : -1;
|
Chris@14
|
255 });
|
Chris@14
|
256 return $components;
|
Chris@14
|
257 }
|
Chris@14
|
258
|
Chris@14
|
259 /**
|
Chris@14
|
260 * Inserts a component after a specified existing component.
|
Chris@14
|
261 *
|
Chris@14
|
262 * @param string $preceding_uuid
|
Chris@14
|
263 * The UUID of the existing component to insert after.
|
Chris@14
|
264 * @param \Drupal\layout_builder\SectionComponent $component
|
Chris@14
|
265 * The component being inserted.
|
Chris@14
|
266 *
|
Chris@14
|
267 * @return $this
|
Chris@14
|
268 *
|
Chris@14
|
269 * @throws \InvalidArgumentException
|
Chris@14
|
270 * Thrown when the expected UUID does not exist.
|
Chris@14
|
271 */
|
Chris@14
|
272 public function insertAfterComponent($preceding_uuid, SectionComponent $component) {
|
Chris@14
|
273 // Find the delta of the specified UUID.
|
Chris@14
|
274 $uuids = array_keys($this->getComponentsByRegion($component->getRegion()));
|
Chris@14
|
275 $delta = array_search($preceding_uuid, $uuids, TRUE);
|
Chris@14
|
276 if ($delta === FALSE) {
|
Chris@14
|
277 throw new \InvalidArgumentException(sprintf('Invalid preceding UUID "%s"', $preceding_uuid));
|
Chris@14
|
278 }
|
Chris@14
|
279 return $this->insertComponent($delta + 1, $component);
|
Chris@14
|
280 }
|
Chris@14
|
281
|
Chris@14
|
282 /**
|
Chris@14
|
283 * Inserts a component at a specified delta.
|
Chris@14
|
284 *
|
Chris@14
|
285 * @param int $delta
|
Chris@14
|
286 * The zero-based delta in which to insert the component.
|
Chris@14
|
287 * @param \Drupal\layout_builder\SectionComponent $new_component
|
Chris@14
|
288 * The component being inserted.
|
Chris@14
|
289 *
|
Chris@14
|
290 * @return $this
|
Chris@14
|
291 *
|
Chris@14
|
292 * @throws \OutOfBoundsException
|
Chris@14
|
293 * Thrown when the specified delta is invalid.
|
Chris@14
|
294 */
|
Chris@14
|
295 public function insertComponent($delta, SectionComponent $new_component) {
|
Chris@14
|
296 $components = $this->getComponentsByRegion($new_component->getRegion());
|
Chris@14
|
297 $count = count($components);
|
Chris@14
|
298 if ($delta > $count) {
|
Chris@14
|
299 throw new \OutOfBoundsException(sprintf('Invalid delta "%s" for the "%s" component', $delta, $new_component->getUuid()));
|
Chris@14
|
300 }
|
Chris@14
|
301
|
Chris@14
|
302 // If the delta is the end of the list, append the component instead.
|
Chris@14
|
303 if ($delta === $count) {
|
Chris@14
|
304 return $this->appendComponent($new_component);
|
Chris@14
|
305 }
|
Chris@14
|
306
|
Chris@14
|
307 // Find the weight of the component that exists at the specified delta.
|
Chris@14
|
308 $weight = array_values($components)[$delta]->getWeight();
|
Chris@14
|
309 $this->setComponent($new_component->setWeight($weight++));
|
Chris@14
|
310
|
Chris@14
|
311 // Increase the weight of every subsequent component.
|
Chris@14
|
312 foreach (array_slice($components, $delta) as $component) {
|
Chris@14
|
313 $component->setWeight($weight++);
|
Chris@14
|
314 }
|
Chris@14
|
315 return $this;
|
Chris@14
|
316 }
|
Chris@14
|
317
|
Chris@14
|
318 /**
|
Chris@14
|
319 * Wraps the layout plugin manager.
|
Chris@14
|
320 *
|
Chris@14
|
321 * @return \Drupal\Core\Layout\LayoutPluginManagerInterface
|
Chris@14
|
322 * The layout plugin manager.
|
Chris@14
|
323 */
|
Chris@14
|
324 protected function layoutPluginManager() {
|
Chris@14
|
325 return \Drupal::service('plugin.manager.core.layout');
|
Chris@14
|
326 }
|
Chris@14
|
327
|
Chris@14
|
328 /**
|
Chris@14
|
329 * Returns an array representation of the section.
|
Chris@14
|
330 *
|
Chris@16
|
331 * Only use this method if you are implementing custom storage for sections.
|
Chris@14
|
332 *
|
Chris@14
|
333 * @return array
|
Chris@14
|
334 * An array representation of the section component.
|
Chris@14
|
335 */
|
Chris@14
|
336 public function toArray() {
|
Chris@14
|
337 return [
|
Chris@14
|
338 'layout_id' => $this->getLayoutId(),
|
Chris@14
|
339 'layout_settings' => $this->getLayoutSettings(),
|
Chris@14
|
340 'components' => array_map(function (SectionComponent $component) {
|
Chris@14
|
341 return $component->toArray();
|
Chris@14
|
342 }, $this->getComponents()),
|
Chris@18
|
343 'third_party_settings' => $this->thirdPartySettings,
|
Chris@14
|
344 ];
|
Chris@14
|
345 }
|
Chris@14
|
346
|
Chris@16
|
347 /**
|
Chris@16
|
348 * Creates an object from an array representation of the section.
|
Chris@16
|
349 *
|
Chris@16
|
350 * Only use this method if you are implementing custom storage for sections.
|
Chris@16
|
351 *
|
Chris@16
|
352 * @param array $section
|
Chris@16
|
353 * An array of section data in the format returned by ::toArray().
|
Chris@16
|
354 *
|
Chris@16
|
355 * @return static
|
Chris@16
|
356 * The section object.
|
Chris@16
|
357 */
|
Chris@16
|
358 public static function fromArray(array $section) {
|
Chris@18
|
359 // Ensure expected array keys are present.
|
Chris@18
|
360 $section += [
|
Chris@18
|
361 'layout_id' => '',
|
Chris@18
|
362 'layout_settings' => [],
|
Chris@18
|
363 'components' => [],
|
Chris@18
|
364 'third_party_settings' => [],
|
Chris@18
|
365 ];
|
Chris@16
|
366 return new static(
|
Chris@16
|
367 $section['layout_id'],
|
Chris@16
|
368 $section['layout_settings'],
|
Chris@18
|
369 array_map([SectionComponent::class, 'fromArray'], $section['components']),
|
Chris@18
|
370 $section['third_party_settings']
|
Chris@16
|
371 );
|
Chris@16
|
372 }
|
Chris@16
|
373
|
Chris@17
|
374 /**
|
Chris@17
|
375 * Magic method: Implements a deep clone.
|
Chris@17
|
376 */
|
Chris@17
|
377 public function __clone() {
|
Chris@17
|
378 foreach ($this->components as $uuid => $component) {
|
Chris@17
|
379 $this->components[$uuid] = clone $component;
|
Chris@17
|
380 }
|
Chris@17
|
381 }
|
Chris@17
|
382
|
Chris@18
|
383 /**
|
Chris@18
|
384 * {@inheritdoc}
|
Chris@18
|
385 */
|
Chris@18
|
386 public function getThirdPartySetting($provider, $key, $default = NULL) {
|
Chris@18
|
387 return isset($this->thirdPartySettings[$provider][$key]) ? $this->thirdPartySettings[$provider][$key] : $default;
|
Chris@18
|
388 }
|
Chris@18
|
389
|
Chris@18
|
390 /**
|
Chris@18
|
391 * {@inheritdoc}
|
Chris@18
|
392 */
|
Chris@18
|
393 public function getThirdPartySettings($provider) {
|
Chris@18
|
394 return isset($this->thirdPartySettings[$provider]) ? $this->thirdPartySettings[$provider] : [];
|
Chris@18
|
395 }
|
Chris@18
|
396
|
Chris@18
|
397 /**
|
Chris@18
|
398 * {@inheritdoc}
|
Chris@18
|
399 */
|
Chris@18
|
400 public function setThirdPartySetting($provider, $key, $value) {
|
Chris@18
|
401 $this->thirdPartySettings[$provider][$key] = $value;
|
Chris@18
|
402 return $this;
|
Chris@18
|
403 }
|
Chris@18
|
404
|
Chris@18
|
405 /**
|
Chris@18
|
406 * {@inheritdoc}
|
Chris@18
|
407 */
|
Chris@18
|
408 public function unsetThirdPartySetting($provider, $key) {
|
Chris@18
|
409 unset($this->thirdPartySettings[$provider][$key]);
|
Chris@18
|
410 // If the third party is no longer storing any information, completely
|
Chris@18
|
411 // remove the array holding the settings for this provider.
|
Chris@18
|
412 if (empty($this->thirdPartySettings[$provider])) {
|
Chris@18
|
413 unset($this->thirdPartySettings[$provider]);
|
Chris@18
|
414 }
|
Chris@18
|
415 return $this;
|
Chris@18
|
416 }
|
Chris@18
|
417
|
Chris@18
|
418 /**
|
Chris@18
|
419 * {@inheritdoc}
|
Chris@18
|
420 */
|
Chris@18
|
421 public function getThirdPartyProviders() {
|
Chris@18
|
422 return array_keys($this->thirdPartySettings);
|
Chris@18
|
423 }
|
Chris@18
|
424
|
Chris@14
|
425 }
|