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