diff --git a/core/composer.json b/core/composer.json index 4b5de2e..72f5327 100644 --- a/core/composer.json +++ b/core/composer.json @@ -95,7 +95,6 @@ "drupal/editor": "self.version", "drupal/entity_reference": "self.version", "drupal/field": "self.version", - "drupal/field_layout": "self.version", "drupal/field_ui": "self.version", "drupal/file": "self.version", "drupal/filter": "self.version", @@ -106,7 +105,6 @@ "drupal/image": "self.version", "drupal/inline_form_errors": "self.version", "drupal/language": "self.version", - "drupal/layout_discovery": "self.version", "drupal/link": "self.version", "drupal/locale": "self.version", "drupal/minimal": "self.version", diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml index 711ca0f..e05a71e 100644 --- a/core/config/schema/core.entity.schema.yml +++ b/core/config/schema/core.entity.schema.yml @@ -84,6 +84,12 @@ core.entity_view_display.*.*.*: sequence: type: boolean label: 'Value' + layout_id: + type: string + label: 'Layout ID' + layout_settings: + type: layout_plugin.settings.[%parent.layout_id] + label: 'Layout settings' # Overview configuration information for form mode displays. core.entity_form_display.*.*.*: @@ -135,6 +141,12 @@ core.entity_form_display.*.*.*: sequence: type: boolean label: 'Component' + layout_id: + type: string + label: 'Layout ID' + layout_settings: + type: layout_plugin.settings.[%parent.layout_id] + label: 'Layout settings' # Default schema for entity display field with undefined type. field.formatter.settings.*: diff --git a/core/core.services.yml b/core/core.services.yml index 8bce755..1b753cb 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -640,6 +640,9 @@ services: plugin.manager.display_variant: class: Drupal\Core\Display\VariantManager parent: default_plugin_manager + plugin.manager.core.layout: + class: Drupal\Core\Layout\LayoutPluginManager + arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@theme_handler'] plugin.manager.queue_worker: class: Drupal\Core\Queue\QueueWorkerManager parent: default_plugin_manager diff --git a/core/includes/theme.inc b/core/includes/theme.inc index f180cbd..b0fed54 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1697,6 +1697,19 @@ function template_preprocess_breadcrumb(&$variables) { } /** + * Prepares variables for layout templates. + * + * @param array &$variables + * An associative array containing: + * - content: An associative array containing the properties of the element. + * Properties used: #settings, #layout. + */ +function template_preprocess_layout(&$variables) { + $variables['settings'] = isset($variables['content']['#settings']) ? $variables['content']['#settings'] : []; + $variables['layout'] = isset($variables['content']['#layout']) ? $variables['content']['#layout'] : []; +} + +/** * Callback for usort() within template_preprocess_field_multiple_value_form(). * * Sorts using ['_weight']['#value'] diff --git a/core/modules/field_layout/src/Display/EntityDisplayWithLayoutInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityDisplayWithLayoutInterface.php similarity index 79% rename from core/modules/field_layout/src/Display/EntityDisplayWithLayoutInterface.php rename to core/lib/Drupal/Core/Entity/Display/EntityDisplayWithLayoutInterface.php index 3bee65e..8c14d63 100644 --- a/core/modules/field_layout/src/Display/EntityDisplayWithLayoutInterface.php +++ b/core/lib/Drupal/Core/Entity/Display/EntityDisplayWithLayoutInterface.php @@ -1,8 +1,7 @@ getFields($element)) { + $layout = $this->getLayout(); + $fill = []; + $fill['#process'][] = '\Drupal\Core\Render\Element\RenderElement::processGroup'; + $fill['#pre_render'][] = '\Drupal\Core\Render\Element\RenderElement::preRenderGroup'; + $regions = array_fill_keys($layout->getPluginDefinition()->getRegionNames(), $fill); + foreach ($fields as $name => $field) { + // As this is a form, #group can be used to relocate the fields. This + // avoids breaking hook_form_alter() implementations by not actually + // moving the field in the form structure. If a #group is already set, + // do not overwrite it. + if (!isset($element[$name]['#group'])) { + $element[$name]['#group'] = $field['region']; + } + } + $element['layout'] = $layout->build($regions); + } + return $element; + } + + /** * {@inheritdoc} */ public function extractFormValues(FieldableEntityInterface $entity, array &$form, FormStateInterface $form_state) { diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php index a6b83cf..af99f09 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php @@ -28,6 +28,8 @@ * "mode", * "content", * "hidden", + * "layout_id", + * "layout_settings", * } * ) */ @@ -278,12 +280,34 @@ public function buildMultiple(array $entities) { 'display' => $this, ); \Drupal::moduleHandler()->alter('entity_display_build', $build_list[$id], $context); + $this->applyLayout($build_list[$id]); } return $build_list; } /** + * Applies the layout to the build. + * + * @param array $build + * Render array. + */ + protected function applyLayout(array &$build) { + if ($fields = $this->getFields($build)) { + $regions = []; + foreach ($fields as $name => $field) { + // Move the field from the top-level of $build into a region-specific + // section. + // @todo Ideally the array structure would remain unchanged, see + // https://www.drupal.org/node/2846393. + $regions[$field['region']][$name] = $build[$name]; + unset($build[$name]); + } + $build['layout'] = $this->getLayout()->build($regions); + } + } + + /** * {@inheritdoc} */ public function getPluginCollections() { diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php index b6c73e5..d851ce1 100644 --- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php +++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php @@ -4,13 +4,15 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Entity\Display\EntityDisplayWithLayoutInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Entity\Display\EntityDisplayInterface; +use Drupal\Core\Layout\LayoutInterface; /** * Provides a common base class for entity view and form displays. */ -abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDisplayInterface { +abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDisplayInterface, EntityDisplayWithLayoutInterface { /** * The 'mode' for runtime EntityDisplay objects used to render entities with @@ -79,6 +81,20 @@ protected $hidden = array(); /** + * The layout ID. + * + * @var string + */ + protected $layout_id = 'layout_onecol'; + + /** + * The layout settings. + * + * @var array + */ + protected $layout_settings = []; + + /** * The original view or form mode that was requested (case of view/form modes * being configured to fall back to the 'default' display). * @@ -264,6 +280,15 @@ public function preSave(EntityStorageInterface $storage, $update = TRUE) { ksort($this->content); ksort($this->hidden); parent::preSave($storage, $update); + + // @fixme + // Ensure the plugin configuration is updated. Once layouts are no longer + // stored as third party settings, this will be handled by the code in + // \Drupal\Core\Config\Entity\ConfigEntityBase::preSave() that handles + // \Drupal\Core\Entity\EntityWithPluginCollectionInterface. + if ($this->getLayoutId()) { + $this->setLayout($this->getLayout()); + } } /** @@ -313,6 +338,24 @@ public function calculateDependencies() { $mode_entity = $this->entityManager()->getStorage('entity_' . $this->displayContext . '_mode')->load($target_entity_type->id() . '.' . $this->mode); $this->addDependency('config', $mode_entity->getConfigDependencyName()); } + + // @todo Remove once https://www.drupal.org/node/2821191 is resolved. + // This can be called during uninstallation, so check for a valid ID first. + if ($this->getLayoutId()) { + /** @var \Drupal\Core\Layout\LayoutInterface $layout */ + $layout = $this->getLayout(); + $definition = $layout->getPluginDefinition(); + + if (!in_array($definition->getProvider(), ['core', 'component'])) { + $this->addDependency('module', $definition->getProvider()); + } + if ($config_dependencies = $definition->getConfigDependencies()) { + $this->addDependencies($config_dependencies); + } + if ($layout_dependencies = $layout->calculateDependencies()) { + $this->addDependencies($layout_dependencies); + } + } return $this; } @@ -373,6 +416,8 @@ public function setComponent($name, array $options = array()) { // Ensure we always have an empty settings and array. $options += ['settings' => [], 'third_party_settings' => []]; + // Ensure a region is set. + $options += ['region' => $this->getDefaultRegion()]; $this->content[$name] = $options; unset($this->hidden[$name]); @@ -424,7 +469,7 @@ protected function getFieldDefinition($field_name) { */ protected function getFieldDefinitions() { if (!isset($this->fieldDefinitions)) { - $definitions = \Drupal::entityManager()->getFieldDefinitions($this->targetEntityType, $this->bundle); + $definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->targetEntityType, $this->bundle); // For "official" view modes and form modes, ignore fields whose // definition states they should not be displayed. if ($this->mode !== static::CUSTOM_MODE) { @@ -545,13 +590,130 @@ protected function getPluginRemovedDependencies(array $plugin_dependencies, arra } /** + * Gets the fields that need to be processed. + * + * @param array $build + * A renderable array representing the entity content or form. + * + * @return array + * An array of configurable fields present in the build. + */ + protected function getFields(array $build) { + $components = $this->getComponents(); + + // Ignore any extra fields from the list of field definitions. Field + // definitions can have a non-configurable display, but all extra fields are + // always displayed. + $field_definitions = array_diff_key( + $this->getFieldDefinitions(), + \Drupal::service('entity_field.manager')->getExtraFields($this->getTargetEntityTypeId(), $this->getTargetBundle()) + ); + + $fields_to_exclude = array_filter($field_definitions, function (FieldDefinitionInterface $field_definition) { + // Remove fields with a non-configurable display. + return !$field_definition->isDisplayConfigurable($this->displayContext); + }); + $components = array_diff_key($components, $fields_to_exclude); + + // Only include fields present in the build. + $components = array_intersect_key($components, $build); + + return $components; + } + + /** * Gets the default region. * * @return string * The default region for this display. */ - protected function getDefaultRegion() { - return 'content'; + public function getDefaultRegion() { + return $this->getLayoutDefinition($this->getLayoutId())->getDefaultRegion(); + } + + /** + * Gets a layout definition. + * + * @param string $layout_id + * The layout ID. + * + * @return \Drupal\Core\Layout\LayoutDefinition + * The layout definition. + */ + protected function getLayoutDefinition($layout_id) { + return \Drupal::service('plugin.manager.core.layout')->getDefinition($layout_id); + } + + /** + * {@inheritdoc} + */ + public function getLayoutId() { + return $this->layout_id; + } + + /** + * {@inheritdoc} + */ + public function getLayoutSettings() { + return $this->layout_settings; + } + + /** + * {@inheritdoc} + */ + public function setLayoutId($layout_id, array $layout_settings = []) { + if ($this->getLayoutId() !== $layout_id) { + // @todo Devise a mechanism for mapping old regions to new ones in + // https://www.drupal.org/node/2796877. + $layout_definition = $this->getLayoutDefinition($layout_id); + $new_region = $layout_definition->getDefaultRegion(); + $layout_regions = $layout_definition->getRegions(); + foreach ($this->getComponents() as $name => $component) { + if (isset($component['region']) && !isset($layout_regions[$component['region']])) { + $component['region'] = $new_region; + $this->setComponent($name, $component); + } + } + } + $this->layout_id = $layout_id; + // Instantiate the plugin and consult it for the updated plugin + // configuration. Once layouts are no longer stored as third party settings, + // this will be handled by the code in + // \Drupal\Core\Config\Entity\ConfigEntityBase::set() that handles + // \Drupal\Core\Entity\EntityWithPluginCollectionInterface. + $layout_settings = $this->doGetLayout($layout_id, $layout_settings)->getConfiguration(); + $this->layout_settings = $layout_settings; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setLayout(LayoutInterface $layout) { + $this->setLayoutId($layout->getPluginId(), $layout->getConfiguration()); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLayout() { + return $this->doGetLayout($this->getLayoutId(), $this->getLayoutSettings()); + } + + /** + * Gets the layout plugin. + * + * @param string $layout_id + * A layout plugin ID. + * @param array $layout_settings + * An array of settings. + * + * @return \Drupal\Core\Layout\LayoutInterface + * The layout plugin. + */ + protected function doGetLayout($layout_id, array $layout_settings) { + return \Drupal::service('plugin.manager.core.layout')->createInstance($layout_id, $layout_settings); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index 1b3e6c0..007b2cb 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -401,9 +401,13 @@ public function viewField(FieldItemListInterface $items, $display_options = arra $output = array(); $build = $display->build($entity); + $field_component = $display->getComponent($field_name); if (isset($build[$field_name])) { $output = $build[$field_name]; } + elseif (isset($field_component['region']) && isset($build['layout'][$field_component['region']][$field_name])) { + $output = $build['layout'][$field_component['region']][$field_name]; + } return $output; } diff --git a/core/lib/Drupal/Core/Layout/Annotation/Layout.php b/core/lib/Drupal/Core/Layout/Annotation/Layout.php index 1cb5ff0..4f30834 100644 --- a/core/lib/Drupal/Core/Layout/Annotation/Layout.php +++ b/core/lib/Drupal/Core/Layout/Annotation/Layout.php @@ -72,9 +72,9 @@ class Layout extends Plugin { /** * The template file to render this layout (relative to the 'path' given). * - * If specified, then the layout_discovery module will register the template - * with hook_theme() and the module or theme registering this layout does not - * need to do it. + * If specified, then the system module will register the template with + * hook_theme() and the module or theme registering this layout does not need + * to do it. * * @var string optional * diff --git a/core/lib/Drupal/Core/Layout/LayoutDefault.php b/core/lib/Drupal/Core/Layout/LayoutDefault.php index 8f2370d..11e9d68 100644 --- a/core/lib/Drupal/Core/Layout/LayoutDefault.php +++ b/core/lib/Drupal/Core/Layout/LayoutDefault.php @@ -34,7 +34,9 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * {@inheritdoc} */ public function build(array $regions) { - $build = array_intersect_key($regions, $this->pluginDefinition->getRegions()); + // Ensure $build only contains defined regions and in that order. + $defined_regions = array_fill_keys($this->getPluginDefinition()->getRegionNames(), []); + $build = array_intersect_key(array_replace($defined_regions, $regions), $defined_regions); $build['#settings'] = $this->getConfiguration(); $build['#layout'] = $this->pluginDefinition; $build['#theme'] = $this->pluginDefinition->getThemeHook(); diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml index e0232cf..a67c230 100644 --- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml +++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml @@ -3,6 +3,7 @@ status: true dependencies: module: - aggregator + - system id: aggregator_feed.aggregator_feed.default targetEntityType: aggregator_feed bundle: aggregator_feed @@ -36,3 +37,5 @@ content: label: inline hidden: more_link: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml index 5e5e468..e4ffd92 100644 --- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml +++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml @@ -5,6 +5,7 @@ dependencies: - core.entity_view_mode.aggregator_feed.summary module: - aggregator + - system id: aggregator_feed.aggregator_feed.summary targetEntityType: aggregator_feed bundle: aggregator_feed @@ -22,3 +23,5 @@ hidden: feed_icon: true image: true link: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml index 8e29395..610747a 100644 --- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml +++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml @@ -5,6 +5,7 @@ dependencies: - core.entity_view_mode.aggregator_item.summary module: - aggregator + - system id: aggregator_item.aggregator_item.summary targetEntityType: aggregator_item bundle: aggregator_item @@ -18,3 +19,5 @@ hidden: description: true feed: true link: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml b/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml index 58aba45..acc9af9 100644 --- a/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml +++ b/core/modules/book/config/optional/core.entity_form_display.node.book.default.yml @@ -5,6 +5,7 @@ dependencies: - field.field.node.book.body - node.type.book module: + - system - text id: node.book.default targetEntityType: node @@ -58,3 +59,5 @@ content: placeholder: '' third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/book/config/optional/core.entity_view_display.node.book.default.yml b/core/modules/book/config/optional/core.entity_view_display.node.book.default.yml index d6ef64d..51ee89e 100644 --- a/core/modules/book/config/optional/core.entity_view_display.node.book.default.yml +++ b/core/modules/book/config/optional/core.entity_view_display.node.book.default.yml @@ -5,6 +5,7 @@ dependencies: - field.field.node.book.body - node.type.book module: + - system - text - user id: node.book.default @@ -23,3 +24,5 @@ content: weight: 101 region: content hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/book/config/optional/core.entity_view_display.node.book.teaser.yml b/core/modules/book/config/optional/core.entity_view_display.node.book.teaser.yml index 77a62c3..9dbed1e 100644 --- a/core/modules/book/config/optional/core.entity_view_display.node.book.teaser.yml +++ b/core/modules/book/config/optional/core.entity_view_display.node.book.teaser.yml @@ -6,6 +6,7 @@ dependencies: - field.field.node.book.body - node.type.book module: + - system - text - user id: node.book.teaser @@ -25,3 +26,5 @@ content: weight: 101 region: content hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php index da4481a..51dee12 100644 --- a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php +++ b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php @@ -193,6 +193,7 @@ public function testEntityFormatter() {
Body

