diff --git a/core/composer.json b/core/composer.json index c3600ab1a6..e399eb2f3a 100644 --- a/core/composer.json +++ b/core/composer.json @@ -110,6 +110,7 @@ "drupal/image": "self.version", "drupal/inline_form_errors": "self.version", "drupal/language": "self.version", + "drupal/layout_builder": "self.version", "drupal/layout_discovery": "self.version", "drupal/link": "self.version", "drupal/locale": "self.version", diff --git a/core/modules/layout_builder/layout_builder.info.yml b/core/modules/layout_builder/layout_builder.info.yml new file mode 100644 index 0000000000..50a51d0d2e --- /dev/null +++ b/core/modules/layout_builder/layout_builder.info.yml @@ -0,0 +1,8 @@ +name: 'Layout Builder' +type: module +description: 'Provides layout building utility.' +package: Core (Experimental) +version: VERSION +core: 8.x +dependencies: + - layout_discovery diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module new file mode 100644 index 0000000000..ab80ff50ce --- /dev/null +++ b/core/modules/layout_builder/layout_builder.module @@ -0,0 +1,19 @@ +' . t('About') . ''; + $output .= '

' . t('Layout Builder provides layout building utility, surprisingly.') . '

'; + $output .= '

' . t('For more information, see the online documentation for the Layout Builder module.', [':layout-builder-documentation' => 'https://www.drupal.org/docs/8/core/modules/layout_builder']) . '

