Section.php 8.41 KB
Newer Older
1 2 3 4 5 6 7
<?php

namespace Drupal\layout_builder;

/**
 * Provides a domain object for layout sections.
 *
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * A section consists of three parts:
 * - The layout plugin ID for the layout applied to the section (for example,
 *   'layout_onecol').
 * - An array of settings for the layout plugin.
 * - An array of components that can be rendered in the section.
 *
 * @internal
 *   Layout Builder is currently experimental and should only be leveraged by
 *   experimental modules and development releases of contributed modules.
 *   See https://www.drupal.org/core/experimental for more information.
 *
 * @see \Drupal\Core\Layout\LayoutDefinition
 * @see \Drupal\layout_builder\SectionComponent
 *
 * @todo Determine whether an interface will be provided for this in
 *   https://www.drupal.org/project/drupal/issues/2930334.
24 25 26 27
 */
class Section {

  /**
28 29 30 31 32 33 34 35
   * The layout plugin ID.
   *
   * @var string
   */
  protected $layoutId;

  /**
   * The layout plugin settings.
36 37 38
   *
   * @var array
   */
39 40 41 42 43 44 45 46
  protected $layoutSettings = [];

  /**
   * An array of components, keyed by UUID.
   *
   * @var \Drupal\layout_builder\SectionComponent[]
   */
  protected $components = [];
47 48 49 50

  /**
   * Constructs a new Section.
   *
51 52 53 54 55 56
   * @param string $layout_id
   *   The layout plugin ID.
   * @param array $layout_settings
   *   (optional) The layout plugin settings.
   * @param \Drupal\layout_builder\SectionComponent[] $components
   *   (optional) The components.
57
   */
58 59 60 61 62 63
  public function __construct($layout_id, array $layout_settings = [], array $components = []) {
    $this->layoutId = $layout_id;
    $this->layoutSettings = $layout_settings;
    foreach ($components as $component) {
      $this->setComponent($component);
    }
64 65 66
  }

  /**
67
   * Returns the renderable array for this section.
68 69
   *
   * @return array
70
   *   A renderable array representing the content of the section.
71
   */
72
  public function toRenderArray() {
73 74
    $regions = [];
    foreach ($this->getComponents() as $component) {
75
      if ($output = $component->toRenderArray()) {
76 77 78 79 80
        $regions[$component->getRegion()][$component->getUuid()] = $output;
      }
    }

    return $this->getLayout()->build($regions);
81 82 83
  }

  /**
84
   * Gets the layout plugin for this section.
85
   *
86 87 88 89 90 91 92 93 94
   * @return \Drupal\Core\Layout\LayoutInterface
   *   The layout plugin.
   */
  public function getLayout() {
    return $this->layoutPluginManager()->createInstance($this->getLayoutId(), $this->getLayoutSettings());
  }

  /**
   * Gets the layout plugin ID for this section.
95
   *
96 97
   * @return string
   *   The layout plugin ID.
98
   *
99 100
   * @internal
   *   This method should only be used by code responsible for storing the data.
101
   */
102 103 104
  public function getLayoutId() {
    return $this->layoutId;
  }
105

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
  /**
   * Gets the layout plugin settings for this section.
   *
   * @return mixed[]
   *   The layout plugin settings.
   *
   * @internal
   *   This method should only be used by code responsible for storing the data.
   */
  public function getLayoutSettings() {
    return $this->layoutSettings;
  }

  /**
   * Sets the layout plugin settings for this section.
   *
   * @param mixed[] $layout_settings
   *   The layout plugin settings.
   *
   * @return $this
   */
  public function setLayoutSettings(array $layout_settings) {
    $this->layoutSettings = $layout_settings;
    return $this;
  }
131

132 133 134 135 136 137 138 139
  /**
   * Returns the components of the section.
   *
   * @return \Drupal\layout_builder\SectionComponent[]
   *   The components.
   */
  public function getComponents() {
    return $this->components;
140 141 142
  }

  /**
143
   * Gets the component for a given UUID.
144 145
   *
   * @param string $uuid
146
   *   The UUID of the component to retrieve.
147
   *
148 149
   * @return \Drupal\layout_builder\SectionComponent
   *   The component.
150 151
   *
   * @throws \InvalidArgumentException
152
   *   Thrown when the expected UUID does not exist.
153
   */
154 155 156
  public function getComponent($uuid) {
    if (!isset($this->components[$uuid])) {
      throw new \InvalidArgumentException(sprintf('Invalid UUID "%s"', $uuid));
157 158
    }

159 160
    return $this->components[$uuid];
  }
161

162 163 164 165 166 167 168 169 170 171
  /**
   * Helper method to set a component.
   *
   * @param \Drupal\layout_builder\SectionComponent $component
   *   The component.
   *
   * @return $this
   */
  protected function setComponent(SectionComponent $component) {
    $this->components[$component->getUuid()] = $component;
172 173 174 175
    return $this;
  }