Hello, world!

+ '; $renderer->renderRoot($build[0]); $this->assertEqual($build[0]['#markup'], 'default | ' . $this->referencedEntity->label() . $expected_rendered_name_field_1 . $expected_rendered_body_field_1, sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter)); @@ -209,6 +210,7 @@ public function testEntityFormatter() {
Body

Hello, unsaved world!

+ '; $renderer->renderRoot($build[1]); diff --git a/core/modules/field_layout/config/schema/field_layout.schema.yml b/core/modules/field_layout/config/schema/field_layout.schema.yml deleted file mode 100644 index 185fb4e..0000000 --- a/core/modules/field_layout/config/schema/field_layout.schema.yml +++ /dev/null @@ -1,16 +0,0 @@ -core.entity_view_display.*.*.*.third_party.field_layout: - type: field_layout.third_party_settings - -core.entity_form_display.*.*.*.third_party.field_layout: - type: field_layout.third_party_settings - -field_layout.third_party_settings: - type: mapping - label: 'Per-view-mode field layout settings' - mapping: - id: - type: string - label: 'Layout ID' - settings: - type: layout_plugin.settings.[%parent.id] - label: 'Layout settings' diff --git a/core/modules/field_layout/field_layout.info.yml b/core/modules/field_layout/field_layout.info.yml deleted file mode 100644 index 237f18d..0000000 --- a/core/modules/field_layout/field_layout.info.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: 'Field Layout' -type: module -description: 'Adds layout capabilities to the Field UI.' -package: Core (Experimental) -version: VERSION -core: 8.x -dependencies: - - layout_discovery diff --git a/core/modules/field_layout/field_layout.install b/core/modules/field_layout/field_layout.install deleted file mode 100644 index 5956bf1..0000000 --- a/core/modules/field_layout/field_layout.install +++ /dev/null @@ -1,42 +0,0 @@ -ensureLayout()->save(); - }; - array_map($entity_save, EntityViewDisplay::loadMultiple()); - array_map($entity_save, EntityFormDisplay::loadMultiple()); - - // Invalidate the render cache since all content will now have a layout. - Cache::invalidateTags(['rendered']); -} - -/** - * Implements hook_uninstall(). - */ -function field_layout_uninstall() { - // Reset each entity display to use the one-column layout to best approximate - // the absence of layouts. - $entity_save = function (EntityDisplayWithLayoutInterface $entity) { - $entity->setLayoutId('layout_onecol')->save(); - }; - array_map($entity_save, EntityViewDisplay::loadMultiple()); - array_map($entity_save, EntityFormDisplay::loadMultiple()); - - // Invalidate the render cache since all content will no longer have a layout. - Cache::invalidateTags(['rendered']); -} diff --git a/core/modules/field_layout/field_layout.libraries.yml b/core/modules/field_layout/field_layout.libraries.yml deleted file mode 100644 index aafe7ee..0000000 --- a/core/modules/field_layout/field_layout.libraries.yml +++ /dev/null @@ -1,5 +0,0 @@ -drupal.layout.twocol: - version: VERSION - css: - layout: - layouts/twocol/twocol.layout.css: {} diff --git a/core/modules/field_layout/field_layout.module b/core/modules/field_layout/field_layout.module deleted file mode 100644 index 5a5fa11..0000000 --- a/core/modules/field_layout/field_layout.module +++ /dev/null @@ -1,69 +0,0 @@ -' . t('About') . ''; - $output .= '

' . t('The Field Layout module allows you to arrange fields into regions on forms and displays of entities such as nodes and users.') . '

'; - $output .= '

' . t('For more information, see the online documentation for the Field Layout module.', [':field-layout-documentation' => 'https://www.drupal.org/documentation/modules/field_layout']) . '