'; + return $output; + } +} diff --git a/core/modules/layout_builder/src/LayoutSectionItemInterface.php b/core/modules/layout_builder/src/LayoutSectionItemInterface.php new file mode 100644 index 0000000000..1a39cdea1e --- /dev/null +++ b/core/modules/layout_builder/src/LayoutSectionItemInterface.php @@ -0,0 +1,15 @@ + FALSE, + 'text' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function build() { + return ['#markup' => $this->configuration['text']]; + } + +} diff --git a/core/modules/layout_builder/src/Plugin/Field/FieldFormatter/LayoutSectionFormatter.php b/core/modules/layout_builder/src/Plugin/Field/FieldFormatter/LayoutSectionFormatter.php new file mode 100644 index 0000000000..5b0700855a --- /dev/null +++ b/core/modules/layout_builder/src/Plugin/Field/FieldFormatter/LayoutSectionFormatter.php @@ -0,0 +1,137 @@ +account = $account; + $this->layoutPluginManager = $layoutPluginManager; + $this->blockManager = $blockManager; + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('current_user'), + $container->get('plugin.manager.core.layout'), + $container->get('plugin.manager.block'), + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'] + ); + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + + foreach ($items as $delta => $item) { + $elements[$delta] = $this->buildSection($item); + } + + return $elements; + } + + /** + * Builds the render array for the layout section. + * + * @param \Drupal\layout_builder\LayoutSectionItemInterface $item + * The layout section item. + * + * @return array + * A render array for the field item. + */ + protected function buildSection(LayoutSectionItemInterface $item) { + /** @var \Drupal\Core\Layout\LayoutInterface $layout */ + $layout = $this->layoutPluginManager->createInstance($item->layout); + $regions = []; + foreach ($item->section as $region => $blocks) { + foreach ($blocks as $uuid => $configuration) { + /** @var \Drupal\Core\Block\BlockPluginInterface $block */ + $block = $this->blockManager->createInstance($configuration['plugin_id'], $configuration); + $access = $block->access($this->account, TRUE); + if ($access->isAllowed()) { + $regions[$region][$uuid] = $block->build(); + } + } + } + return $layout->build($regions); + } + +} diff --git a/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php new file mode 100644 index 0000000000..8cf547d5ed --- /dev/null +++ b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php @@ -0,0 +1,87 @@ +setLabel(new TranslatableMarkup('Layout')) + ->setSetting('case_sensitive', FALSE) + ->setRequired(TRUE); + $properties[static::mainPropertyName()] = MapDataDefinition::create('map') + ->setLabel(new TranslatableMarkup('Layout Section')) + ->setRequired(FALSE); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function mainPropertyName() { + return 'section'; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + $schema = [ + 'columns' => [ + 'layout' => [ + 'type' => 'varchar', + 'length' => '255', + 'binary' => FALSE, + ], + static::mainPropertyName() => [ + 'type' => 'blob', + 'size' => 'normal', + 'serialize' => TRUE, + ], + ], + ]; + + return $schema; + } + + /** + * {@inheritdoc} + */ + public static function generateSampleValue(FieldDefinitionInterface $field_definition) { + $values['layout'] = 'layout_onecol'; + $values[static::mainPropertyName()] = []; + return $values; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $sections = $this->get(static::mainPropertyName())->getValue(); + return empty($sections); + } + +} diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutSectionFormatterTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionFormatterTest.php new file mode 100644 index 0000000000..c391ed92d4 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionFormatterTest.php @@ -0,0 +1,174 @@ +installConfig(['field']); + $this->installEntitySchema('entity_test'); + + $entity_type = 'entity_test'; + $bundle = $entity_type; + $this->fieldName = Unicode::strtolower($this->randomMachineName()); + + $field_storage = FieldStorageConfig::create([ + 'field_name' => $this->fieldName, + 'entity_type' => $entity_type, + 'type' => 'layout_section', + ]); + $field_storage->save(); + + $instance = FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => $bundle, + 'label' => $this->randomMachineName(), + ]); + $instance->save(); + + $this->display = EntityViewDisplay::create([ + 'targetEntityType' => $entity_type, + 'bundle' => $bundle, + 'mode' => 'default', + 'status' => TRUE, + ]); + $this->display->setComponent($this->fieldName, [ + 'type' => 'layout_section', + 'settings' => [], + ]); + $this->display->save(); + } + + /** + * Tests layout_section formatter output. + * + * @dataProvider providerTestLayoutSectionFormatter + */ + public function testLayoutSectionFormatter($layout_data, $expected_selector, $expected_content) { + $values = []; + $values[$this->fieldName] = $layout_data; + $entity = EntityTest::create($values); + + // Build and render the content. + $content = $this->display->build($entity); + $this->render($content); + + // Find the given selector. + if (!is_array($expected_selector)) { + $expected_selector = [$expected_selector]; + } + foreach ($expected_selector as $selector) { + $element = $this->cssSelect($selector); + $this->assertNotEmpty($element); + } + + // Find the given content. + if (!is_array($expected_content)) { + $expected_content = [$expected_content]; + } + foreach ($expected_content as $content) { + $this->assertRaw($content); + } + } + + /** + * Provides test data to ::testLayoutSectionFormatter(). + */ + public function providerTestLayoutSectionFormatter() { + $data = []; + $data[] = [ + [ + [ + 'layout' => 'layout_onecol', + 'section' => [ + 'content' => [ + 'baz' => [ + 'plugin_id' => 'system_powered_by_block' + ] + ] + ] + ], + ], + '.layout--onecol', + 'Powered by', + ]; + $data[] = [ + [ + [ + 'layout' => 'layout_onecol', + 'section' => [ + 'content' => [ + 'baz' => [ + 'plugin_id' => 'system_powered_by_block' + ] + ] + ] + ], + [ + 'layout' => 'layout_twocol', + 'section' => [ + 'left' => [ + 'foo' => [ + 'plugin_id' => 'test_content', + 'text' => 'foo text' + ] + ], + 'right' => [ + 'bar' => [ + 'plugin_id' => 'test_content', + 'text' => 'bar text' + ] + ] + ] + ] + ], + [ + '.layout--onecol', + '.layout--twocol' + ], + [ + 'Powered by', + 'foo text', + 'bar text' + ], + ]; + return $data; + } + +}