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