  /**
176
   * Removes a given component from a region.
177 178
   *
   * @param string $uuid
179
   *   The UUID of the component to remove.
180 181 182
   *
   * @return $this
   */
183 184
  public function removeComponent($uuid) {
    unset($this->components[$uuid]);
185 186 187 188
    return $this;
  }

  /**
189
   * Appends a component to the end of a region.
190
   *
191 192
   * @param \Drupal\layout_builder\SectionComponent $component
   *   The component being appended.
193 194 195
   *
   * @return $this
   */
196 197 198
  public function appendComponent(SectionComponent $component) {
    $component->setWeight($this->getNextHighestWeight($component->getRegion()));
    $this->setComponent($component);
199 200 201 202
    return $this;
  }

  /**
203
   * Returns the next highest weight of the component in a region.
204 205 206
   *
   * @param string $region
   *   The region name.
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
   *
   * @return int
   *   A number higher than the highest weight of the component in the region.
   */
  protected function getNextHighestWeight($region) {
    $components = $this->getComponentsByRegion($region);
    $weights = array_map(function (SectionComponent $component) {
      return $component->getWeight();
    }, $components);
    return $weights ? max($weights) + 1 : 0;
  }

  /**
   * Gets the components for a specific region.
   *
   * @param string $region
   *   The region name.
   *
   * @return \Drupal\layout_builder\SectionComponent[]
   *   An array of components in the specified region, sorted by weight.
   */
  protected function getComponentsByRegion($region) {
    $components = array_filter($this->getComponents(), function (SectionComponent $component) use ($region) {
      return $component->getRegion() === $region;
    });
    uasort($components, function (SectionComponent $a, SectionComponent $b) {
      return $a->getWeight() > $b->getWeight() ? 1 : -1;
    });
    return $components;
  }

  /**
   * Inserts a component after a specified existing component.
   *
241
   * @param string $preceding_uuid
242 243 244
   *   The UUID of the existing component to insert after.
   * @param \Drupal\layout_builder\SectionComponent $component
   *   The component being inserted.
245 246 247 248
   *
   * @return $this
   *
   * @throws \InvalidArgumentException
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
   *   Thrown when the expected UUID does not exist.
   */
  public function insertAfterComponent($preceding_uuid, SectionComponent $component) {
    // Find the delta of the specified UUID.
    $uuids = array_keys($this->getComponentsByRegion($component->getRegion()));
    $delta = array_search($preceding_uuid, $uuids, TRUE);
    if ($delta === FALSE) {
      throw new \InvalidArgumentException(sprintf('Invalid preceding UUID "%s"', $preceding_uuid));
    }
    return $this->insertComponent($delta + 1, $component);
  }

  /**
   * Inserts a component at a specified delta.
   *
   * @param int $delta
   *   The zero-based delta in which to insert the component.
   * @param \Drupal\layout_builder\SectionComponent $new_component
   *   The component being inserted.
   *
   * @return $this
   *
   * @throws \OutOfBoundsException
   *   Thrown when the specified delta is invalid.
273
   */
274 275 276 277 278
  public function insertComponent($delta, SectionComponent $new_component) {
    $components = $this->getComponentsByRegion($new_component->getRegion());
    $count = count($components);
    if ($delta > $count) {
      throw new \OutOfBoundsException(sprintf('Invalid delta "%s" for the "%s" component', $delta, $new_component->getUuid()));
279 280
    }

281 282 283
    // If the delta is the end of the list, append the component instead.
    if ($delta === $count) {
      return $this->appendComponent($new_component);
284 285
    }

286 287 288 289 290 291 292 293
    // Find the weight of the component that exists at the specified delta.
    $weight = array_values($components)[$delta]->getWeight();
    $this->setComponent($new_component->setWeight($weight++));

    // Increase the weight of every subsequent component.
    foreach (array_slice($components, $delta) as $component) {
      $component->setWeight($weight++);
    }
294 295 296
    return $this;
  }

297 298 299 300 301 302 303 304 305 306
  /**
   * Wraps the layout plugin manager.
   *
   * @return \Drupal\Core\Layout\LayoutPluginManagerInterface
   *   The layout plugin manager.
   */
  protected function layoutPluginManager() {
    return \Drupal::service('plugin.manager.core.layout');
  }

307
}