'; - return $output; - } -} - -/** - * Implements hook_entity_type_alter(). - */ -function field_layout_entity_type_alter(array &$entity_types) { - /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */ - $entity_types['entity_view_display']->setClass(FieldLayoutEntityViewDisplay::class); - $entity_types['entity_form_display']->setClass(FieldLayoutEntityFormDisplay::class); - - // The form classes are only needed when Field UI is installed. - if (\Drupal::moduleHandler()->moduleExists('field_ui')) { - $entity_types['entity_view_display']->setFormClass('edit', FieldLayoutEntityViewDisplayEditForm::class); - $entity_types['entity_form_display']->setFormClass('edit', FieldLayoutEntityFormDisplayEditForm::class); - } -} - -/** - * Implements hook_entity_view_alter(). - */ -function field_layout_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) { - if ($display instanceof EntityDisplayWithLayoutInterface) { - \Drupal::classResolver()->getInstanceFromDefinition(FieldLayoutBuilder::class) - ->buildView($build, $display); - } -} - -/** - * Implements hook_form_alter(). - */ -function field_layout_form_alter(&$form, FormStateInterface $form_state, $form_id) { - $form_object = $form_state->getFormObject(); - if ($form_object instanceof ContentEntityFormInterface && $display = $form_object->getFormDisplay($form_state)) { - if ($display instanceof EntityDisplayWithLayoutInterface) { - \Drupal::classResolver()->getInstanceFromDefinition(FieldLayoutBuilder::class) - ->buildForm($form, $display); - } - } -} diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php deleted file mode 100644 index 49da6a9..0000000 --- a/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php +++ /dev/null @@ -1,164 +0,0 @@ -getDefinition($layout_id); - } - - /** - * Implements \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface::getLayoutId(). - */ - public function getLayoutId() { - return $this->getThirdPartySetting('field_layout', 'id'); - } - - /** - * Implements \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface::getLayoutSettings(). - */ - public function getLayoutSettings() { - return $this->getThirdPartySetting('field_layout', 'settings', []); - } - - /** - * Implements \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface::setLayoutId(). - */ - public function setLayoutId($layout_id, array $layout_settings = []) { - if ($this->getLayoutId() !== $layout_id) { - // @todo Devise a mechanism for mapping old regions to new ones in - // https://www.drupal.org/node/2796877. - $layout_definition = $this->getLayoutDefinition($layout_id); - $new_region = $layout_definition->getDefaultRegion(); - $layout_regions = $layout_definition->getRegions(); - foreach ($this->getComponents() as $name => $component) { - if (isset($component['region']) && !isset($layout_regions[$component['region']])) { - $component['region'] = $new_region; - $this->setComponent($name, $component); - } - } - } - $this->setThirdPartySetting('field_layout', 'id', $layout_id); - // Instantiate the plugin and consult it for the updated plugin - // configuration. Once layouts are no longer stored as third party settings, - // this will be handled by the code in - // \Drupal\Core\Config\Entity\ConfigEntityBase::set() that handles - // \Drupal\Core\Entity\EntityWithPluginCollectionInterface. - $layout_settings = $this->doGetLayout($layout_id, $layout_settings)->getConfiguration(); - $this->setThirdPartySetting('field_layout', 'settings', $layout_settings); - return $this; - } - - /** - * Implements \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface::setLayout(). - */ - public function setLayout(LayoutInterface $layout) { - $this->setLayoutId($layout->getPluginId(), $layout->getConfiguration()); - return $this; - } - - /** - * Implements \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface::getLayout(). - */ - public function getLayout() { - return $this->doGetLayout($this->getLayoutId(), $this->getLayoutSettings()); - } - - /** - * Gets the layout plugin. - * - * @param string $layout_id - * A layout plugin ID. - * @param array $layout_settings - * An array of settings. - * - * @return \Drupal\Core\Layout\LayoutInterface - * The layout plugin. - */ - protected function doGetLayout($layout_id, array $layout_settings) { - return \Drupal::service('plugin.manager.core.layout')->createInstance($layout_id, $layout_settings); - } - - /** - * Overrides \Drupal\Core\Entity\EntityDisplayBase::init(). - */ - protected function init() { - $this->ensureLayout(); - parent::init(); - } - - /** - * Overrides \Drupal\Core\Entity\EntityDisplayBase::preSave(). - */ - public function preSave(EntityStorageInterface $storage) { - parent::preSave($storage); - - // Ensure the plugin configuration is updated. Once layouts are no longer - // stored as third party settings, this will be handled by the code in - // \Drupal\Core\Config\Entity\ConfigEntityBase::preSave() that handles - // \Drupal\Core\Entity\EntityWithPluginCollectionInterface. - if ($this->getLayoutId()) { - $this->setLayout($this->getLayout()); - } - } - - /** - * {@inheritdoc} - */ - public function ensureLayout($default_layout_id = 'layout_onecol') { - if (!$this->getLayoutId()) { - $this->setLayoutId($default_layout_id); - } - - return $this; - } - - /** - * Overrides \Drupal\Core\Entity\EntityDisplayBase::calculateDependencies(). - * - * @see \Drupal\Core\Plugin\PluginDependencyTrait::calculatePluginDependencies() - * - * @todo Remove once https://www.drupal.org/node/2821191 is resolved. - */ - public function calculateDependencies() { - parent::calculateDependencies(); - - // This can be called during uninstallation, so check for a valid ID first. - if ($this->getLayoutId()) { - /** @var \Drupal\Core\Layout\LayoutInterface $layout */ - $layout = $this->getLayout(); - $definition = $layout->getPluginDefinition(); - - if (!in_array($definition->getProvider(), ['core', 'component'])) { - $this->addDependency('module', $definition->getProvider()); - } - if ($config_dependencies = $definition->getConfigDependencies()) { - $this->addDependencies($config_dependencies); - } - if ($layout_dependencies = $layout->calculateDependencies()) { - $this->addDependencies($layout_dependencies); - } - } - } - -} diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php deleted file mode 100644 index c58938e..0000000 --- a/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php +++ /dev/null @@ -1,24 +0,0 @@ -getLayoutDefinition($this->getLayoutId())->getDefaultRegion(); - } - -} diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php deleted file mode 100644 index 4f0c274..0000000 --- a/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php +++ /dev/null @@ -1,24 +0,0 @@ -getLayoutDefinition($this->getLayoutId())->getDefaultRegion(); - } - -} diff --git a/core/modules/field_layout/src/FieldLayoutBuilder.php b/core/modules/field_layout/src/FieldLayoutBuilder.php deleted file mode 100644 index 8ef5d04..0000000 --- a/core/modules/field_layout/src/FieldLayoutBuilder.php +++ /dev/null @@ -1,153 +0,0 @@ -layoutPluginManager = $layout_plugin_manager; - $this->entityFieldManager = $entity_field_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('plugin.manager.core.layout'), - $container->get('entity_field.manager') - ); - } - - /** - * Applies the layout to an entity build. - * - * @param array $build - * A renderable array representing the entity content or form. - * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display - * The entity display holding the display options configured for the entity - * components. - */ - public function buildView(array &$build, EntityDisplayWithLayoutInterface $display) { - $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE); - if ($layout_definition && $fields = $this->getFields($build, $display, 'view')) { - // Add the regions to the $build in the correct order. - $regions = array_fill_keys($layout_definition->getRegionNames(), []); - - foreach ($fields as $name => $field) { - // Move the field from the top-level of $build into a region-specific - // section. - // @todo Ideally the array structure would remain unchanged, see - // https://www.drupal.org/node/2846393. - $regions[$field['region']][$name] = $build[$name]; - unset($build[$name]); - } - // Ensure this will not conflict with any existing array elements by - // prefixing with an underscore. - $build['_field_layout'] = $display->getLayout()->build($regions); - } - } - - /** - * Applies the layout to an entity form. - * - * @param array $build - * A renderable array representing the entity content or form. - * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display - * The entity display holding the display options configured for the entity - * components. - */ - public function buildForm(array &$build, EntityDisplayWithLayoutInterface $display) { - $layout_definition = $this->layoutPluginManager->getDefinition($display->getLayoutId(), FALSE); - if ($layout_definition && $fields = $this->getFields($build, $display, 'form')) { - $fill = []; - $fill['#process'][] = '\Drupal\Core\Render\Element\RenderElement::processGroup'; - $fill['#pre_render'][] = '\Drupal\Core\Render\Element\RenderElement::preRenderGroup'; - // Add the regions to the $build in the correct order. - $regions = array_fill_keys($layout_definition->getRegionNames(), $fill); - - foreach ($fields as $name => $field) { - // As this is a form, #group can be used to relocate the fields. This - // avoids breaking hook_form_alter() implementations by not actually - // moving the field in the form structure. If a #group is already set, - // do not overwrite it. - if (!isset($build[$name]['#group'])) { - $build[$name]['#group'] = $field['region']; - } - } - // Ensure this will not conflict with any existing array elements by - // prefixing with an underscore. - $build['_field_layout'] = $display->getLayout()->build($regions); - } - } - - /** - * Gets the fields that need to be processed. - * - * @param array $build - * A renderable array representing the entity content or form. - * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $display - * The entity display holding the display options configured for the entity - * components. - * @param string $display_context - * The display context, either 'form' or 'view'. - * - * @return array - * An array of configurable fields present in the build. - */ - protected function getFields(array $build, EntityDisplayWithLayoutInterface $display, $display_context) { - $components = $display->getComponents(); - - // Ignore any extra fields from the list of field definitions. Field - // definitions can have a non-configurable display, but all extra fields are - // always displayed. - $field_definitions = array_diff_key( - $this->entityFieldManager->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle()), - $this->entityFieldManager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle()) - ); - - $fields_to_exclude = array_filter($field_definitions, function (FieldDefinitionInterface $field_definition) use ($display_context) { - // Remove fields with a non-configurable display. - return !$field_definition->isDisplayConfigurable($display_context); - }); - $components = array_diff_key($components, $fields_to_exclude); - - // Only include fields present in the build. - $components = array_intersect_key($components, $build); - - return $components; - } - -} diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php deleted file mode 100644 index 043e5c7..0000000 --- a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php +++ /dev/null @@ -1,177 +0,0 @@ -layoutPluginManager->getDefinition($this->getEntity()->getLayoutId()); - foreach ($layout_definition->getRegions() as $name => $region) { - $regions[$name] = [ - 'title' => $region['label'], - 'message' => $this->t('No field is displayed.'), - ]; - } - - $regions['hidden'] = [ - 'title' => $this->t('Disabled', [], ['context' => 'Plural']), - 'message' => $this->t('No field is hidden.'), - ]; - - return $regions; - } - - /** - * Overrides \Drupal\field_ui\Form\EntityDisplayFormBase::form(). - */ - public function form(array $form, FormStateInterface $form_state) { - $form = parent::form($form, $form_state); - - $form['field_layouts'] = [ - '#type' => 'details', - '#title' => $this->t('Layout settings'), - ]; - - $layout_plugin = $this->getLayout($this->getEntity(), $form_state); - - $form['field_layouts']['field_layout'] = [ - '#type' => 'select', - '#title' => $this->t('Select a layout'), - '#options' => $this->layoutPluginManager->getLayoutOptions(), - '#default_value' => $layout_plugin->getPluginId(), - '#ajax' => [ - 'callback' => '::settingsAjax', - 'wrapper' => 'field-layout-settings-wrapper', - 'trigger_as' => ['name' => 'field_layout_change'], - ], - ]; - $form['field_layouts']['submit'] = [ - '#type' => 'submit', - '#name' => 'field_layout_change', - '#value' => $this->t('Change layout'), - '#submit' => ['::settingsAjaxSubmit'], - '#attributes' => ['class' => ['js-hide']], - '#ajax' => [ - 'callback' => '::settingsAjax', - 'wrapper' => 'field-layout-settings-wrapper', - ], - ]; - - $form['field_layouts']['settings_wrapper'] = [ - '#type' => 'container', - '#id' => 'field-layout-settings-wrapper', - '#tree' => TRUE, - ]; - - if ($layout_plugin instanceof PluginFormInterface) { - $form['field_layouts']['settings_wrapper']['layout_settings'] = []; - $subform_state = SubformState::createForSubform($form['field_layouts']['settings_wrapper']['layout_settings'], $form, $form_state); - $form['field_layouts']['settings_wrapper']['layout_settings'] = $layout_plugin->buildConfigurationForm($form['field_layouts']['settings_wrapper']['layout_settings'], $subform_state); - } - - return $form; - } - - /** - * Gets the layout plugin for the currently selected field layout. - * - * @param \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface $entity - * The current form entity. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return \Drupal\Core\Layout\LayoutInterface - * The layout plugin. - */ - protected function getLayout(EntityDisplayWithLayoutInterface $entity, FormStateInterface $form_state) { - if (!$layout_plugin = $form_state->get('layout_plugin')) { - $stored_layout_id = $entity->getLayoutId(); - // Use selected layout if it exists, falling back to the stored layout. - $layout_id = $form_state->getValue('field_layout', $stored_layout_id); - // If the current layout is the stored layout, use the stored layout - // settings. Otherwise leave the settings empty. - $layout_settings = $layout_id === $stored_layout_id ? $entity->getLayoutSettings() : []; - - $layout_plugin = $this->layoutPluginManager->createInstance($layout_id, $layout_settings); - $form_state->set('layout_plugin', $layout_plugin); - } - return $layout_plugin; - } - - /** - * Ajax callback for the field layout settings form. - */ - public static function settingsAjax($form, FormStateInterface $form_state) { - return $form['field_layouts']['settings_wrapper']; - } - - /** - * Submit handler for the non-JS case. - */ - public function settingsAjaxSubmit($form, FormStateInterface $form_state) { - $form_state->set('layout_plugin', NULL); - $form_state->setRebuild(); - } - - /** - * Overrides \Drupal\field_ui\Form\EntityDisplayFormBase::validateForm(). - */ - public function validateForm(array &$form, FormStateInterface $form_state) { - parent::validateForm($form, $form_state); - - $layout_plugin = $this->getLayout($this->getEntity(), $form_state); - if ($layout_plugin instanceof PluginFormInterface) { - $subform_state = SubformState::createForSubform($form['field_layouts']['settings_wrapper']['layout_settings'], $form, $form_state); - $layout_plugin->validateConfigurationForm($form['field_layouts']['settings_wrapper']['layout_settings'], $subform_state); - } - } - - /** - * Overrides \Drupal\field_ui\Form\EntityDisplayFormBase::submitForm(). - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - parent::submitForm($form, $form_state); - - $entity = $this->getEntity(); - $layout_plugin = $this->getLayout($entity, $form_state); - if ($layout_plugin instanceof PluginFormInterface) { - $subform_state = SubformState::createForSubform($form['field_layouts']['settings_wrapper']['layout_settings'], $form, $form_state); - $layout_plugin->submitConfigurationForm($form['field_layouts']['settings_wrapper']['layout_settings'], $subform_state); - } - - $entity->setLayout($layout_plugin); - } - - /** - * Gets the form entity. - * - * @return \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface - * The current form entity. - */ - abstract public function getEntity(); - -} diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php b/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php deleted file mode 100644 index c42a9d8..0000000 --- a/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php +++ /dev/null @@ -1,44 +0,0 @@ -layoutPluginManager = $layout_plugin_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('plugin.manager.field.field_type'), - $container->get('plugin.manager.field.widget'), - $container->get('plugin.manager.core.layout') - ); - } - -} diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php b/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php deleted file mode 100644 index 92bfbde..0000000 --- a/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php +++ /dev/null @@ -1,44 +0,0 @@ -layoutPluginManager = $layout_plugin_manager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('plugin.manager.field.field_type'), - $container->get('plugin.manager.field.formatter'), - $container->get('plugin.manager.core.layout') - ); - } - -} diff --git a/core/modules/field_layout/tests/src/Functional/FieldLayoutTest.php b/core/modules/field_layout/tests/src/Functional/FieldLayoutTest.php deleted file mode 100644 index 683ccd2..0000000 --- a/core/modules/field_layout/tests/src/Functional/FieldLayoutTest.php +++ /dev/null @@ -1,76 +0,0 @@ -createContentType([ - 'type' => 'article', - ]); - $this->createNode([ - 'type' => 'article', - 'title' => 'The node title', - 'body' => [[ - 'value' => 'The node body', - ]], - ]); - $this->drupalLogin($this->drupalCreateUser([ - 'access administration pages', - 'administer content types', - 'administer nodes', - 'administer node fields', - 'administer node display', - 'administer node form display', - 'view the administration theme', - ])); - } - - /** - * Tests an entity type that has fields shown by default. - */ - public function testNodeView() { - // By default, the one-column layout is used. - $this->drupalGet('node/1'); - $this->assertSession()->elementExists('css', '.layout--onecol'); - $this->assertSession()->elementExists('css', '.layout-region--content .field--name-body'); - - $this->drupalGet('admin/structure/types/manage/article/display'); - $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles()); - $this->assertSession()->optionExists('fields[body][region]', 'content'); - } - - /** - * Gets the region titles on the page. - * - * @return string[] - * An array of region titles. - */ - protected function getRegionTitles() { - $region_titles = []; - $region_title_elements = $this->getSession()->getPage()->findAll('css', '.region-title td'); - /** @var \Behat\Mink\Element\NodeElement[] $region_title_elements */ - foreach ($region_title_elements as $region_title_element) { - $region_titles[] = $region_title_element->getText(); - } - return $region_titles; - } - -} diff --git a/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php b/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php deleted file mode 100644 index 1336a70..0000000 --- a/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php +++ /dev/null @@ -1,266 +0,0 @@ - 'The name for this entity', - 'field_test_text' => [[ - 'value' => 'The field test text value', - ]], - ]); - $entity->save(); - $this->drupalLogin($this->drupalCreateUser([ - 'access administration pages', - 'view test entity', - 'administer entity_test content', - 'administer entity_test fields', - 'administer entity_test display', - 'administer entity_test form display', - 'view the administration theme', - ])); - } - - /** - * Tests that layouts are unique per view mode. - */ - public function testEntityViewModes() { - // By default, the field is not visible. - $this->drupalGet('entity_test/1/test'); - $this->assertSession()->elementNotExists('css', '.layout-region--content .field--name-field-test-text'); - $this->drupalGet('entity_test/1'); - $this->assertSession()->elementNotExists('css', '.layout-region--content .field--name-field-test-text'); - - // Change the layout for the "test" view mode. See - // core.entity_view_mode.entity_test.test.yml. - $this->drupalGet('entity_test/structure/entity_test/display'); - $this->click('#edit-modes'); - $this->getSession()->getPage()->checkField('display_modes_custom[test]'); - $this->submitForm([], 'Save'); - $this->clickLink('configure them'); - $this->getSession()->getPage()->pressButton('Show row weights'); - $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->submitForm([], 'Save'); - - // Each view mode has a different layout. - $this->drupalGet('entity_test/1/test'); - $this->assertSession()->elementExists('css', '.layout-region--content .field--name-field-test-text'); - $this->drupalGet('entity_test/1'); - $this->assertSession()->elementNotExists('css', '.layout-region--content .field--name-field-test-text'); - } - - /** - * Tests the use of field layout for entity form displays. - */ - public function testEntityForm() { - // By default, the one-column layout is used. - $this->drupalGet('entity_test/manage/1/edit'); - $this->assertFieldInRegion('field_test_text[0][value]', 'content'); - - // The one-column layout is in use. - $this->drupalGet('entity_test/structure/entity_test/form-display'); - $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles()); - - // Switch the layout to two columns. - $this->click('#edit-field-layouts'); - $this->getSession()->getPage()->selectFieldOption('field_layout', 'layout_twocol'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->submitForm([], 'Save'); - - // The field is moved to the default region for the new layout. - $this->assertSession()->pageTextContains('Your settings have been saved.'); - $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); - - $this->drupalGet('entity_test/manage/1/edit'); - // No fields are visible, and the regions don't display when empty. - $this->assertFieldInRegion('field_test_text[0][value]', 'left'); - $this->assertSession()->elementExists('css', '.layout-region--left .field--name-field-test-text'); - - // After a refresh the new regions are still there. - $this->drupalGet('entity_test/structure/entity_test/form-display'); - $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); - - // Drag the field to the right region. - $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text'); - $right_region_row = $this->getSession()->getPage()->find('css', '.region-right-message'); - $field_test_text_row->find('css', '.handle')->dragTo($right_region_row); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->submitForm([], 'Save'); - $this->assertSession()->pageTextContains('Your settings have been saved.'); - - // The new layout is used. - $this->drupalGet('entity_test/manage/1/edit'); - $this->assertSession()->elementExists('css', '.layout-region--right .field--name-field-test-text'); - $this->assertFieldInRegion('field_test_text[0][value]', 'right'); - - // Move the field to the right region without tabledrag. - $this->drupalGet('entity_test/structure/entity_test/form-display'); - $this->getSession()->getPage()->pressButton('Show row weights'); - $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->submitForm([], 'Save'); - $this->assertSession()->pageTextContains('Your settings have been saved.'); - - // The updated region is used. - $this->drupalGet('entity_test/manage/1/edit'); - $this->assertFieldInRegion('field_test_text[0][value]', 'right'); - - // The layout is still in use without Field UI. - $this->container->get('module_installer')->uninstall(['field_ui']); - $this->drupalGet('entity_test/manage/1/edit'); - $this->assertFieldInRegion('field_test_text[0][value]', 'right'); - } - - /** - * Tests the use of field layout for entity view displays. - */ - public function testEntityView() { - // The one-column layout is in use. - $this->drupalGet('entity_test/structure/entity_test/display'); - $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles()); - - // Switch the layout to two columns. - $this->click('#edit-field-layouts'); - $this->getSession()->getPage()->selectFieldOption('field_layout', 'layout_twocol'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->submitForm([], 'Save'); - - $this->assertSession()->pageTextContains('Your settings have been saved.'); - $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); - - $this->drupalGet('entity_test/1'); - // No fields are visible, and the regions don't display when empty. - $this->assertSession()->elementNotExists('css', '.layout--twocol'); - $this->assertSession()->elementNotExists('css', '.layout-region'); - $this->assertSession()->elementNotExists('css', '.field--name-field-test-text'); - - // After a refresh the new regions are still there. - $this->drupalGet('entity_test/structure/entity_test/display'); - $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); - - // Drag the field to the left region. - $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); - $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text'); - $left_region_row = $this->getSession()->getPage()->find('css', '.region-left-message'); - $field_test_text_row->find('css', '.handle')->dragTo($left_region_row); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->assertFalse($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); - $this->submitForm([], 'Save'); - $this->assertSession()->pageTextContains('Your settings have been saved.'); - - // The new layout is used. - $this->drupalGet('entity_test/1'); - $this->assertSession()->elementExists('css', '.layout--twocol'); - $this->assertSession()->elementExists('css', '.layout-region--left .field--name-field-test-text'); - - // Move the field to the right region without tabledrag. - $this->drupalGet('entity_test/structure/entity_test/display'); - $this->getSession()->getPage()->pressButton('Show row weights'); - $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->submitForm([], 'Save'); - $this->assertSession()->pageTextContains('Your settings have been saved.'); - - // The updated region is used. - $this->drupalGet('entity_test/1'); - $this->assertSession()->elementExists('css', '.layout-region--right .field--name-field-test-text'); - - // The layout is still in use without Field UI. - $this->container->get('module_installer')->uninstall(['field_ui']); - $this->drupalGet('entity_test/1'); - $this->assertSession()->elementExists('css', '.layout--twocol'); - $this->assertSession()->elementExists('css', '.layout-region--right .field--name-field-test-text'); - } - - /** - * Tests layout plugins with forms. - */ - public function testLayoutForms() { - $this->drupalGet('entity_test/structure/entity_test/display'); - // Switch to a field layout with settings. - $this->click('#edit-field-layouts'); - - // Test switching between layouts with and without forms. - $this->getSession()->getPage()->selectFieldOption('field_layout', 'layout_test_plugin'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->assertSession()->fieldExists('settings_wrapper[layout_settings][setting_1]'); - - $this->getSession()->getPage()->selectFieldOption('field_layout', 'layout_test_2col'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->assertSession()->fieldNotExists('settings_wrapper[layout_settings][setting_1]'); - - $this->getSession()->getPage()->selectFieldOption('field_layout', 'layout_test_plugin'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->assertSession()->fieldExists('settings_wrapper[layout_settings][setting_1]'); - - // Move the test field to the content region. - $this->getSession()->getPage()->pressButton('Show row weights'); - $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content'); - $this->assertSession()->assertWaitOnAjaxRequest(); - $this->submitForm([], 'Save'); - - $this->drupalGet('entity_test/1'); - $this->assertSession()->pageTextContains('Blah: Default'); - - // Update the field layout settings. - $this->drupalGet('entity_test/structure/entity_test/display'); - $this->click('#edit-field-layouts'); - $this->getSession()->getPage()->fillField('settings_wrapper[layout_settings][setting_1]', 'Test text'); - $this->submitForm([], 'Save'); - - $this->drupalGet('entity_test/1'); - $this->assertSession()->pageTextContains('Blah: Test text'); - } - - /** - * Gets the region titles on the page. - * - * @return string[] - * An array of region titles. - */ - protected function getRegionTitles() { - $region_titles = []; - $region_title_elements = $this->getSession()->getPage()->findAll('css', '.region-title td'); - /** @var \Behat\Mink\Element\NodeElement[] $region_title_elements */ - foreach ($region_title_elements as $region_title_element) { - $region_titles[] = $region_title_element->getText(); - } - return $region_titles; - } - - /** - * Asserts that a field exists in a given region. - * - * @param string $field_selector - * The field selector, one of field id|name|label|value. - * @param string $region_name - * The machine name of the region. - */ - protected function assertFieldInRegion($field_selector, $region_name) { - $region_element = $this->getSession()->getPage()->find('css', ".layout-region--$region_name"); - $this->assertNotNull($region_element); - $this->assertSession()->fieldExists($field_selector, $region_element); - } - -} diff --git a/core/modules/field_layout/tests/src/Unit/FieldLayoutBuilderTest.php b/core/modules/field_layout/tests/src/Unit/FieldLayoutBuilderTest.php deleted file mode 100644 index 8e6bf95..0000000 --- a/core/modules/field_layout/tests/src/Unit/FieldLayoutBuilderTest.php +++ /dev/null @@ -1,308 +0,0 @@ -pluginDefinition = new LayoutDefinition([ - 'library' => 'field_layout/drupal.layout.twocol', - 'theme_hook' => 'layout__twocol', - 'regions' => [ - 'left' => [ - 'label' => 'Left', - ], - 'right' => [ - 'label' => 'Right', - ], - ], - ]); - $this->layoutPlugin = new LayoutDefault([], 'two_column', $this->pluginDefinition); - - $this->layoutPluginManager = $this->prophesize(LayoutPluginManagerInterface::class); - $this->layoutPluginManager->getDefinition('unknown', FALSE)->willReturn(NULL); - $this->layoutPluginManager->getDefinition('two_column', FALSE)->willReturn($this->pluginDefinition); - - $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class); - - $this->fieldLayoutBuilder = new FieldLayoutBuilder($this->layoutPluginManager->reveal(), $this->entityFieldManager->reveal()); - } - - /** - * @covers ::buildView - * @covers ::getFields - */ - public function testBuildView() { - $definitions = []; - $non_configurable_field_definition = $this->prophesize(FieldDefinitionInterface::class); - $non_configurable_field_definition->isDisplayConfigurable('view')->willReturn(FALSE); - $definitions['non_configurable_field'] = $non_configurable_field_definition->reveal(); - $definitions['non_configurable_field_with_extra_field'] = $non_configurable_field_definition->reveal(); - $this->entityFieldManager->getFieldDefinitions('the_entity_type_id', 'the_entity_type_bundle')->willReturn($definitions); - $extra_fields = []; - $extra_fields['non_configurable_field_with_extra_field'] = [ - 'label' => 'This non-configurable field is also defined in hook_entity_extra_field_info()', - ]; - $this->entityFieldManager->getExtraFields('the_entity_type_id', 'the_entity_type_bundle')->willReturn($extra_fields); - - $build = [ - 'test1' => [ - '#markup' => 'Test1', - ], - 'non_configurable_field' => [ - '#markup' => 'Non-configurable', - ], - 'non_configurable_field_with_extra_field' => [ - '#markup' => 'Non-configurable with extra field', - ], - ]; - - $display = $this->prophesize(EntityDisplayWithLayoutInterface::class); - $display->getTargetEntityTypeId()->willReturn('the_entity_type_id'); - $display->getTargetBundle()->willReturn('the_entity_type_bundle'); - $display->getLayout()->willReturn($this->layoutPlugin); - $display->getLayoutId()->willReturn('two_column'); - $display->getLayoutSettings()->willReturn([]); - $display->getComponents()->willReturn([ - 'test1' => [ - 'region' => 'right', - ], - 'non_configurable_field' => [ - 'region' => 'left', - ], - 'non_configurable_field_with_extra_field' => [ - 'region' => 'left', - ], - ]); - - $expected = [ - 'non_configurable_field' => [ - '#markup' => 'Non-configurable', - ], - '_field_layout' => [ - 'left' => [ - 'non_configurable_field_with_extra_field' => [ - '#markup' => 'Non-configurable with extra field', - ], - ], - 'right' => [ - 'test1' => [ - '#markup' => 'Test1', - ], - ], - '#settings' => [], - '#layout' => $this->pluginDefinition, - '#theme' => 'layout__twocol', - '#attached' => [ - 'library' => [ - 'field_layout/drupal.layout.twocol', - ], - ], - ], - ]; - $this->fieldLayoutBuilder->buildView($build, $display->reveal()); - $this->assertEquals($expected, $build); - $this->assertSame($expected, $build); - } - - /** - * @covers ::buildForm - * @covers ::getFields - */ - public function testBuildForm() { - $definitions = []; - $non_configurable_field_definition = $this->prophesize(FieldDefinitionInterface::class); - $non_configurable_field_definition->isDisplayConfigurable('form')->willReturn(FALSE); - $definitions['non_configurable_field'] = $non_configurable_field_definition->reveal(); - $this->entityFieldManager->getFieldDefinitions('the_entity_type_id', 'the_entity_type_bundle')->willReturn($definitions); - $this->entityFieldManager->getExtraFields('the_entity_type_id', 'the_entity_type_bundle')->willReturn([]); - - $build = [ - 'test1' => [ - '#markup' => 'Test1', - ], - 'test2' => [ - '#markup' => 'Test2', - '#group' => 'existing_group', - ], - 'field_layout' => [ - '#markup' => 'Field created through the UI happens to be named "Layout"', - ], - 'non_configurable_field' => [ - '#markup' => 'Non-configurable', - ], - ]; - - $display = $this->prophesize(EntityDisplayWithLayoutInterface::class); - $display->getTargetEntityTypeId()->willReturn('the_entity_type_id'); - $display->getTargetBundle()->willReturn('the_entity_type_bundle'); - $display->getLayout()->willReturn($this->layoutPlugin); - $display->getLayoutId()->willReturn('two_column'); - $display->getLayoutSettings()->willReturn([]); - $display->getComponents()->willReturn([ - 'test1' => [ - 'region' => 'right', - ], - 'test2' => [ - 'region' => 'left', - ], - 'field_layout' => [ - 'region' => 'right', - ], - 'non_configurable_field' => [ - 'region' => 'left', - ], - ]); - - $expected = [ - 'test1' => [ - '#markup' => 'Test1', - '#group' => 'right', - ], - 'test2' => [ - '#markup' => 'Test2', - '#group' => 'existing_group', - ], - 'field_layout' => [ - '#markup' => 'Field created through the UI happens to be named "Layout"', - '#group' => 'right', - ], - 'non_configurable_field' => [ - '#markup' => 'Non-configurable', - ], - '_field_layout' => [ - 'left' => [ - '#process' => ['\Drupal\Core\Render\Element\RenderElement::processGroup'], - '#pre_render' => ['\Drupal\Core\Render\Element\RenderElement::preRenderGroup'], - ], - 'right' => [ - '#process' => ['\Drupal\Core\Render\Element\RenderElement::processGroup'], - '#pre_render' => ['\Drupal\Core\Render\Element\RenderElement::preRenderGroup'], - ], - '#settings' => [], - '#layout' => $this->pluginDefinition, - '#theme' => 'layout__twocol', - '#attached' => [ - 'library' => [ - 'field_layout/drupal.layout.twocol', - ], - ], - ], - ]; - $this->fieldLayoutBuilder->buildForm($build, $display->reveal()); - $this->assertEquals($expected, $build); - $this->assertSame($expected, $build); - } - - /** - * @covers ::buildForm - */ - public function testBuildFormEmpty() { - $definitions = []; - $non_configurable_field_definition = $this->prophesize(FieldDefinitionInterface::class); - $non_configurable_field_definition->isDisplayConfigurable('form')->willReturn(FALSE); - $definitions['non_configurable_field'] = $non_configurable_field_definition->reveal(); - $this->entityFieldManager->getFieldDefinitions('the_entity_type_id', 'the_entity_type_bundle')->willReturn($definitions); - $this->entityFieldManager->getExtraFields('the_entity_type_id', 'the_entity_type_bundle')->willReturn([]); - - $build = [ - 'non_configurable_field' => [ - '#markup' => 'Non-configurable', - ], - ]; - - $display = $this->prophesize(EntityDisplayWithLayoutInterface::class); - $display->getTargetEntityTypeId()->willReturn('the_entity_type_id'); - $display->getTargetBundle()->willReturn('the_entity_type_bundle'); - $display->getLayout()->willReturn($this->layoutPlugin); - $display->getLayoutId()->willReturn('two_column'); - $display->getLayoutSettings()->willReturn([]); - $display->getComponents()->willReturn([ - 'test1' => [ - 'region' => 'right', - ], - 'non_configurable_field' => [ - 'region' => 'left', - ], - ]); - - $expected = [ - 'non_configurable_field' => [ - '#markup' => 'Non-configurable', - ], - ]; - $this->fieldLayoutBuilder->buildForm($build, $display->reveal()); - $this->assertSame($expected, $build); - } - - /** - * @covers ::buildForm - */ - public function testBuildFormNoLayout() { - $this->entityFieldManager->getFieldDefinitions(Argument::any(), Argument::any())->shouldNotBeCalled(); - - $build = [ - 'test1' => [ - '#markup' => 'Test1', - ], - ]; - - $display = $this->prophesize(EntityDisplayWithLayoutInterface::class); - $display->getLayoutId()->willReturn('unknown'); - $display->getLayoutSettings()->willReturn([]); - $display->getComponents()->shouldNotBeCalled(); - - $expected = [ - 'test1' => [ - '#markup' => 'Test1', - ], - ]; - $this->fieldLayoutBuilder->buildForm($build, $display->reveal()); - $this->assertSame($expected, $build); - } - -} diff --git a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php index 8908f65..f4d3213 100644 --- a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php +++ b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php @@ -11,7 +11,11 @@ use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Field\PluginSettingsInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\SubformState; +use Drupal\Core\Layout\LayoutPluginManagerInterface; +use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Entity\Display\EntityDisplayWithLayoutInterface; use Drupal\field_ui\Element\FieldUiTable; use Drupal\field_ui\FieldUI; @@ -35,6 +39,13 @@ protected $pluginManager; /** + * The layout plugin manager. + * + * @var \Drupal\Core\Layout\LayoutPluginManagerInterface + */ + protected $layoutPluginManager; + + /** * A list of field types. * * @var array @@ -55,10 +66,13 @@ * The field type manager. * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager * The widget or formatter plugin manager. + * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_plugin_manager + * The layout plugin manager. */ - public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager) { + public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, LayoutPluginManagerInterface $layout_plugin_manager) { $this->fieldTypes = $field_type_manager->getDefinitions(); $this->pluginManager = $plugin_manager; + $this->layoutPluginManager = $layout_plugin_manager; } /** @@ -90,17 +104,22 @@ public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entit * @endcode */ public function getRegions() { - return array( - 'content' => array( - 'title' => $this->t('Content'), - 'invisible' => TRUE, - 'message' => $this->t('No field is displayed.') - ), - 'hidden' => array( - 'title' => $this->t('Disabled', array(), array('context' => 'Plural')), - 'message' => $this->t('No field is hidden.') - ), - ); + $regions = []; + + $layout_definition = $this->layoutPluginManager->getDefinition($this->getEntity()->getLayoutId()); + foreach ($layout_definition->getRegions() as $name => $region) { + $regions[$name] = [ + 'title' => $region['label'], + 'message' => $this->t('No field is displayed.'), + ]; + } + + $regions['hidden'] = [ + 'title' => $this->t('Disabled', [], ['context' => 'Plural']), + 'message' => $this->t('No field is hidden.'), + ]; + + return $regions; } /** @@ -244,6 +263,48 @@ public function form(array $form, FormStateInterface $form_state) { '#attributes' => array('class' => array('visually-hidden')) ); + $form['layouts'] = [ + '#type' => 'details', + '#title' => $this->t('Layout settings'), + ]; + + $layout_plugin = $this->getLayout($this->getEntity(), $form_state); + + $form['layouts']['layout'] = [ + '#type' => 'select', + '#title' => $this->t('Select a layout'), + '#options' => $this->layoutPluginManager->getLayoutOptions(), + '#default_value' => $layout_plugin->getPluginId(), + '#ajax' => [ + 'callback' => '::settingsAjax', + 'wrapper' => 'layout-settings-wrapper', + 'trigger_as' => ['name' => 'layout_change'], + ], + ]; + $form['layouts']['submit'] = [ + '#type' => 'submit', + '#name' => 'layout_change', + '#value' => $this->t('Change layout'), + '#submit' => ['::settingsAjaxSubmit'], + '#attributes' => ['class' => ['js-hide']], + '#ajax' => [ + 'callback' => '::settingsAjax', + 'wrapper' => 'layout-settings-wrapper', + ], + ]; + + $form['layouts']['settings_wrapper'] = [ + '#type' => 'container', + '#id' => 'layout-settings-wrapper', + '#tree' => TRUE, + ]; + + if ($layout_plugin instanceof PluginFormInterface) { + $form['layouts']['settings_wrapper']['layout_settings'] = []; + $subform_state = SubformState::createForSubform($form['layouts']['settings_wrapper']['layout_settings'], $form, $form_state); + $form['layouts']['settings_wrapper']['layout_settings'] = $layout_plugin->buildConfigurationForm($form['layouts']['settings_wrapper']['layout_settings'], $subform_state); + } + $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', @@ -508,6 +569,19 @@ protected function buildExtraFieldRow($field_id, $extra_field) { /** * {@inheritdoc} */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + + $layout_plugin = $this->getLayout($this->getEntity(), $form_state); + if ($layout_plugin instanceof PluginFormInterface) { + $subform_state = SubformState::createForSubform($form['layouts']['settings_wrapper']['layout_settings'], $form, $form_state); + $layout_plugin->validateConfigurationForm($form['layouts']['settings_wrapper']['layout_settings'], $subform_state); + } + } + + /** + * {@inheritdoc} + */ public function submitForm(array &$form, FormStateInterface $form_state) { // If the main "Save" button was submitted while a field settings subform // was being edited, update the new incoming settings when rebuilding the @@ -517,6 +591,15 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } parent::submitForm($form, $form_state); + + $layout_plugin = $this->getLayout($this->entity, $form_state); + if ($layout_plugin instanceof PluginFormInterface) { + $subform_state = SubformState::createForSubform($form['layouts']['settings_wrapper']['layout_settings'], $form, $form_state); + $layout_plugin->submitConfigurationForm($form['layouts']['settings_wrapper']['layout_settings'], $subform_state); + } + + $this->entity->setLayout($layout_plugin); + $form_values = $form_state->getValues(); // Handle the 'display modes' checkboxes if present. @@ -684,6 +767,47 @@ public function multistepAjax($form, FormStateInterface $form_state) { } /** + * Gets the layout plugin for the currently selected layout. + * + * @param \Drupal\Core\Entity\Display\EntityDisplayWithLayoutInterface $entity + * The current form entity. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Layout\LayoutInterface + * The layout plugin. + */ + protected function getLayout(EntityDisplayWithLayoutInterface $entity, FormStateInterface $form_state) { + if (!$layout_plugin = $form_state->get('layout_plugin')) { + $stored_layout_id = $entity->getLayoutId(); + // Use selected layout if it exists, falling back to the stored layout. + $layout_id = $form_state->getValue('layout', $stored_layout_id); + // If the current layout is the stored layout, use the stored layout + // settings. Otherwise leave the settings empty. + $layout_settings = $layout_id === $stored_layout_id ? $entity->getLayoutSettings() : []; + + $layout_plugin = $this->layoutPluginManager->createInstance($layout_id, $layout_settings); + $form_state->set('layout_plugin', $layout_plugin); + } + return $layout_plugin; + } + + /** + * Ajax callback for the layout settings form. + */ + public static function settingsAjax($form, FormStateInterface $form_state) { + return $form['layouts']['settings_wrapper']; + } + + /** + * Submit handler for the non-JS case. + */ + public function settingsAjaxSubmit($form, FormStateInterface $form_state) { + $form_state->set('layout_plugin', NULL); + $form_state->setRebuild(); + } + + /** * Performs pre-render tasks on field_ui_table elements. * * @param array $elements diff --git a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php index af8e2ed..59520a7 100644 --- a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php +++ b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php @@ -25,7 +25,8 @@ class EntityFormDisplayEditForm extends EntityDisplayFormBase { public static function create(ContainerInterface $container) { return new static( $container->get('plugin.manager.field.field_type'), - $container->get('plugin.manager.field.widget') + $container->get('plugin.manager.field.widget'), + $container->get('plugin.manager.core.layout') ); } diff --git a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php index 174726f..066cdf8 100644 --- a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php +++ b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php @@ -25,7 +25,8 @@ class EntityViewDisplayEditForm extends EntityDisplayFormBase { public static function create(ContainerInterface $container) { return new static( $container->get('plugin.manager.field.field_type'), - $container->get('plugin.manager.field.formatter') + $container->get('plugin.manager.field.formatter'), + $container->get('plugin.manager.core.layout') ); } diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php index 319e8df..850505b 100644 --- a/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php +++ b/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php @@ -15,7 +15,7 @@ class EntityDisplayTest extends JavascriptTestBase { /** * {@inheritdoc} */ - public static $modules = ['field_ui', 'entity_test']; + public static $modules = ['field_ui', 'entity_test', 'field_layout_test', 'layout_test']; /** * {@inheritdoc} @@ -49,6 +49,7 @@ public function testEntityForm() { $this->assertSession()->fieldExists('field_test_text[0][value]'); $this->drupalGet('entity_test/structure/entity_test/form-display'); + $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles()); $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected()); $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'hidden'); @@ -61,6 +62,61 @@ public function testEntityForm() { $this->drupalGet('entity_test/manage/1/edit'); $this->assertSession()->fieldNotExists('field_test_text[0][value]'); + + // Restore the field to the default region. + $this->drupalGet('entity_test/structure/entity_test/form-display'); + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + + // Switch the layout to two columns. + $this->click('#edit-layouts'); + $this->getSession()->getPage()->selectFieldOption('layout', 'layout_twocol'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + + // The field is moved to the default region for the new layout. + $this->assertSession()->pageTextContains('Your settings have been saved.'); + $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); + + $this->drupalGet('entity_test/manage/1/edit'); + // No fields are visible, and the regions don't display when empty. + $this->assertFieldInRegion('field_test_text[0][value]', 'left'); + $this->assertSession()->elementExists('css', '.layout-region--left .field--name-field-test-text'); + + // After a refresh the new regions are still there. + $this->drupalGet('entity_test/structure/entity_test/form-display'); + $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); + + // Drag the field to the right region. + $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text'); + $right_region_row = $this->getSession()->getPage()->find('css', '.region-right-message'); + $field_test_text_row->find('css', '.handle')->dragTo($right_region_row); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Your settings have been saved.'); + + // The new layout is used. + $this->drupalGet('entity_test/manage/1/edit'); + $this->assertSession()->elementExists('css', '.layout-region--right .field--name-field-test-text'); + $this->assertFieldInRegion('field_test_text[0][value]', 'right'); + + // Move the field to the right region without tabledrag. + $this->drupalGet('entity_test/structure/entity_test/form-display'); + $this->getSession()->getPage()->pressButton('Show row weights'); + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Your settings have been saved.'); + + // The updated region is used. + $this->drupalGet('entity_test/manage/1/edit'); + $this->assertFieldInRegion('field_test_text[0][value]', 'right'); + + // The layout is still in use without Field UI. + $this->container->get('module_installer')->uninstall(['field_ui']); + $this->drupalGet('entity_test/manage/1/edit'); + $this->assertFieldInRegion('field_test_text[0][value]', 'right'); } /** @@ -71,6 +127,8 @@ public function testEntityView() { $this->assertSession()->elementNotExists('css', '.field--name-field-test-text'); $this->drupalGet('entity_test/structure/entity_test/display'); + // The one-column layout is in use. + $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles()); $this->assertSession()->elementExists('css', '.region-content-message.region-empty'); $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); @@ -84,6 +142,163 @@ public function testEntityView() { $this->drupalGet('entity_test/1'); $this->assertSession()->elementExists('css', '.field--name-field-test-text'); + + // Restore the field to the hidden region. + $this->drupalGet('entity_test/structure/entity_test/display'); + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'hidden'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + + // Switch the layout to two columns. + $this->click('#edit-layouts'); + $this->getSession()->getPage()->selectFieldOption('layout', 'layout_twocol'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + + $this->assertSession()->pageTextContains('Your settings have been saved.'); + $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); + + $this->drupalGet('entity_test/1'); + // No fields are visible, and the regions don't display when empty. + $this->assertSession()->elementNotExists('css', '.layout--twocol'); + $this->assertSession()->elementNotExists('css', '.layout-region'); + $this->assertSession()->elementNotExists('css', '.field--name-field-test-text'); + + // After a refresh the new regions are still there. + $this->drupalGet('entity_test/structure/entity_test/display'); + $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles()); + + // Drag the field to the left region. + $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); + $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text'); + $left_region_row = $this->getSession()->getPage()->find('css', '.region-left-message'); + $field_test_text_row->find('css', '.handle')->dragTo($left_region_row); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertFalse($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Your settings have been saved.'); + + // The new layout is used. + $this->drupalGet('entity_test/1'); + $this->assertSession()->elementExists('css', '.layout--twocol'); + $this->assertSession()->elementExists('css', '.layout-region--left .field--name-field-test-text'); + + // Move the field to the right region without tabledrag. + $this->drupalGet('entity_test/structure/entity_test/display'); + $this->getSession()->getPage()->pressButton('Show row weights'); + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Your settings have been saved.'); + + // The updated region is used. + $this->drupalGet('entity_test/1'); + $this->assertSession()->elementExists('css', '.layout-region--right .field--name-field-test-text'); + + // The layout is still in use without Field UI. + $this->container->get('module_installer')->uninstall(['field_ui']); + $this->drupalGet('entity_test/1'); + $this->assertSession()->elementExists('css', '.layout--twocol'); + $this->assertSession()->elementExists('css', '.layout-region--right .field--name-field-test-text'); + } + + /** + * Tests that layouts are unique per view mode. + */ + public function testEntityViewModes() { + // By default, the field is not visible. + $this->drupalGet('entity_test/1/test'); + $this->assertSession()->elementNotExists('css', '.layout-region--content .field--name-field-test-text'); + $this->drupalGet('entity_test/1'); + $this->assertSession()->elementNotExists('css', '.layout-region--content .field--name-field-test-text'); + + // Change the layout for the "test" view mode. See + // core.entity_view_mode.entity_test.test.yml. + $this->drupalGet('entity_test/structure/entity_test/display'); + $this->click('#edit-modes'); + $this->getSession()->getPage()->checkField('display_modes_custom[test]'); + $this->submitForm([], 'Save'); + $this->clickLink('configure them'); + $this->getSession()->getPage()->pressButton('Show row weights'); + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + + // Each view mode has a different layout. + $this->drupalGet('entity_test/1/test'); + $this->assertSession()->elementExists('css', '.layout-region--content .field--name-field-test-text'); + $this->drupalGet('entity_test/1'); + $this->assertSession()->elementNotExists('css', '.layout-region--content .field--name-field-test-text'); + } + + /** + * Tests layout plugins with forms. + */ + public function testLayoutForms() { + $this->drupalGet('entity_test/structure/entity_test/display'); + // Switch to a layout with settings. + $this->click('#edit-layouts'); + + // Test switching between layouts with and without forms. + $this->getSession()->getPage()->selectFieldOption('layout', 'layout_test_plugin'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->fieldExists('settings_wrapper[layout_settings][setting_1]'); + + $this->getSession()->getPage()->selectFieldOption('layout', 'layout_test_2col'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->fieldNotExists('settings_wrapper[layout_settings][setting_1]'); + + $this->getSession()->getPage()->selectFieldOption('layout', 'layout_test_plugin'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->fieldExists('settings_wrapper[layout_settings][setting_1]'); + + // Move the test field to the content region. + $this->getSession()->getPage()->pressButton('Show row weights'); + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->submitForm([], 'Save'); + + $this->drupalGet('entity_test/1'); + $this->assertSession()->pageTextContains('Blah: Default'); + + // Update the layout settings. + $this->drupalGet('entity_test/structure/entity_test/display'); + $this->click('#edit-layouts'); + $this->getSession()->getPage()->fillField('settings_wrapper[layout_settings][setting_1]', 'Test text'); + $this->submitForm([], 'Save'); + + $this->drupalGet('entity_test/1'); + $this->assertSession()->pageTextContains('Blah: Test text'); + } + + /** + * Gets the region titles on the page. + * + * @return string[] + * An array of region titles. + */ + protected function getRegionTitles() { + $region_titles = []; + $region_title_elements = $this->getSession()->getPage()->findAll('css', '.region-title td'); + /** @var \Behat\Mink\Element\NodeElement[] $region_title_elements */ + foreach ($region_title_elements as $region_title_element) { + $region_titles[] = $region_title_element->getText(); + } + return $region_titles; + } + + /** + * Asserts that a field exists in a given region. + * + * @param string $field_selector + * The field selector, one of field id|name|label|value. + * @param string $region_name + * The machine name of the region. + */ + protected function assertFieldInRegion($field_selector, $region_name) { + $region_element = $this->getSession()->getPage()->find('css', ".layout-region--$region_name"); + $this->assertNotNull($region_element); + $this->assertSession()->fieldExists($field_selector, $region_element); } } diff --git a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php index d060163..f877c59 100644 --- a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php +++ b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php @@ -107,7 +107,7 @@ public function testEntityDisplayCRUD() { $new_display->save(); $new_display = EntityViewDisplay::load($new_display->id()); $dependencies = $new_display->calculateDependencies()->getDependencies(); - $this->assertEqual(array('config' => array('core.entity_view_mode.entity_test.other_view_mode'), 'module' => array('entity_test')), $dependencies); + $this->assertEqual(array('config' => array('core.entity_view_mode.entity_test.other_view_mode'), 'module' => array('entity_test', 'system')), $dependencies); $this->assertEqual($new_display->getTargetEntityTypeId(), $display->getTargetEntityTypeId()); $this->assertEqual($new_display->getTargetBundle(), $display->getTargetBundle()); $this->assertEqual($new_display->getMode(), 'other_view_mode'); @@ -266,7 +266,7 @@ public function testFieldComponent() { // Check that the display has dependencies on the field and the module that // provides the formatter. $dependencies = $display->calculateDependencies()->getDependencies(); - $this->assertEqual(array('config' => array('field.field.entity_test.entity_test.test_field'), 'module' => array('entity_test', 'field_test')), $dependencies); + $this->assertEqual(array('config' => array('field.field.entity_test.entity_test.test_field'), 'module' => array('entity_test', 'field_test', 'system')), $dependencies); } /** diff --git a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php index 6f770d6..0aa4d77 100644 --- a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php +++ b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php @@ -20,7 +20,7 @@ class EntityFormDisplayTest extends KernelTestBase { * * @var string[] */ - public static $modules = ['field_ui', 'field', 'entity_test', 'field_test', 'user', 'text']; + public static $modules = ['field_ui', 'field', 'entity_test', 'field_test', 'user', 'text', 'system']; protected function setUp() { parent::setUp(); diff --git a/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml index 4738bbb..0414a86 100644 --- a/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml +++ b/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml @@ -5,6 +5,7 @@ dependencies: - comment.type.comment_forum - field.field.comment.comment_forum.comment_body module: + - system - text id: comment.comment_forum.default targetEntityType: comment @@ -31,3 +32,5 @@ content: placeholder: '' third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml index 6773d32..66fc7b6 100644 --- a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml +++ b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml @@ -8,6 +8,7 @@ dependencies: - node.type.forum module: - comment + - system - text id: node.forum.default targetEntityType: node @@ -73,3 +74,5 @@ content: placeholder: '' third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml index 50df98a..9eabb94 100644 --- a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml +++ b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml @@ -5,6 +5,7 @@ dependencies: - field.field.taxonomy_term.forums.forum_container - taxonomy.vocabulary.forums module: + - system - text id: taxonomy_term.forums.default targetEntityType: taxonomy_term @@ -27,3 +28,5 @@ content: third_party_settings: { } hidden: forum_container: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml b/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml index befeba8..34c73a8 100644 --- a/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml +++ b/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml @@ -5,6 +5,7 @@ dependencies: - comment.type.comment_forum - field.field.comment.comment_forum.comment_body module: + - system - text id: comment.comment_forum.default targetEntityType: comment @@ -22,3 +23,5 @@ content: weight: 100 region: content hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml index f3e8c5c..23ff070 100644 --- a/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml +++ b/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml @@ -9,6 +9,7 @@ dependencies: - node.type.forum module: - comment + - system - text - user id: node.forum.default @@ -44,3 +45,5 @@ content: link: true third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml b/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml index 7b174f4..ee2f177 100644 --- a/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml +++ b/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml @@ -8,6 +8,7 @@ dependencies: - field.field.node.forum.taxonomy_forums - node.type.forum module: + - system - text - user id: node.forum.teaser @@ -36,3 +37,5 @@ content: third_party_settings: { } hidden: comment_forum: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml b/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml index b326039..ff8fa58 100644 --- a/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml +++ b/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml @@ -5,6 +5,7 @@ dependencies: - field.field.taxonomy_term.forums.forum_container - taxonomy.vocabulary.forums module: + - system - text id: taxonomy_term.forums.default targetEntityType: taxonomy_term @@ -20,3 +21,5 @@ content: label: above hidden: forum_container: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/layout_discovery/layout_discovery.info.yml b/core/modules/layout_discovery/layout_discovery.info.yml deleted file mode 100644 index a9a4139..0000000 --- a/core/modules/layout_discovery/layout_discovery.info.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: 'Layout Discovery' -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_discovery/layout_discovery.install b/core/modules/layout_discovery/layout_discovery.install deleted file mode 100644 index e6c99e0..0000000 --- a/core/modules/layout_discovery/layout_discovery.install +++ /dev/null @@ -1,22 +0,0 @@ -moduleExists('layout_plugin')) { - $requirements['layout_discovery'] = [ - 'description' => t('Layout Discovery cannot be installed because the Layout Plugin module is installed and incompatible.'), - 'severity' => REQUIREMENT_ERROR, - ]; - } - } - return $requirements; -} diff --git a/core/modules/layout_discovery/layout_discovery.module b/core/modules/layout_discovery/layout_discovery.module deleted file mode 100644 index 3cffee9..0000000 --- a/core/modules/layout_discovery/layout_discovery.module +++ /dev/null @@ -1,39 +0,0 @@ -' . t('About') . ''; - $output .= '

