Chris@14: layoutId = $layout_id; Chris@14: $this->layoutSettings = $layout_settings; Chris@14: foreach ($components as $component) { Chris@14: $this->setComponent($component); Chris@14: } Chris@18: $this->thirdPartySettings = $third_party_settings; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns the renderable array for this section. Chris@14: * Chris@14: * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts Chris@14: * An array of available contexts. Chris@14: * @param bool $in_preview Chris@14: * TRUE if the section is being previewed, FALSE otherwise. Chris@14: * Chris@14: * @return array Chris@14: * A renderable array representing the content of the section. Chris@14: */ Chris@14: public function toRenderArray(array $contexts = [], $in_preview = FALSE) { Chris@14: $regions = []; Chris@14: foreach ($this->getComponents() as $component) { Chris@14: if ($output = $component->toRenderArray($contexts, $in_preview)) { Chris@14: $regions[$component->getRegion()][$component->getUuid()] = $output; Chris@14: } Chris@14: } Chris@14: Chris@14: return $this->getLayout()->build($regions); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Gets the layout plugin for this section. Chris@14: * Chris@14: * @return \Drupal\Core\Layout\LayoutInterface Chris@14: * The layout plugin. Chris@14: */ Chris@14: public function getLayout() { Chris@14: return $this->layoutPluginManager()->createInstance($this->getLayoutId(), $this->getLayoutSettings()); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Gets the layout plugin ID for this section. Chris@14: * Chris@14: * @return string Chris@14: * The layout plugin ID. Chris@14: * Chris@14: * @internal Chris@14: * This method should only be used by code responsible for storing the data. Chris@14: */ Chris@14: public function getLayoutId() { Chris@14: return $this->layoutId; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Gets the layout plugin settings for this section. Chris@14: * Chris@14: * @return mixed[] Chris@14: * The layout plugin settings. Chris@14: * Chris@14: * @internal Chris@14: * This method should only be used by code responsible for storing the data. Chris@14: */ Chris@14: public function getLayoutSettings() { Chris@14: return $this->layoutSettings; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Sets the layout plugin settings for this section. Chris@14: * Chris@14: * @param mixed[] $layout_settings Chris@14: * The layout plugin settings. Chris@14: * Chris@14: * @return $this Chris@14: */ Chris@14: public function setLayoutSettings(array $layout_settings) { Chris@14: $this->layoutSettings = $layout_settings; Chris@14: return $this; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Gets the default region. Chris@14: * Chris@14: * @return string Chris@14: * The machine-readable name of the default region. Chris@14: */ Chris@14: public function getDefaultRegion() { Chris@14: return $this->layoutPluginManager()->getDefinition($this->getLayoutId())->getDefaultRegion(); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns the components of the section. Chris@14: * Chris@14: * @return \Drupal\layout_builder\SectionComponent[] Chris@14: * The components. Chris@14: */ Chris@14: public function getComponents() { Chris@14: return $this->components; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Gets the component for a given UUID. Chris@14: * Chris@14: * @param string $uuid Chris@14: * The UUID of the component to retrieve. Chris@14: * Chris@14: * @return \Drupal\layout_builder\SectionComponent Chris@14: * The component. Chris@14: * Chris@14: * @throws \InvalidArgumentException Chris@14: * Thrown when the expected UUID does not exist. Chris@14: */ Chris@14: public function getComponent($uuid) { Chris@14: if (!isset($this->components[$uuid])) { Chris@14: throw new \InvalidArgumentException(sprintf('Invalid UUID "%s"', $uuid)); Chris@14: } Chris@14: Chris@14: return $this->components[$uuid]; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Helper method to set a component. Chris@14: * Chris@14: * @param \Drupal\layout_builder\SectionComponent $component Chris@14: * The component. Chris@14: * Chris@14: * @return $this Chris@14: */ Chris@14: protected function setComponent(SectionComponent $component) { Chris@14: $this->components[$component->getUuid()] = $component; Chris@14: return $this; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Removes a given component from a region. Chris@14: * Chris@14: * @param string $uuid Chris@14: * The UUID of the component to remove. Chris@14: * Chris@14: * @return $this Chris@14: */ Chris@14: public function removeComponent($uuid) { Chris@14: unset($this->components[$uuid]); Chris@14: return $this; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Appends a component to the end of a region. Chris@14: * Chris@14: * @param \Drupal\layout_builder\SectionComponent $component Chris@14: * The component being appended. Chris@14: * Chris@14: * @return $this Chris@14: */ Chris@14: public function appendComponent(SectionComponent $component) { Chris@14: $component->setWeight($this->getNextHighestWeight($component->getRegion())); Chris@14: $this->setComponent($component); Chris@14: return $this; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns the next highest weight of the component in a region. Chris@14: * Chris@14: * @param string $region Chris@14: * The region name. Chris@14: * Chris@14: * @return int Chris@14: * A number higher than the highest weight of the component in the region. Chris@14: */ Chris@14: protected function getNextHighestWeight($region) { Chris@14: $components = $this->getComponentsByRegion($region); Chris@14: $weights = array_map(function (SectionComponent $component) { Chris@14: return $component->getWeight(); Chris@14: }, $components); Chris@14: return $weights ? max($weights) + 1 : 0; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Gets the components for a specific region. Chris@14: * Chris@14: * @param string $region Chris@14: * The region name. Chris@14: * Chris@14: * @return \Drupal\layout_builder\SectionComponent[] Chris@14: * An array of components in the specified region, sorted by weight. Chris@14: */ Chris@18: public function getComponentsByRegion($region) { Chris@14: $components = array_filter($this->getComponents(), function (SectionComponent $component) use ($region) { Chris@14: return $component->getRegion() === $region; Chris@14: }); Chris@14: uasort($components, function (SectionComponent $a, SectionComponent $b) { Chris@14: return $a->getWeight() > $b->getWeight() ? 1 : -1; Chris@14: }); Chris@14: return $components; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Inserts a component after a specified existing component. Chris@14: * Chris@14: * @param string $preceding_uuid Chris@14: * The UUID of the existing component to insert after. Chris@14: * @param \Drupal\layout_builder\SectionComponent $component Chris@14: * The component being inserted. Chris@14: * Chris@14: * @return $this Chris@14: * Chris@14: * @throws \InvalidArgumentException Chris@14: * Thrown when the expected UUID does not exist. Chris@14: */ Chris@14: public function insertAfterComponent($preceding_uuid, SectionComponent $component) { Chris@14: // Find the delta of the specified UUID. Chris@14: $uuids = array_keys($this->getComponentsByRegion($component->getRegion())); Chris@14: $delta = array_search($preceding_uuid, $uuids, TRUE); Chris@14: if ($delta === FALSE) { Chris@14: throw new \InvalidArgumentException(sprintf('Invalid preceding UUID "%s"', $preceding_uuid)); Chris@14: } Chris@14: return $this->insertComponent($delta + 1, $component); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Inserts a component at a specified delta. Chris@14: * Chris@14: * @param int $delta Chris@14: * The zero-based delta in which to insert the component. Chris@14: * @param \Drupal\layout_builder\SectionComponent $new_component Chris@14: * The component being inserted. Chris@14: * Chris@14: * @return $this Chris@14: * Chris@14: * @throws \OutOfBoundsException Chris@14: * Thrown when the specified delta is invalid. Chris@14: */ Chris@14: public function insertComponent($delta, SectionComponent $new_component) { Chris@14: $components = $this->getComponentsByRegion($new_component->getRegion()); Chris@14: $count = count($components); Chris@14: if ($delta > $count) { Chris@14: throw new \OutOfBoundsException(sprintf('Invalid delta "%s" for the "%s" component', $delta, $new_component->getUuid())); Chris@14: } Chris@14: Chris@14: // If the delta is the end of the list, append the component instead. Chris@14: if ($delta === $count) { Chris@14: return $this->appendComponent($new_component); Chris@14: } Chris@14: Chris@14: // Find the weight of the component that exists at the specified delta. Chris@14: $weight = array_values($components)[$delta]->getWeight(); Chris@14: $this->setComponent($new_component->setWeight($weight++)); Chris@14: Chris@14: // Increase the weight of every subsequent component. Chris@14: foreach (array_slice($components, $delta) as $component) { Chris@14: $component->setWeight($weight++); Chris@14: } Chris@14: return $this; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Wraps the layout plugin manager. Chris@14: * Chris@14: * @return \Drupal\Core\Layout\LayoutPluginManagerInterface Chris@14: * The layout plugin manager. Chris@14: */ Chris@14: protected function layoutPluginManager() { Chris@14: return \Drupal::service('plugin.manager.core.layout'); Chris@14: } Chris@14: Chris@14: /** Chris@14: * Returns an array representation of the section. Chris@14: * Chris@16: * Only use this method if you are implementing custom storage for sections. Chris@14: * Chris@14: * @return array Chris@14: * An array representation of the section component. Chris@14: */ Chris@14: public function toArray() { Chris@14: return [ Chris@14: 'layout_id' => $this->getLayoutId(), Chris@14: 'layout_settings' => $this->getLayoutSettings(), Chris@14: 'components' => array_map(function (SectionComponent $component) { Chris@14: return $component->toArray(); Chris@14: }, $this->getComponents()), Chris@18: 'third_party_settings' => $this->thirdPartySettings, Chris@14: ]; Chris@14: } Chris@14: Chris@16: /** Chris@16: * Creates an object from an array representation of the section. Chris@16: * Chris@16: * Only use this method if you are implementing custom storage for sections. Chris@16: * Chris@16: * @param array $section Chris@16: * An array of section data in the format returned by ::toArray(). Chris@16: * Chris@16: * @return static Chris@16: * The section object. Chris@16: */ Chris@16: public static function fromArray(array $section) { Chris@18: // Ensure expected array keys are present. Chris@18: $section += [ Chris@18: 'layout_id' => '', Chris@18: 'layout_settings' => [], Chris@18: 'components' => [], Chris@18: 'third_party_settings' => [], Chris@18: ]; Chris@16: return new static( Chris@16: $section['layout_id'], Chris@16: $section['layout_settings'], Chris@18: array_map([SectionComponent::class, 'fromArray'], $section['components']), Chris@18: $section['third_party_settings'] Chris@16: ); Chris@16: } Chris@16: Chris@17: /** Chris@17: * Magic method: Implements a deep clone. Chris@17: */ Chris@17: public function __clone() { Chris@17: foreach ($this->components as $uuid => $component) { Chris@17: $this->components[$uuid] = clone $component; Chris@17: } Chris@17: } Chris@17: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function getThirdPartySetting($provider, $key, $default = NULL) { Chris@18: return isset($this->thirdPartySettings[$provider][$key]) ? $this->thirdPartySettings[$provider][$key] : $default; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function getThirdPartySettings($provider) { Chris@18: return isset($this->thirdPartySettings[$provider]) ? $this->thirdPartySettings[$provider] : []; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function setThirdPartySetting($provider, $key, $value) { Chris@18: $this->thirdPartySettings[$provider][$key] = $value; Chris@18: return $this; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function unsetThirdPartySetting($provider, $key) { Chris@18: unset($this->thirdPartySettings[$provider][$key]); Chris@18: // If the third party is no longer storing any information, completely Chris@18: // remove the array holding the settings for this provider. Chris@18: if (empty($this->thirdPartySettings[$provider])) { Chris@18: unset($this->thirdPartySettings[$provider]); Chris@18: } Chris@18: return $this; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function getThirdPartyProviders() { Chris@18: return array_keys($this->thirdPartySettings); Chris@18: } Chris@18: Chris@14: }