diff --git a/core/composer.json b/core/composer.json index 862b5a3..c52add0 100644 --- a/core/composer.json +++ b/core/composer.json @@ -105,6 +105,7 @@ "drupal/image": "self.version", "drupal/inline_form_errors": "self.version", "drupal/language": "self.version", + "drupal/layout_plugin": "self.version", "drupal/link": "self.version", "drupal/locale": "self.version", "drupal/minimal": "self.version", diff --git a/core/modules/field_layout/field_layout.info.yml b/core/modules/field_layout/field_layout.info.yml index 62f225a..67f2ba0 100644 --- a/core/modules/field_layout/field_layout.info.yml +++ b/core/modules/field_layout/field_layout.info.yml @@ -4,3 +4,5 @@ description: 'Adds layout capabilities to the Field UI.' package: Core (Experimental) version: VERSION core: 8.x +dependencies: + - layout_plugin diff --git a/core/modules/field_layout/field_layout.layouts.yml b/core/modules/field_layout/field_layout.layouts.yml new file mode 100644 index 0000000..0aaa87b --- /dev/null +++ b/core/modules/field_layout/field_layout.layouts.yml @@ -0,0 +1,21 @@ +onecol: + label: 'One column' + path: layouts/onecol + theme: field_layout__onecol + category: 'Columns: 1' + default_region: content + regions: + content: + label: Content +twocol: + label: 'Two column' + path: layouts/twocol + theme: field_layout__twocol + library: field_layout/drupal.field_layout.twocol + category: 'Columns: 2' + default_region: left + regions: + left: + label: Left + right: + label: Right diff --git a/core/modules/field_layout/field_layout.module b/core/modules/field_layout/field_layout.module index 5310188..8ce1400 100644 --- a/core/modules/field_layout/field_layout.module +++ b/core/modules/field_layout/field_layout.module @@ -93,10 +93,3 @@ function field_layout_form_alter(&$form, FormStateInterface $form_state, $form_i } } } - -/** - * Implements hook_theme(). - */ -function field_layout_theme($existing, $type, $theme, $path) { - return \Drupal::service('field_layout.layout_plugin_manager')->getThemeImplementations(); -} diff --git a/core/modules/field_layout/field_layout.services.yml b/core/modules/field_layout/field_layout.services.yml deleted file mode 100644 index e491e99..0000000 --- a/core/modules/field_layout/field_layout.services.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - field_layout.layout_plugin_manager: - class: Drupal\field_layout\LayoutPluginManager - arguments: ['@module_handler', '@theme_handler', '@string_translation'] diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php index 931cc1c..a14ef05 100644 --- a/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php +++ b/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php @@ -19,7 +19,7 @@ class FieldLayoutEntityFormDisplay extends EntityFormDisplay implements EntityDi * which is fixed in PHP 7.0.6. */ public function getDefaultRegion() { - $layout_definition = \Drupal::service('field_layout.layout_plugin_manager')->getDefinition($this->getLayoutId() ?: 'onecol'); + $layout_definition = \Drupal::service('plugin.manager.layout_plugin')->getDefinition($this->getLayoutId() ?: 'onecol'); return isset($layout_definition['default_region']) ? $layout_definition['default_region'] : key($layout_definition['regions']); } diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php index 53952e5..94d0913 100644 --- a/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php +++ b/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php @@ -19,7 +19,7 @@ class FieldLayoutEntityViewDisplay extends EntityViewDisplay implements EntityDi * which is fixed in PHP 7.0.6. */ public function getDefaultRegion() { - $layout_definition = \Drupal::service('field_layout.layout_plugin_manager')->getDefinition($this->getLayoutId() ?: 'onecol'); + $layout_definition = \Drupal::service('plugin.manager.layout_plugin')->getDefinition($this->getLayoutId() ?: 'onecol'); return isset($layout_definition['default_region']) ? $layout_definition['default_region'] : key($layout_definition['regions']); } diff --git a/core/modules/field_layout/src/FieldLayoutBuilder.php b/core/modules/field_layout/src/FieldLayoutBuilder.php index f5a0650..d725374 100644 --- a/core/modules/field_layout/src/FieldLayoutBuilder.php +++ b/core/modules/field_layout/src/FieldLayoutBuilder.php @@ -6,6 +6,7 @@ use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\field_layout\Display\EntityDisplayWithLayoutInterface; +use Drupal\layout_plugin\LayoutPluginManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -16,7 +17,7 @@ class FieldLayoutBuilder implements ContainerInjectionInterface { /** * The layout plugin manager. * - * @var \Drupal\field_layout\LayoutPluginManagerInterface + * @var \Drupal\layout_plugin\LayoutPluginManagerInterface */ protected $layoutPluginManager; @@ -30,7 +31,7 @@ class FieldLayoutBuilder implements ContainerInjectionInterface { /** * FieldLayoutBuilder constructor. * - * @param \Drupal\field_layout\LayoutPluginManagerInterface $layout_plugin_manager + * @param \Drupal\layout_plugin\LayoutPluginManagerInterface $layout_plugin_manager * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */ public function __construct(LayoutPluginManagerInterface $layout_plugin_manager, EntityFieldManagerInterface $entity_field_manager) { @@ -43,7 +44,7 @@ public function __construct(LayoutPluginManagerInterface $layout_plugin_manager, */ public static function create(ContainerInterface $container) { return new static( - $container->get('field_layout.layout_plugin_manager'), + $container->get('plugin.manager.layout_plugin'), $container->get('entity_field.manager') ); } @@ -85,12 +86,7 @@ public function build(array &$build, EntityDisplayWithLayoutInterface $display, unset($build[$name]); } } - $build['field_layout'] = $regions; - // Wrap the regions in a layout element. - $build['field_layout']['#theme'] = $layout_definition['theme']; - if (isset($layout_definition['library'])) { - $build['field_layout']['#attached']['library'][] = $layout_definition['library']; - } + $build['field_layout'] = $this->layoutPluginManager->createInstance($display->getLayoutId())->build($regions); } } diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php index 94cae70..1fb8e3b 100644 --- a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php +++ b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php @@ -14,7 +14,7 @@ /** * The field layout plugin manager. * - * @var \Drupal\field_layout\LayoutPluginManagerInterface + * @var \Drupal\layout_plugin\LayoutPluginManagerInterface */ protected $layoutPluginManager; diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php b/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php index 712171b..05c85b5 100644 --- a/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php +++ b/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php @@ -6,7 +6,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\field_layout\LayoutPluginManagerInterface; +use Drupal\layout_plugin\LayoutPluginManagerInterface; use Drupal\field_ui\Form\EntityFormDisplayEditForm; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -24,7 +24,7 @@ class FieldLayoutEntityFormDisplayEditForm extends EntityFormDisplayEditForm { * The field type manager. * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager * The widget plugin manager. - * @param \Drupal\field_layout\LayoutPluginManagerInterface $field_layout_plugin_manager + * @param \Drupal\layout_plugin\LayoutPluginManagerInterface $field_layout_plugin_manager * The field layout plugin manager. */ public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, LayoutPluginManagerInterface $field_layout_plugin_manager) { @@ -39,7 +39,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('plugin.manager.field.field_type'), $container->get('plugin.manager.field.widget'), - $container->get('field_layout.layout_plugin_manager') + $container->get('plugin.manager.layout_plugin') ); } diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php b/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php index bddad57..d12da7d 100644 --- a/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php +++ b/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php @@ -6,7 +6,7 @@ use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\field_layout\LayoutPluginManagerInterface; +use Drupal\layout_plugin\LayoutPluginManagerInterface; use Drupal\field_ui\Form\EntityViewDisplayEditForm; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -24,7 +24,7 @@ class FieldLayoutEntityViewDisplayEditForm extends EntityViewDisplayEditForm { * The field type manager. * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager * The formatter plugin manager. - * @param \Drupal\field_layout\LayoutPluginManagerInterface $field_layout_plugin_manager + * @param \Drupal\layout_plugin\LayoutPluginManagerInterface $field_layout_plugin_manager * The field layout plugin manager. */ public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, LayoutPluginManagerInterface $field_layout_plugin_manager) { @@ -39,7 +39,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('plugin.manager.field.field_type'), $container->get('plugin.manager.field.formatter'), - $container->get('field_layout.layout_plugin_manager') + $container->get('plugin.manager.layout_plugin') ); } diff --git a/core/modules/field_layout/src/LayoutPluginManagerInterface.php b/core/modules/field_layout/src/LayoutPluginManagerInterface.php deleted file mode 100644 index 2c95561..0000000 --- a/core/modules/field_layout/src/LayoutPluginManagerInterface.php +++ /dev/null @@ -1,42 +0,0 @@ -layoutPluginManager = $this->prophesize(LayoutPluginManagerInterface::class); $this->layoutPluginManager->getDefinition('unknown', FALSE)->willReturn([]); - $this->layoutPluginManager->getDefinition('twocol', FALSE)->willReturn([ + + $twocol_definition = [ 'library' => 'field_layout/drupal.field_layout.twocol', 'theme' => 'field_layout__twocol', 'regions' => [ @@ -50,8 +52,13 @@ protected function setUp() { 'label' => 'Right', ], ], - ]); + ]; + $layout_plugin = new LayoutDefault([], 'twocol', $twocol_definition); + $this->layoutPluginManager->createInstance('twocol')->willReturn($layout_plugin); + $this->layoutPluginManager->getDefinition('twocol', FALSE)->willReturn($twocol_definition); + $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class); + $this->fieldLayoutBuilder = new FieldLayoutBuilder($this->layoutPluginManager->reveal(), $this->entityFieldManager->reveal()); } diff --git a/core/modules/layout_plugin/layout_plugin.info.yml b/core/modules/layout_plugin/layout_plugin.info.yml new file mode 100644 index 0000000..7058dca --- /dev/null +++ b/core/modules/layout_plugin/layout_plugin.info.yml @@ -0,0 +1,6 @@ +name: 'Layout Plugin' +type: module +description: 'Provides a way for modules or themes to register layouts.' +package: Core (Experimental) +version: VERSION +core: 8.x diff --git a/core/modules/layout_plugin/layout_plugin.module b/core/modules/layout_plugin/layout_plugin.module new file mode 100644 index 0000000..661b332 --- /dev/null +++ b/core/modules/layout_plugin/layout_plugin.module @@ -0,0 +1,28 @@ +' . t('About') . ''; + $output .= '

' . t('Layout Plugin allows modules or themes to register layouts, and for other modules to list the available layouts and render them.') . '

'; + $output .= '

' . t('For more information, see the online documentation for the Layout Plugin module.', [':layout-plugin-documentation' => 'https://www.drupal.org/node/2619128']) . '

'; + return $output; + } +} + +/** + * Implements hook_theme(). + */ +function layout_plugin_theme($existing, $type, $theme, $path) { + return \Drupal::service('plugin.manager.layout_plugin')->getThemeImplementations(); +} diff --git a/core/modules/layout_plugin/layout_plugin.services.yml b/core/modules/layout_plugin/layout_plugin.services.yml new file mode 100644 index 0000000..30e3b96 --- /dev/null +++ b/core/modules/layout_plugin/layout_plugin.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.layout_plugin: + class: Drupal\layout_plugin\LayoutPluginManager + arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@theme_handler', '@string_translation'] diff --git a/core/modules/layout_plugin/src/Annotation/Layout.php b/core/modules/layout_plugin/src/Annotation/Layout.php new file mode 100644 index 0000000..4ad0405 --- /dev/null +++ b/core/modules/layout_plugin/src/Annotation/Layout.php @@ -0,0 +1,90 @@ + LayoutDefault::class, + ]; + + /** * LayoutPluginManager constructor. * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler @@ -45,7 +59,8 @@ class LayoutPluginManager implements LayoutPluginManagerInterface { * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation * The string translation service. */ - public function __construct(ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation) { + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation) { + parent::__construct('Plugin/Layout', $namespaces, $module_handler, LayoutInterface::class, Layout::class); $this->moduleHandler = $module_handler; $this->themeHandler = $theme_handler; $this->stringTranslation = $string_translation; @@ -54,50 +69,27 @@ public function __construct(ModuleHandlerInterface $module_handler, ThemeHandler /** * {@inheritdoc} */ - public function getDefinitions() { - // @todo Replace with layout_plugin in https://www.drupal.org/node/2296423. - $layouts = [ - 'onecol' => [ - 'label' => $this->t('One column'), - 'theme' => 'field_layout__onecol', - 'path' => 'layouts/onecol', - 'provider' => 'field_layout', - 'category' => $this->t('Columns: 1'), - 'default_region' => 'content', - 'regions' => [ - 'content' => [ - 'label' => $this->t('Content'), - ], - ], - ], - 'twocol' => [ - 'label' => $this->t('Two column'), - 'library' => 'field_layout/drupal.field_layout.twocol', - 'theme' => 'field_layout__twocol', - 'path' => 'layouts/twocol', - 'provider' => 'field_layout', - 'category' => $this->t('Columns: 2'), - 'default_region' => 'left', - 'regions' => [ - 'left' => [ - 'label' => $this->t('Left'), - ], - 'right' => [ - 'label' => $this->t('Right'), - ], - ], - ], - ]; - foreach ($layouts as $layout_id => &$layout) { - $this->processDefinition($layout, $layout_id); + protected function providerExists($provider) { + return $this->moduleHandler->moduleExists($provider) || $this->themeHandler->themeExists($provider); + } + + /** + * {@inheritdoc} + */ + protected function getDiscovery() { + if (!$this->discovery) { + $discovery = parent::getDiscovery(); + $this->discovery = new YamlDiscoveryDecorator($discovery, 'layouts', $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories()); } - return $layouts; + return $this->discovery; } /** - * Performs extra processing on layout definitions. + * {@inheritdoc} */ - protected function processDefinition(&$definition, $plugin_id) { + public function processDefinition(&$definition, $plugin_id) { + parent::processDefinition($definition, $plugin_id); + // Add the module or theme path to the 'path'. if ($this->moduleHandler->moduleExists($definition['provider'])) { $definition['provider_type'] = 'module'; @@ -123,10 +115,6 @@ protected function processDefinition(&$definition, $plugin_id) { elseif (empty($definition['theme'])) { $definition['theme'] = strtr($definition['template'], '-', '_'); } - - // @todo Remove once layout_plugin is done, this will be added by the plugin - // system in https://www.drupal.org/node/2296423. - $definition['id'] = $plugin_id; } /** diff --git a/core/modules/layout_plugin/src/LayoutPluginManagerInterface.php b/core/modules/layout_plugin/src/LayoutPluginManagerInterface.php new file mode 100644 index 0000000..11cb9bf --- /dev/null +++ b/core/modules/layout_plugin/src/LayoutPluginManagerInterface.php @@ -0,0 +1,22 @@ +setConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ + public function build(array $regions) { + $build = array_intersect_key($regions, $this->pluginDefinition['regions']); + $build['#theme'] = $this->pluginDefinition['theme']; + if (isset($this->pluginDefinition['library'])) { + $build['#attached']['library'][] = $this->pluginDefinition['library']; + } + return $build; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration = $form_state->getValues(); + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = NestedArray::mergeDeep( + $this->defaultConfiguration(), + $configuration + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return []; + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + return []; + } + +} diff --git a/core/modules/layout_plugin/src/Plugin/LayoutDefault.php b/core/modules/layout_plugin/src/Plugin/LayoutDefault.php new file mode 100644 index 0000000..ef99ef6 --- /dev/null +++ b/core/modules/layout_plugin/src/Plugin/LayoutDefault.php @@ -0,0 +1,10 @@ +moduleHandler->moduleExists('field_layout')->willReturn(TRUE); $extension = new Extension('/', 'module', 'core/modules/field_layout/field_layout.info.yml'); $this->moduleHandler->getModule('field_layout')->willReturn($extension); + $this->moduleHandler->getModuleDirectories()->willReturn(['field_layout' => $this->root . '/core/modules/field_layout']); $this->themeHandler = $this->prophesize(ThemeHandlerInterface::class); + $this->themeHandler->getThemeDirectories()->willReturn([]); + + $this->cacheBackend = $this->prophesize(CacheBackendInterface::class); - $this->layoutPluginManager = new LayoutPluginManager($this->moduleHandler->reveal(), $this->themeHandler->reveal(), $this->getStringTranslationStub()); + $this->layoutPluginManager = new LayoutPluginManager(new \ArrayObject(), $this->cacheBackend->reveal(), $this->moduleHandler->reveal(), $this->themeHandler->reveal(), $this->getStringTranslationStub()); } /** @@ -78,6 +91,7 @@ public function testGetDefinition() { 'library' => 'field_layout/drupal.field_layout.twocol', 'provider_type' => 'module', 'id' => 'twocol', + 'class' => LayoutDefault::class, ]; $layout_definition = $this->layoutPluginManager->getDefinition('twocol'); $this->assertEquals($expected, $layout_definition);