' . t('Layout Discovery 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 Discovery module.', [':layout-discovery-documentation' => 'https://www.drupal.org/node/2619128']) . '

'; - return $output; - } -} - -/** - * Implements hook_theme(). - */ -function layout_discovery_theme() { - return \Drupal::service('plugin.manager.core.layout')->getThemeImplementations(); -} - -/** - * Prepares variables for layout templates. - * - * @param array &$variables - * An associative array containing: - * - content: An associative array containing the properties of the element. - * Properties used: #settings, #layout. - */ -function template_preprocess_layout(&$variables) { - $variables['settings'] = isset($variables['content']['#settings']) ? $variables['content']['#settings'] : []; - $variables['layout'] = isset($variables['content']['#layout']) ? $variables['content']['#layout'] : []; -} diff --git a/core/modules/layout_discovery/layout_discovery.services.yml b/core/modules/layout_discovery/layout_discovery.services.yml deleted file mode 100644 index 1e24db4..0000000 --- a/core/modules/layout_discovery/layout_discovery.services.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - plugin.manager.core.layout: - class: Drupal\Core\Layout\LayoutPluginManager - arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@theme_handler'] diff --git a/core/modules/node/src/Tests/NodeEntityViewModeAlterTest.php b/core/modules/node/src/Tests/NodeEntityViewModeAlterTest.php index 25ae6b6..275a6b3 100644 --- a/core/modules/node/src/Tests/NodeEntityViewModeAlterTest.php +++ b/core/modules/node/src/Tests/NodeEntityViewModeAlterTest.php @@ -43,7 +43,7 @@ function testNodeViewModeChange() { $this->assertNoText('Data that should appear only in the body for the node.', 'Body text not present'); // Test that the correct build mode has been set. - $build = $this->drupalBuildEntityView($node); + $build = $this->drupalBuildEntityView($node, 'full', NULL, FALSE, FALSE); $this->assertEqual($build['#view_mode'], 'teaser', 'The view mode has correctly been set to teaser.'); } diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml index ff5f0ec..c760b97 100644 --- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml +++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml @@ -5,6 +5,7 @@ dependencies: - field.field.node.options_install_test.body - node.type.options_install_test module: + - system - text id: node.options_install_test.default targetEntityType: node @@ -59,3 +60,5 @@ content: third_party_settings: { } hidden: { } third_party_settings: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml index aaea1cb..22051df 100644 --- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml +++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml @@ -5,6 +5,7 @@ dependencies: - field.field.node.options_install_test.body - node.type.options_install_test module: + - system - text - user id: node.options_install_test.default @@ -25,3 +26,5 @@ content: hidden: langcode: true third_party_settings: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml index 6e79af9..f8ad011 100644 --- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml +++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml @@ -6,6 +6,7 @@ dependencies: - field.field.node.options_install_test.body - node.type.options_install_test module: + - system - text - user id: node.options_install_test.teaser @@ -27,3 +28,5 @@ content: hidden: langcode: true third_party_settings: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index 3108ee1..82d449a 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -5,6 +5,7 @@ * Enables semantically enriched output for Drupal sites in the form of RDFa. */ +use Drupal\comment\CommentInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Template\Attribute; use Drupal\rdf\Entity\RdfMapping; @@ -502,7 +503,19 @@ function rdf_preprocess_comment(&$variables) { $variables['title_attributes']['property'] = $title_mapping['properties']; $variables['title_attributes']['datatype'] = ''; } +} +/** + * Implements hook_entity_display_build_alter(). + */ +function rdf_entity_display_build_alter(&$build, $context) { + if (!($context['entity'] instanceof CommentInterface)) { + return; + } + + $comment = $context['entity']; + $rdf_metadata_attributes = []; + $mapping = rdf_get_mapping('comment', $comment->bundle()); // Annotates the parent relationship between the current comment and the node // it belongs to. If available, the parent comment is also annotated. // @todo When comments are turned into fields, this should be changed. @@ -514,7 +527,7 @@ function rdf_preprocess_comment(&$variables) { // The parent entity URI is precomputed as part of the rdf_data so that it // can be cached as part of the entity. $parent_entity_attributes['resource'] = $comment->rdf_data['entity_uri']; - $variables['rdf_metadata_attributes'][] = $parent_entity_attributes; + $rdf_metadata_attributes[] = $parent_entity_attributes; // Adds the relation to parent comment, if it exists. if ($comment->hasParentComment()) { @@ -522,19 +535,19 @@ function rdf_preprocess_comment(&$variables) { // The parent comment URI is precomputed as part of the rdf_data so that // it can be cached as part of the entity. $parent_comment_attributes['resource'] = $comment->rdf_data['pid_uri']; - $variables['rdf_metadata_attributes'][] = $parent_comment_attributes; + $rdf_metadata_attributes[] = $parent_comment_attributes; } } // Adds RDF metadata markup above comment body if any. - if (!empty($variables['rdf_metadata_attributes']) && isset($variables['content']['comment_body'])) { + if (!empty($rdf_metadata_attributes) && isset($build['comment_body'])) { $rdf_metadata = array( '#theme' => 'rdf_metadata', - '#metadata' => $variables['rdf_metadata_attributes'], + '#metadata' => $rdf_metadata_attributes, ); - if (!empty($variables['content']['comment_body']['#prefix'])) { - $rdf_metadata['#suffix'] = $variables['content']['comment_body']['#prefix']; + if (!empty($build['comment_body']['#prefix'])) { + $rdf_metadata['#suffix'] = $build['comment_body']['#prefix']; } - $variables['content']['comment_body']['#prefix'] = drupal_render($rdf_metadata); + $build['comment_body']['#prefix'] = drupal_render($rdf_metadata); } } diff --git a/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php b/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php index d481291..cee1bbc 100644 --- a/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php +++ b/core/modules/responsive_image/tests/src/Kernel/ResponsiveImageIntegrationTest.php @@ -18,7 +18,7 @@ class ResponsiveImageIntegrationTest extends KernelTestBase { /** * {@inheritdoc} */ - public static $modules = ['responsive_image', 'field', 'image', 'file', 'entity_test', 'breakpoint', 'responsive_image_test_module']; + public static $modules = ['responsive_image', 'field', 'image', 'file', 'entity_test', 'breakpoint', 'responsive_image_test_module', 'system']; /** * Tests integration with entity view display. diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index c3098e1..009ac9d 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -229,11 +229,17 @@ function __construct($test_id = NULL) { * the current content language. * @param bool $reset * (optional) Whether to clear the cache for this entity. + * @param bool $return_fields_only + * (optional) Whether to return the part of the render array containing the + * fields. Defaults to TRUE. + * * @return array + * The render array for the given entity. If $return_full_array is TRUE, + * only the portion within the layout will be returned. * * @see drupal_render() */ - protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = 'full', $langcode = NULL, $reset = FALSE) { + protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = 'full', $langcode = NULL, $reset = FALSE, $return_fields_only = TRUE) { $ensure_fully_built = function(&$elements) use (&$ensure_fully_built) { // If the default values for this element have not been loaded yet, populate // them. @@ -264,6 +270,11 @@ protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = ' $build = $render_controller->view($entity, $view_mode, $langcode); $ensure_fully_built($build); + if ($return_fields_only) { + // By default all fields will be nested in the onecol layout. + return $build['layout']['content']; + } + return $build; } diff --git a/core/modules/field_layout/layouts/onecol/layout--onecol.html.twig b/core/modules/system/layouts/onecol/layout--onecol.html.twig similarity index 54% copy from core/modules/field_layout/layouts/onecol/layout--onecol.html.twig copy to core/modules/system/layouts/onecol/layout--onecol.html.twig index 69fed03..34b58ee 100644 --- a/core/modules/field_layout/layouts/onecol/layout--onecol.html.twig +++ b/core/modules/system/layouts/onecol/layout--onecol.html.twig @@ -10,15 +10,4 @@ * @ingroup themeable */ #} -{% -set classes = [ -'layout--onecol', -] -%} -{% if content %} - -
- {{ content }} -
- -{% endif %} +{{ content }} diff --git a/core/modules/field_layout/layouts/twocol/layout--twocol.html.twig b/core/modules/system/layouts/twocol/layout--twocol.html.twig similarity index 100% copy from core/modules/field_layout/layouts/twocol/layout--twocol.html.twig copy to core/modules/system/layouts/twocol/layout--twocol.html.twig diff --git a/core/modules/field_layout/layouts/twocol/twocol.layout.css b/core/modules/system/layouts/twocol/twocol.layout.css similarity index 100% rename from core/modules/field_layout/layouts/twocol/twocol.layout.css rename to core/modules/system/layouts/twocol/twocol.layout.css diff --git a/core/modules/field_layout/field_layout.layouts.yml b/core/modules/system/system.layouts.yml similarity index 76% rename from core/modules/field_layout/field_layout.layouts.yml rename to core/modules/system/system.layouts.yml index 7f51a69..fcbef29 100644 --- a/core/modules/field_layout/field_layout.layouts.yml +++ b/core/modules/system/system.layouts.yml @@ -1,4 +1,3 @@ -# @todo Move to layout_discovery in https://www.drupal.org/node/2840832. layout_onecol: label: 'One column' path: layouts/onecol @@ -12,7 +11,7 @@ layout_twocol: label: 'Two column' path: layouts/twocol template: layout--twocol - library: field_layout/drupal.layout.twocol + library: system/drupal.layout.twocol category: 'Columns: 2' default_region: left regions: diff --git a/core/modules/system/system.libraries.yml b/core/modules/system/system.libraries.yml index 206ae4f..6e1362d 100644 --- a/core/modules/system/system.libraries.yml +++ b/core/modules/system/system.libraries.yml @@ -77,3 +77,9 @@ drupal.system.date: - core/drupalSettings - core/jquery.once - core/drupal.form + +drupal.layout.twocol: + version: VERSION + css: + layout: + layouts/twocol/twocol.layout.css: {} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 6581e60..6d2d47f 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -224,7 +224,7 @@ function system_theme() { ), 'template' => 'entity-add-list', ), - )); + ), \Drupal::service('plugin.manager.core.layout')->getThemeImplementations()); } /** diff --git a/core/modules/layout_discovery/templates/layout.html.twig b/core/modules/system/templates/layout.html.twig similarity index 100% copy from core/modules/layout_discovery/templates/layout.html.twig copy to core/modules/system/templates/layout.html.twig diff --git a/core/modules/field_layout/tests/modules/field_layout_test/config/schema/field_layout_test.schema.yml b/core/modules/system/tests/modules/field_layout_test/config/schema/field_layout_test.schema.yml similarity index 100% rename from core/modules/field_layout/tests/modules/field_layout_test/config/schema/field_layout_test.schema.yml rename to core/modules/system/tests/modules/field_layout_test/config/schema/field_layout_test.schema.yml diff --git a/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml b/core/modules/system/tests/modules/field_layout_test/field_layout_test.info.yml similarity index 100% rename from core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml rename to core/modules/system/tests/modules/field_layout_test/field_layout_test.info.yml diff --git a/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml b/core/modules/system/tests/modules/field_layout_test/field_layout_test.routing.yml similarity index 100% rename from core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml rename to core/modules/system/tests/modules/field_layout_test/field_layout_test.routing.yml diff --git a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php b/core/modules/system/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php similarity index 100% rename from core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php rename to core/modules/system/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php diff --git a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php b/core/modules/system/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php similarity index 100% rename from core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php rename to core/modules/system/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 90b813f..502db9d 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -379,13 +379,14 @@ function user_user_view(array &$build, UserInterface $account, EntityViewDisplay } /** - * Implements hook_ENTITY_TYPE_view_alter() for user entities. + * Implements hook_entity_display_build_alter(). * * This function adds a default alt tag to the user_picture field to maintain * accessibility. */ -function user_user_view_alter(array &$build, UserInterface $account, EntityViewDisplayInterface $display) { - if (user_picture_enabled() && !empty($build['user_picture'])) { +function user_entity_display_build_alter(&$build, $context) { + $account = $context['entity']; + if ($account instanceof UserInterface && user_picture_enabled() && !empty($build['user_picture'])) { foreach (Element::children($build['user_picture']) as $key) { $item = $build['user_picture'][$key]['#item']; if (!$item->get('alt')->getValue()) { diff --git a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php index 3c38da1..888be83 100644 --- a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php +++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php @@ -236,11 +236,22 @@ protected function buildFields(array $values) { } // Let the display build the render array for the entities. $display_build = $display->buildMultiple($bundle_entities); + $components = $display->getComponents(); // Collect the field render arrays and index them using our internal // row indexes and field IDs. foreach ($display_build as $row_index => $entity_build) { foreach ($display_fields['field_ids'] as $field_id => $field) { - $build[$row_index][$field_id] = !empty($entity_build[$field->definition['field_name']]) ? $entity_build[$field->definition['field_name']] : []; + $field_name = $field->definition['field_name']; + $field_component = isset($components[$field_name]) ? $components[$field_name] : []; + if (!empty($entity_build[$field_name])) { + $build[$row_index][$field_id] = $entity_build[$field_name]; + } + elseif (isset($field_component['region']) && !empty($entity_build['layout'][$field_component['region']][$field_name])) { + $build[$row_index][$field_id] = $entity_build['layout'][$field_component['region']][$field_name]; + } + else { + $build[$row_index][$field_id] = []; + } } } } diff --git a/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml b/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml index 7ccb5b0..7cefbc1 100644 --- a/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml +++ b/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml @@ -5,6 +5,7 @@ dependencies: - block_content.type.basic - field.field.block_content.basic.body module: + - system - text id: block_content.basic.default targetEntityType: block_content @@ -29,3 +30,5 @@ content: placeholder: '' third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml b/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml index 1010be2..481a7c6 100644 --- a/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml +++ b/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml @@ -5,6 +5,7 @@ dependencies: - comment.type.comment - field.field.comment.comment.comment_body module: + - system - text id: comment.comment.default targetEntityType: comment @@ -31,3 +32,5 @@ content: placeholder: '' third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml index c94e36e..3f9d743 100644 --- a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml +++ b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml @@ -12,6 +12,7 @@ dependencies: - comment - image - path + - system - text id: node.article.default targetEntityType: node @@ -91,3 +92,5 @@ content: placeholder: '' third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml index 0b7ffd1..b8fb89d 100644 --- a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml +++ b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml @@ -6,6 +6,7 @@ dependencies: - node.type.page module: - path + - system - text id: node.page.default targetEntityType: node @@ -65,3 +66,5 @@ content: placeholder: '' third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml index 6832229..a1f3371 100644 --- a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml +++ b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml @@ -6,6 +6,7 @@ dependencies: - image.style.thumbnail module: - image + - system - user id: user.user.default targetEntityType: user @@ -33,3 +34,5 @@ content: weight: -1 region: content hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml b/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml index e494882..09da9e0 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml @@ -5,6 +5,7 @@ dependencies: - block_content.type.basic - field.field.block_content.basic.body module: + - system - text id: block_content.basic.default targetEntityType: block_content @@ -19,3 +20,5 @@ content: settings: { } third_party_settings: { } hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml b/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml index 6ae213d..c3e08ab 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml @@ -5,6 +5,7 @@ dependencies: - comment.type.comment - field.field.comment.comment.comment_body module: + - system - text id: comment.comment.default targetEntityType: comment @@ -22,3 +23,5 @@ content: weight: 100 region: content hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml index 5c43252..159219f 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml @@ -12,6 +12,7 @@ dependencies: module: - comment - image + - system - text - user id: node.article.default @@ -58,3 +59,5 @@ content: hidden: field_image: true field_tags: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml index 84660b6..1e02899 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml @@ -9,6 +9,7 @@ dependencies: - field.field.node.article.field_tags - node.type.article module: + - system - user id: node.article.rss targetEntityType: node @@ -23,3 +24,5 @@ hidden: comment: true field_image: true field_tags: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml index 7b96908..912945d 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml @@ -11,6 +11,7 @@ dependencies: - node.type.article module: - image + - system - text - user id: node.article.teaser @@ -50,3 +51,5 @@ hidden: comment: true field_image: true field_tags: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml index 8afd942..eaa319c 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml @@ -5,6 +5,7 @@ dependencies: - field.field.node.page.body - node.type.page module: + - system - text - user id: node.page.default @@ -23,3 +24,5 @@ content: weight: 101 region: content hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml b/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml index bc7a68c..9b8a17c 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml @@ -6,6 +6,7 @@ dependencies: - field.field.node.page.body - node.type.page module: + - system - text - user id: node.page.teaser @@ -25,3 +26,5 @@ content: weight: 101 region: content hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml index 2ff13ad..1a4c8d2 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml @@ -7,6 +7,7 @@ dependencies: - image.style.thumbnail module: - image + - system - user id: user.user.compact targetEntityType: user @@ -24,3 +25,5 @@ content: label: hidden hidden: member_for: true +layout_id: layout_onecol +layout_settings: { } diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml index ef1fdd7..d526a2f 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml @@ -6,6 +6,7 @@ dependencies: - image.style.thumbnail module: - image + - system - user id: user.user.default targetEntityType: user @@ -25,3 +26,5 @@ content: third_party_settings: { } label: hidden hidden: { } +layout_id: layout_onecol +layout_settings: { } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayBaseTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayBaseTest.php index ec1a55c..198f5a1 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayBaseTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayBaseTest.php @@ -132,6 +132,7 @@ public function testOnDependencyRemoval() { 'comment', 'entity_test', 'entity_test_third_party', + 'system', ], ]; $this->assertSame($expected_dependencies, $entity_display->getDependencies()); @@ -146,6 +147,7 @@ public function testOnDependencyRemoval() { $expected_dependencies['module'] = [ 'comment', 'entity_test', + 'system', ]; $this->assertSame($expected_dependencies, $entity_display->getDependencies()); } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php index 11aeede..f9157dd 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php @@ -119,7 +119,7 @@ public function testCopyFormValuesToEntity() { }) ->shouldBeCalled(); - $form_object = new EntityViewDisplayEditForm($this->container->get('plugin.manager.field.field_type'), $this->container->get('plugin.manager.field.formatter')); + $form_object = new EntityViewDisplayEditForm($this->container->get('plugin.manager.field.field_type'), $this->container->get('plugin.manager.field.formatter'), $this->container->get('plugin.manager.core.layout')); $form_object->setEntityManager($this->container->get('entity.manager')); $form_object->setEntity($entity->reveal()); diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewBuilderTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewBuilderTest.php index 0143128..fd9c697 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewBuilderTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewBuilderTest.php @@ -191,7 +191,7 @@ public function testEntityViewBuilderWeight() { $renderer->renderRoot($view); // Check that the weight is respected. - $this->assertEqual($view['label']['#weight'], 20, 'The weight of a display component is respected.'); + $this->assertEqual($view['layout']['content']['label']['#weight'], 20, 'The weight of a display component is respected.'); } /** diff --git a/core/modules/field_layout/tests/src/Kernel/FieldLayoutEntityDisplayTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayTest.php similarity index 62% rename from core/modules/field_layout/tests/src/Kernel/FieldLayoutEntityDisplayTest.php rename to core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayTest.php index 8a7fde9..7bfe5f9 100644 --- a/core/modules/field_layout/tests/src/Kernel/FieldLayoutEntityDisplayTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayTest.php @@ -1,20 +1,95 @@ 'entity_test', + 'field_name' => 'test1', + 'type' => 'string', + ]); + $field_storage->save(); + $field = FieldConfig::create([ + 'field_storage' => $field_storage, + 'label' => $this->randomMachineName(), + 'bundle' => 'entity_test', + ]); + $field->save(); + $entity_test = EntityTest::create([ + 'test1' => 'Test1', + ]); + $entity_display = EntityViewDisplay::create([ + 'targetEntityType' => 'entity_test', + 'bundle' => 'entity_test', + 'mode' => 'default', + 'status' => TRUE, + 'layout_id' => 'layout_twocol', + 'content' => [ + 'test1' => [ + 'type' => 'string', + 'region' => 'right', + 'settings' => [], + 'label' => 'hidden', + 'third_party_settings' => [], + 'weight' => 0, + ], + ], + ]); + $entity_display->save(); + + $expected_attached = [ + 'library' => [ + 'system/drupal.layout.twocol', + ], + ]; + $expected_cache_contexts = [ + 'languages:language_interface', + 'theme', + 'user.permissions', + ]; + $expected_markup = <<<'EOS' +
+
+ +
+ +
+ +
Test1
+ +
+
+ +EOS; + + $build = $entity_display->build($entity_test); + $this->container->get('renderer')->renderRoot($build); + + $this->assertEquals($expected_markup, $build['#markup']); + $this->assertEquals($expected_attached, $build['#attached']); + $this->assertEquals($expected_cache_contexts, $build['#cache']['contexts']); + } /** * @covers ::preSave @@ -22,7 +97,7 @@ class FieldLayoutEntityDisplayTest extends KernelTestBase { */ public function testPreSave() { // Create an entity display with one hidden and one visible field. - $entity_display = FieldLayoutEntityViewDisplay::create([ + $entity_display = EntityViewDisplay::create([ 'targetEntityType' => 'entity_test', 'bundle' => 'entity_test', 'mode' => 'default', @@ -38,12 +113,6 @@ public function testPreSave() { 'langcode' => 'en', 'status' => TRUE, 'dependencies' => [], - 'third_party_settings' => [ - 'field_layout' => [ - 'id' => 'layout_onecol', - 'settings' => [], - ], - ], 'id' => 'entity_test.entity_test.default', 'targetEntityType' => 'entity_test', 'bundle' => 'entity_test', @@ -57,6 +126,8 @@ public function testPreSave() { ], ], 'hidden' => [], + 'layout_id' => 'layout_onecol', + 'layout_settings' => [], ]; $this->assertEntityValues($expected, $entity_display->toArray()); @@ -67,7 +138,7 @@ public function testPreSave() { // The dependencies have been updated. $expected['dependencies']['module'] = [ 'entity_test', - 'field_layout', + 'system', ]; // A third party setting is added by the entity_test module. $expected['third_party_settings']['entity_test'] = ['foo' => 'bar']; @@ -85,11 +156,9 @@ public function testPreSave() { $entity_display->setLayoutId('test_layout_main_and_footer'); // The default settings were added. - $expected['third_party_settings']['field_layout'] = [ - 'id' => 'test_layout_main_and_footer', - 'settings' => [ - 'setting_1' => 'Default', - ], + $expected['layout_id'] = 'test_layout_main_and_footer'; + $expected['layout_settings'] = [ + 'setting_1' => 'Default', ]; // The field was moved to the default region. $expected['content']['foo'] = [ @@ -107,7 +176,6 @@ public function testPreSave() { 'dependency_from_annotation', 'dependency_from_calculateDependencies', 'entity_test', - 'field_layout', 'field_layout_test', ]; $this->assertEntityValues($expected, $entity_display->toArray()); @@ -117,7 +185,7 @@ public function testPreSave() { $entity_display->save(); // The setting overrides the default value. - $expected['third_party_settings']['field_layout']['settings']['setting_1'] = 'foobar'; + $expected['layout_settings']['setting_1'] = 'foobar'; $this->assertEntityValues($expected, $entity_display->toArray()); // Move a field to the non-default region. @@ -137,31 +205,13 @@ public function testPreSave() { // The dependencies have been updated. $expected['dependencies']['module'] = [ 'entity_test', - 'field_layout', 'field_layout_test', ]; // The layout has been updated. - $expected['third_party_settings']['field_layout'] = [ - 'id' => 'test_layout_content_and_footer', - 'settings' => [], - ]; + $expected['layout_id'] = 'test_layout_content_and_footer'; + $expected['layout_settings'] = []; // The field remains in its current region instead of moving to the default. $this->assertEntityValues($expected, $entity_display->toArray()); - - $this->container->get('module_installer')->uninstall(['field_layout']); - - $entity_storage = $this->container->get('entity_type.manager')->getStorage('entity_view_display'); - $entity_display = $entity_storage->load('entity_test.entity_test.default'); - - // The dependencies have been updated. - $expected['dependencies']['module'] = [ - 'entity_test', - ]; - // All field_layout settings were removed. - unset($expected['third_party_settings']['field_layout']); - // The field has returned to the default content region. - $expected['content']['foo']['region'] = 'content'; - $this->assertEntityValues($expected, $entity_display->toArray()); } /** diff --git a/core/modules/layout_discovery/tests/src/Kernel/LayoutTest.php b/core/tests/Drupal/KernelTests/Core/Layout/LayoutTest.php similarity index 94% rename from core/modules/layout_discovery/tests/src/Kernel/LayoutTest.php rename to core/tests/Drupal/KernelTests/Core/Layout/LayoutTest.php index 8602377..998a274 100644 --- a/core/modules/layout_discovery/tests/src/Kernel/LayoutTest.php +++ b/core/tests/Drupal/KernelTests/Core/Layout/LayoutTest.php @@ -1,7 +1,8 @@ prophesize(FormInterface::class); + $form_state->setFormObject($form_object->reveal()); + $form_builder = $this->container->get('form_builder'); $form_builder->prepareForm('the_form_id', $built, $form_state); $form_builder->processForm('the_form_id', $built, $form_state); diff --git a/core/tests/Drupal/Tests/Core/Entity/Display/EntityFormDisplayTest.php b/core/tests/Drupal/Tests/Core/Entity/Display/EntityFormDisplayTest.php new file mode 100644 index 0000000..0d8177e --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Entity/Display/EntityFormDisplayTest.php @@ -0,0 +1,226 @@ +pluginDefinition = new LayoutDefinition([ + 'library' => 'system/drupal.layout.twocol', + 'theme_hook' => 'layout__twocol', + 'regions' => [ + 'left' => [ + 'label' => 'Left', + ], + 'right' => [ + 'label' => 'Right', + ], + ], + ]); + $layout_plugin = new LayoutDefault([], 'two_column', $this->pluginDefinition); + + $layout_plugin_manager = $this->prophesize(LayoutPluginManagerInterface::class); + $layout_plugin_manager->getDefinition('unknown', FALSE)->willReturn(NULL); + $layout_plugin_manager->getDefinition('two_column', FALSE)->willReturn($this->pluginDefinition); + $layout_plugin_manager->createInstance('two_column', [])->willReturn($layout_plugin); + + $renderer = $this->prophesize(RendererInterface::class); + + $entity_type = $this->prophesize(EntityTypeInterface::class); + $entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE); + + $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); + $entity_type_manager->getDefinition('the_entity_type_id')->willReturn($entity_type->reveal()); + + $widget_manager = $this->prophesize(WidgetPluginManager::class); + + $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class); + + $container = new ContainerBuilder(); + $container->set('entity_field.manager', $this->entityFieldManager->reveal()); + $container->set('entity_type.manager', $entity_type_manager->reveal()); + $container->set('plugin.manager.field.widget', $widget_manager->reveal()); + $container->set('renderer', $renderer->reveal()); + $container->set('plugin.manager.core.layout', $layout_plugin_manager->reveal()); + \Drupal::setContainer($container); + } + + /** + * @covers ::applyLayout + * @covers ::getFields + */ + public function testApplyLayout() { + $non_configurable_field_definition = $this->prophesize(FieldDefinitionInterface::class); + $non_configurable_field_definition->isDisplayConfigurable('form')->willReturn(FALSE); + + $definitions = []; + $definitions['non_configurable_field'] = $non_configurable_field_definition->reveal(); + $this->entityFieldManager->getFieldDefinitions('the_entity_type_id', 'the_entity_type_bundle')->willReturn($definitions); + + $this->entityFieldManager->getExtraFields('the_entity_type_id', 'the_entity_type_bundle')->willReturn([]); + + $build = [ + 'test1' => [ + '#markup' => 'Test1', + ], + 'test2' => [ + '#markup' => 'Test2', + '#group' => 'existing_group', + ], + 'field_layout' => [ + '#markup' => 'Field created through the UI happens to be named "Layout"', + ], + 'non_configurable_field' => [ + '#markup' => 'Non-configurable', + ], + ]; + + $display = new EntityFormDisplay( + [ + 'targetEntityType' => 'the_entity_type_id', + 'bundle' => 'the_entity_type_bundle', + 'layout_id' => 'two_column', + 'layout_settings' => [], + 'content' => [ + 'test1' => [ + 'region' => 'right', + ], + 'test2' => [ + 'region' => 'left', + ], + 'field_layout' => [ + 'region' => 'right', + ], + 'non_configurable_field' => [ + 'region' => 'left', + ], + ], + ], + 'entity_form_display' + ); + + $expected = [ + 'test1' => [ + '#markup' => 'Test1', + '#group' => 'right', + ], + 'test2' => [ + '#markup' => 'Test2', + '#group' => 'existing_group', + ], + 'field_layout' => [ + '#markup' => 'Field created through the UI happens to be named "Layout"', + '#group' => 'right', + ], + 'non_configurable_field' => [ + '#markup' => 'Non-configurable', + ], + 'layout' => [ + 'left' => [ + '#process' => ['\Drupal\Core\Render\Element\RenderElement::processGroup'], + '#pre_render' => ['\Drupal\Core\Render\Element\RenderElement::preRenderGroup'], + ], + 'right' => [ + '#process' => ['\Drupal\Core\Render\Element\RenderElement::processGroup'], + '#pre_render' => ['\Drupal\Core\Render\Element\RenderElement::preRenderGroup'], + ], + '#settings' => [], + '#layout' => $this->pluginDefinition, + '#theme' => 'layout__twocol', + '#attached' => [ + 'library' => [ + 'system/drupal.layout.twocol', + ], + ], + ], + ]; + + $build = $display->applyLayout($build, new FormState(), []); + $this->assertEquals($expected, $build); + $this->assertSame($expected, $build); + } + + /** + * @covers ::applyLayout + * @covers ::getFields + */ + public function testApplyLayoutEmpty() { + $definitions = []; + $non_configurable_field_definition = $this->prophesize(FieldDefinitionInterface::class); + $non_configurable_field_definition->isDisplayConfigurable('form')->willReturn(FALSE); + $definitions['non_configurable_field'] = $non_configurable_field_definition->reveal(); + $this->entityFieldManager->getFieldDefinitions('the_entity_type_id', 'the_entity_type_bundle')->willReturn($definitions); + $this->entityFieldManager->getExtraFields('the_entity_type_id', 'the_entity_type_bundle')->willReturn([]); + + $build = [ + 'non_configurable_field' => [ + '#markup' => 'Non-configurable', + ], + ]; + + $display = new EntityFormDisplay( + [ + 'targetEntityType' => 'the_entity_type_id', + 'bundle' => 'the_entity_type_bundle', + 'layout_id' => 'two_column', + 'layout_settings' => [], + 'content' => [ + 'test1' => [ + 'region' => 'right', + ], + 'non_configurable_field' => [ + 'region' => 'left', + ], + ], + ], + 'entity_form_display' + ); + + $expected = [ + 'non_configurable_field' => [ + '#markup' => 'Non-configurable', + ], + ]; + $build = $display->applyLayout($build, new FormState(), []); + $this->assertSame($expected, $build); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Entity/Display/EntityViewDisplayTest.php b/core/tests/Drupal/Tests/Core/Entity/Display/EntityViewDisplayTest.php new file mode 100644 index 0000000..9650ab5 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Entity/Display/EntityViewDisplayTest.php @@ -0,0 +1,169 @@ +pluginDefinition = new LayoutDefinition([ + 'library' => 'system/drupal.layout.twocol', + 'theme_hook' => 'layout__twocol', + 'regions' => [ + 'left' => [ + 'label' => 'Left', + ], + 'right' => [ + 'label' => 'Right', + ], + ], + ]); + $layout_plugin = new LayoutDefault([], 'two_column', $this->pluginDefinition); + + $layout_plugin_manager = $this->prophesize(LayoutPluginManagerInterface::class); + $layout_plugin_manager->getDefinition('unknown', FALSE)->willReturn(NULL); + $layout_plugin_manager->getDefinition('two_column', FALSE)->willReturn($this->pluginDefinition); + $layout_plugin_manager->createInstance('two_column', [])->willReturn($layout_plugin); + + $renderer = $this->prophesize(RendererInterface::class); + + $entity_type = $this->prophesize(EntityTypeInterface::class); + $entity_type->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE); + + $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); + $entity_type_manager->getDefinition('the_entity_type_id')->willReturn($entity_type->reveal()); + + $formatter_manager = $this->prophesize(FormatterPluginManager::class); + + $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class); + + $container = new ContainerBuilder(); + $container->set('entity_field.manager', $this->entityFieldManager->reveal()); + $container->set('entity_type.manager', $entity_type_manager->reveal()); + $container->set('plugin.manager.field.formatter', $formatter_manager->reveal()); + $container->set('renderer', $renderer->reveal()); + $container->set('plugin.manager.core.layout', $layout_plugin_manager->reveal()); + \Drupal::setContainer($container); + } + + /** + * @covers ::applyLayout + * @covers ::getFields + */ + public function testApplyLayout() { + $non_configurable_field_definition = $this->prophesize(FieldDefinitionInterface::class); + $non_configurable_field_definition->isDisplayConfigurable('view')->willReturn(FALSE); + + $definitions = []; + $definitions['non_configurable_field'] = $non_configurable_field_definition->reveal(); + $definitions['non_configurable_field_with_extra_field'] = $non_configurable_field_definition->reveal(); + $this->entityFieldManager->getFieldDefinitions('the_entity_type_id', 'the_entity_type_bundle')->willReturn($definitions); + + $extra_fields = []; + $extra_fields['non_configurable_field_with_extra_field'] = [ + 'label' => 'This non-configurable field is also defined in hook_entity_extra_field_info()', + ]; + $this->entityFieldManager->getExtraFields('the_entity_type_id', 'the_entity_type_bundle')->willReturn($extra_fields); + + $build = [ + 'test1' => [ + '#markup' => 'Test1', + ], + 'non_configurable_field' => [ + '#markup' => 'Non-configurable', + ], + 'non_configurable_field_with_extra_field' => [ + '#markup' => 'Non-configurable with extra field', + ], + ]; + + $display = new EntityViewDisplay( + [ + 'targetEntityType' => 'the_entity_type_id', + 'bundle' => 'the_entity_type_bundle', + 'layout_id' => 'two_column', + 'layout_settings' => [], + 'content' => [ + 'test1' => [ + 'region' => 'right', + ], + 'non_configurable_field' => [ + 'region' => 'left', + ], + 'non_configurable_field_with_extra_field' => [ + 'region' => 'left', + ], + ], + ], + 'entity_view_display' + ); + + $expected = [ + 'non_configurable_field' => [ + '#markup' => 'Non-configurable', + ], + 'layout' => [ + 'left' => [ + 'non_configurable_field_with_extra_field' => [ + '#markup' => 'Non-configurable with extra field', + ], + ], + 'right' => [ + 'test1' => [ + '#markup' => 'Test1', + ], + ], + '#settings' => [], + '#layout' => $this->pluginDefinition, + '#theme' => 'layout__twocol', + '#attached' => [ + 'library' => [ + 'system/drupal.layout.twocol', + ], + ], + ], + ]; + + $method_ref = new \ReflectionMethod($display, 'applyLayout'); + $method_ref->setAccessible(TRUE); + $method_ref->invokeArgs($display, [&$build]); + $this->assertEquals($expected, $build); + } + +} diff --git a/core/modules/field_layout/layouts/onecol/layout--onecol.html.twig b/core/themes/stable/templates/layout/layout--onecol.html.twig similarity index 54% rename from core/modules/field_layout/layouts/onecol/layout--onecol.html.twig rename to core/themes/stable/templates/layout/layout--onecol.html.twig index 69fed03..34b58ee 100644 --- a/core/modules/field_layout/layouts/onecol/layout--onecol.html.twig +++ b/core/themes/stable/templates/layout/layout--onecol.html.twig @@ -10,15 +10,4 @@ * @ingroup themeable */ #} -{% -set classes = [ -'layout--onecol', -] -%} -{% if content %} - -
- {{ content }} -
- -{% endif %} +{{ content }} diff --git a/core/modules/field_layout/layouts/twocol/layout--twocol.html.twig b/core/themes/stable/templates/layout/layout--twocol.html.twig similarity index 100% rename from core/modules/field_layout/layouts/twocol/layout--twocol.html.twig rename to core/themes/stable/templates/layout/layout--twocol.html.twig diff --git a/core/modules/layout_discovery/templates/layout.html.twig b/core/themes/stable/templates/layout/layout.html.twig similarity index 100% rename from core/modules/layout_discovery/templates/layout.html.twig rename to core/themes/stable/templates/layout/layout.html.twig