updated core to 8.6.1 via composer
This commit is contained in:
@@ -2,6 +2,9 @@ core.entity_view_display.*.*.*.third_party.layout_builder:
|
||||
type: mapping
|
||||
label: 'Per-view-mode Layout Builder settings'
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: 'Whether the Layout Builder is enabled for this display'
|
||||
allow_custom:
|
||||
type: boolean
|
||||
label: 'Allow a customized layout'
|
||||
@@ -44,3 +47,20 @@ layout_builder.component:
|
||||
additional:
|
||||
type: ignore
|
||||
label: 'Additional data'
|
||||
|
||||
inline_block:
|
||||
type: block_settings
|
||||
label: 'Inline block'
|
||||
mapping:
|
||||
view_mode:
|
||||
type: string
|
||||
lable: 'View mode'
|
||||
block_revision_id:
|
||||
type: integer
|
||||
label: 'Block revision ID'
|
||||
block_serialized:
|
||||
type: string
|
||||
label: 'Serialized block'
|
||||
|
||||
block.settings.inline_block:*:
|
||||
type: inline_block
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.add-section {
|
||||
.new-section {
|
||||
background-color: #f7f7f7;
|
||||
width: 100%;
|
||||
outline: 2px dashed #979797;
|
||||
padding: 1.5em 0;
|
||||
@@ -7,19 +8,54 @@
|
||||
transition: visually-hidden 2s ease-out, height 2s ease-in;
|
||||
}
|
||||
|
||||
.new-section__link,
|
||||
.new-block__link {
|
||||
color: #787878;
|
||||
border-bottom: none;
|
||||
padding-left: 24px;
|
||||
background: url(../../../misc/icons/787878/plus.svg) transparent top left / 16px 16px no-repeat;
|
||||
}
|
||||
|
||||
.new-section__link:hover,
|
||||
.new-section__link:active,
|
||||
.new-section__link:focus,
|
||||
.new-block__link:hover,
|
||||
.new-block__link:active,
|
||||
.new-block__link:focus {
|
||||
border-bottom-style: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.layout-section {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.layout-section .layout-builder--layout__region {
|
||||
outline: 2px dashed #2f91da;
|
||||
padding: 1.5em 0;
|
||||
.layout-section .ui-sortable-helper {
|
||||
background-color: #fff;
|
||||
outline: 2px solid #f7f7f7;
|
||||
}
|
||||
|
||||
.layout-section .layout-builder--layout__region .add-block {
|
||||
.layout-section .ui-state-drop {
|
||||
background-color: #ffd;
|
||||
outline: 2px dashed #fedb60;
|
||||
margin: 20px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.layout-section .layout-builder--layout__region {
|
||||
outline: 2px dashed #2f91da;
|
||||
}
|
||||
|
||||
.layout-section .layout-builder--layout__region .new-block {
|
||||
background-color: #eff6fc;
|
||||
padding: 1.5em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layout-section .layout-builder--layout__region .block {
|
||||
padding: 1.5em;
|
||||
}
|
||||
|
||||
.layout-section .remove-section {
|
||||
position: relative;
|
||||
background: url(../../../misc/icons/bebebe/ex.svg) #fff center center / 16px 16px no-repeat;
|
||||
|
||||
@@ -1,41 +1,54 @@
|
||||
(($, { ajax, behaviors }) => {
|
||||
behaviors.layoutBuilder = {
|
||||
attach(context) {
|
||||
$(context).find('.layout-builder--layout__region').sortable({
|
||||
items: '> .draggable',
|
||||
connectWith: '.layout-builder--layout__region',
|
||||
$(context)
|
||||
.find('.layout-builder--layout__region')
|
||||
.sortable({
|
||||
items: '> .draggable',
|
||||
connectWith: '.layout-builder--layout__region',
|
||||
placeholder: 'ui-state-drop',
|
||||
|
||||
/**
|
||||
* Updates the layout with the new position of the block.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The jQuery Event object.
|
||||
* @param {Object} ui
|
||||
* An object containing information about the item being sorted.
|
||||
*/
|
||||
update(event, ui) {
|
||||
// Check if the region from the event and region for the item match.
|
||||
const itemRegion = ui.item.closest('.layout-builder--layout__region');
|
||||
if (event.target === itemRegion[0]) {
|
||||
// Find the destination delta.
|
||||
const deltaTo = ui.item.closest('[data-layout-delta]').data('layout-delta');
|
||||
// If the block didn't leave the original delta use the destination.
|
||||
const deltaFrom = ui.sender ? ui.sender.closest('[data-layout-delta]').data('layout-delta') : deltaTo;
|
||||
ajax({
|
||||
url: [
|
||||
ui.item.closest('[data-layout-update-url]').data('layout-update-url'),
|
||||
deltaFrom,
|
||||
deltaTo,
|
||||
itemRegion.data('region'),
|
||||
ui.item.data('layout-block-uuid'),
|
||||
ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid'),
|
||||
]
|
||||
.filter(element => element !== undefined)
|
||||
.join('/'),
|
||||
}).execute();
|
||||
}
|
||||
},
|
||||
});
|
||||
/**
|
||||
* Updates the layout with the new position of the block.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The jQuery Event object.
|
||||
* @param {Object} ui
|
||||
* An object containing information about the item being sorted.
|
||||
*/
|
||||
update(event, ui) {
|
||||
// Check if the region from the event and region for the item match.
|
||||
const itemRegion = ui.item.closest(
|
||||
'.layout-builder--layout__region',
|
||||
);
|
||||
if (event.target === itemRegion[0]) {
|
||||
// Find the destination delta.
|
||||
const deltaTo = ui.item
|
||||
.closest('[data-layout-delta]')
|
||||
.data('layout-delta');
|
||||
// If the block didn't leave the original delta use the destination.
|
||||
const deltaFrom = ui.sender
|
||||
? ui.sender.closest('[data-layout-delta]').data('layout-delta')
|
||||
: deltaTo;
|
||||
ajax({
|
||||
url: [
|
||||
ui.item
|
||||
.closest('[data-layout-update-url]')
|
||||
.data('layout-update-url'),
|
||||
deltaFrom,
|
||||
deltaTo,
|
||||
itemRegion.data('region'),
|
||||
ui.item.data('layout-block-uuid'),
|
||||
ui.item
|
||||
.prev('[data-layout-block-uuid]')
|
||||
.data('layout-block-uuid'),
|
||||
]
|
||||
.filter(element => element !== undefined)
|
||||
.join('/'),
|
||||
}).execute();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
$(context).find('.layout-builder--layout__region').sortable({
|
||||
items: '> .draggable',
|
||||
connectWith: '.layout-builder--layout__region',
|
||||
placeholder: 'ui-state-drop',
|
||||
|
||||
update: function update(event, ui) {
|
||||
var itemRegion = ui.item.closest('.layout-builder--layout__region');
|
||||
|
||||
@@ -5,7 +5,7 @@ package: Core (Experimental)
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- layout_discovery
|
||||
- contextual
|
||||
- drupal:layout_discovery
|
||||
- drupal:contextual
|
||||
# @todo Discuss removing in https://www.drupal.org/project/drupal/issues/2935999.
|
||||
- field_ui
|
||||
- drupal:field_ui
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
*/
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\layout_builder\Section;
|
||||
|
||||
@@ -13,6 +15,8 @@ use Drupal\layout_builder\Section;
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function layout_builder_install() {
|
||||
$display_changed = FALSE;
|
||||
|
||||
$displays = LayoutBuilderEntityViewDisplay::loadMultiple();
|
||||
/** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */
|
||||
foreach ($displays as $display) {
|
||||
@@ -20,21 +24,115 @@ function layout_builder_install() {
|
||||
$field_layout = $display->getThirdPartySettings('field_layout');
|
||||
if (isset($field_layout['id'])) {
|
||||
$field_layout += ['settings' => []];
|
||||
$display->appendSection(new Section($field_layout['id'], $field_layout['settings']));
|
||||
$display
|
||||
->enableLayoutBuilder()
|
||||
->appendSection(new Section($field_layout['id'], $field_layout['settings']))
|
||||
->save();
|
||||
$display_changed = TRUE;
|
||||
}
|
||||
|
||||
// Sort the components by weight.
|
||||
$components = $display->get('content');
|
||||
uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
|
||||
foreach ($components as $name => $component) {
|
||||
$display->setComponent($name, $component);
|
||||
}
|
||||
$display->save();
|
||||
}
|
||||
|
||||
// Clear the rendered cache to ensure the new layout builder flow is used.
|
||||
// While in many cases the above change will not affect the rendered output,
|
||||
// the cacheability metadata will have changed and should be processed to
|
||||
// prepare for future changes.
|
||||
Cache::invalidateTags(['rendered']);
|
||||
if ($display_changed) {
|
||||
Cache::invalidateTags(['rendered']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Layout Builder for existing entity displays.
|
||||
*/
|
||||
function layout_builder_update_8601(&$sandbox) {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
|
||||
if (!isset($sandbox['count'])) {
|
||||
$sandbox['ids'] = $config_factory->listAll('core.entity_view_display.');
|
||||
$sandbox['count'] = count($sandbox['ids']);
|
||||
}
|
||||
|
||||
$ids = array_splice($sandbox['ids'], 0, 50);
|
||||
foreach ($ids as $id) {
|
||||
$display = $config_factory->getEditable($id);
|
||||
if ($display->get('third_party_settings.layout_builder')) {
|
||||
$display
|
||||
->set('third_party_settings.layout_builder.enabled', TRUE)
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
$sandbox['#finished'] = empty($sandbox['ids']) ? 1 : ($sandbox['count'] - count($sandbox['ids'])) / $sandbox['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function layout_builder_schema() {
|
||||
$schema['inline_block_usage'] = [
|
||||
'description' => 'Track where a block_content entity is used.',
|
||||
'fields' => [
|
||||
'block_content_id' => [
|
||||
'description' => 'The block_content entity ID.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
],
|
||||
'layout_entity_type' => [
|
||||
'description' => 'The entity type of the parent entity.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => EntityTypeInterface::ID_MAX_LENGTH,
|
||||
'not null' => FALSE,
|
||||
'default' => '',
|
||||
],
|
||||
'layout_entity_id' => [
|
||||
'description' => 'The ID of the parent entity.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 128,
|
||||
'not null' => FALSE,
|
||||
'default' => 0,
|
||||
],
|
||||
],
|
||||
'primary key' => ['block_content_id'],
|
||||
'indexes' => [
|
||||
'type_id' => ['layout_entity_type', 'layout_entity_id'],
|
||||
],
|
||||
];
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the 'inline_block_usage' table.
|
||||
*/
|
||||
function layout_builder_update_8602() {
|
||||
$inline_block_usage = [
|
||||
'description' => 'Track where a block_content entity is used.',
|
||||
'fields' => [
|
||||
'block_content_id' => [
|
||||
'description' => 'The block_content entity ID.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
],
|
||||
'layout_entity_type' => [
|
||||
'description' => 'The entity type of the parent entity.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => EntityTypeInterface::ID_MAX_LENGTH,
|
||||
'not null' => FALSE,
|
||||
'default' => '',
|
||||
],
|
||||
'layout_entity_id' => [
|
||||
'description' => 'The ID of the parent entity.',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 128,
|
||||
'not null' => FALSE,
|
||||
'default' => 0,
|
||||
],
|
||||
],
|
||||
'primary key' => ['block_content_id'],
|
||||
'indexes' => [
|
||||
'type_id' => ['layout_entity_type', 'layout_entity_id'],
|
||||
],
|
||||
];
|
||||
Database::getConnection()->schema()->createTable('inline_block_usage', $inline_block_usage);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Provides hook implementations for Layout Builder.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
@@ -12,6 +13,12 @@ use Drupal\field\FieldConfigInterface;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage;
|
||||
use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
|
||||
use Drupal\layout_builder\Plugin\Block\ExtraFieldBlock;
|
||||
use Drupal\layout_builder\InlineBlockEntityOperations;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
@@ -81,3 +88,117 @@ function layout_builder_field_config_delete(FieldConfigInterface $field_config)
|
||||
$sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
|
||||
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_view_alter().
|
||||
*
|
||||
* ExtraFieldBlock block plugins add placeholders for each extra field which is
|
||||
* configured to be displayed. Those placeholders are replaced by this hook.
|
||||
* Modules that implement hook_entity_extra_field_info() use their
|
||||
* implementations of hook_entity_view_alter() to add the rendered output of
|
||||
* the extra fields they provide, so we cannot get the rendered output of extra
|
||||
* fields before this point in the view process.
|
||||
* layout_builder_module_implements_alter() moves this implementation of
|
||||
* hook_entity_view_alter() to the end of the list.
|
||||
*
|
||||
* @see \Drupal\layout_builder\Plugin\Block\ExtraFieldBlock::build()
|
||||
* @see layout_builder_module_implements_alter()
|
||||
*/
|
||||
function layout_builder_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
|
||||
if ($display instanceof LayoutEntityDisplayInterface) {
|
||||
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
|
||||
$field_manager = \Drupal::service('entity_field.manager');
|
||||
$extra_fields = $field_manager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
|
||||
if (!empty($extra_fields['display'])) {
|
||||
foreach ($extra_fields['display'] as $field_name => $extra_field) {
|
||||
// If the extra field is not set replace with an empty array to avoid
|
||||
// the placeholder text from being rendered.
|
||||
$replacement = isset($build[$field_name]) ? $build[$field_name] : [];
|
||||
ExtraFieldBlock::replaceFieldPlaceholder($build, $replacement, $field_name);
|
||||
// After the rendered field in $build has been copied over to the
|
||||
// ExtraFieldBlock block we must remove it from its original location or
|
||||
// else it will be rendered twice.
|
||||
unset($build[$field_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_builder_module_implements_alter().
|
||||
*/
|
||||
function layout_builder_module_implements_alter(&$implementations, $hook) {
|
||||
if ($hook === 'entity_view_alter') {
|
||||
// Ensure that this module's implementation of hook_entity_view_alter() runs
|
||||
// last so that other modules that use this hook to render extra fields will
|
||||
// run before it.
|
||||
$group = $implementations['layout_builder'];
|
||||
unset($implementations['layout_builder']);
|
||||
$implementations['layout_builder'] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_presave().
|
||||
*/
|
||||
function layout_builder_entity_presave(EntityInterface $entity) {
|
||||
if (\Drupal::moduleHandler()->moduleExists('block_content')) {
|
||||
/** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */
|
||||
$entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class);
|
||||
$entity_operations->handlePreSave($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_delete().
|
||||
*/
|
||||
function layout_builder_entity_delete(EntityInterface $entity) {
|
||||
if (\Drupal::moduleHandler()->moduleExists('block_content')) {
|
||||
/** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */
|
||||
$entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class);
|
||||
$entity_operations->handleEntityDelete($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*/
|
||||
function layout_builder_cron() {
|
||||
if (\Drupal::moduleHandler()->moduleExists('block_content')) {
|
||||
/** @var \Drupal\layout_builder\InlineBlockEntityOperations $entity_operations */
|
||||
$entity_operations = \Drupal::classResolver(InlineBlockEntityOperations::class);
|
||||
$entity_operations->removeUnused();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_plugin_filter_TYPE_alter().
|
||||
*/
|
||||
function layout_builder_plugin_filter_block_alter(array &$definitions, array $extra, $consumer) {
|
||||
// @todo Determine the 'inline_block' blocks should be allowed outside
|
||||
// of layout_builder https://www.drupal.org/node/2979142.
|
||||
if ($consumer !== 'layout_builder') {
|
||||
foreach ($definitions as $id => $definition) {
|
||||
if ($definition['id'] === 'inline_block') {
|
||||
unset($definitions[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_access().
|
||||
*/
|
||||
function layout_builder_block_content_access(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
/** @var \Drupal\block_content\BlockContentInterface $entity */
|
||||
if ($operation === 'view' || $entity->isReusable() || empty(\Drupal::service('inline_block.usage')->getUsage($entity->id()))) {
|
||||
// If the operation is 'view' or this is reusable block or if this is
|
||||
// non-reusable that isn't used by this module then don't alter the access.
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
if ($account->hasPermission('configure any layout')) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
* Post update functions for Layout Builder.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
|
||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
|
||||
|
||||
/**
|
||||
* Rebuild plugin dependencies for all entity view displays.
|
||||
*/
|
||||
@@ -24,3 +27,34 @@ function layout_builder_post_update_rebuild_plugin_dependencies(&$sandbox = NULL
|
||||
|
||||
$sandbox['#finished'] = empty($sandbox['ids']) ? 1 : ($sandbox['count'] - count($sandbox['ids'])) / $sandbox['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure all extra fields are properly stored on entity view displays.
|
||||
*
|
||||
* Previously
|
||||
* \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::setComponent()
|
||||
* was not correctly setting the configuration for extra fields. This function
|
||||
* calls setComponent() for all extra field components to ensure the updated
|
||||
* logic is invoked on all extra fields to correct the settings.
|
||||
*/
|
||||
function layout_builder_post_update_add_extra_fields(&$sandbox = NULL) {
|
||||
$entity_field_manager = \Drupal::service('entity_field.manager');
|
||||
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'entity_view_display', function (LayoutEntityDisplayInterface $display) use ($entity_field_manager) {
|
||||
if (!$display->isLayoutBuilderEnabled()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$extra_fields = $entity_field_manager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle());
|
||||
$components = $display->getComponents();
|
||||
// Sort the components to avoid them being reordered by setComponent().
|
||||
uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
|
||||
$result = FALSE;
|
||||
foreach ($components as $name => $component) {
|
||||
if (isset($extra_fields['display'][$name])) {
|
||||
$display->setComponent($name, $component);
|
||||
$result = TRUE;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ layout_builder.choose_section:
|
||||
_controller: '\Drupal\layout_builder\Controller\ChooseSectionController::build'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -16,6 +17,7 @@ layout_builder.add_section:
|
||||
_controller: '\Drupal\layout_builder\Controller\AddSectionController::build'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -32,6 +34,7 @@ layout_builder.configure_section:
|
||||
plugin_id: null
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -44,6 +47,7 @@ layout_builder.remove_section:
|
||||
_form: '\Drupal\layout_builder\Form\RemoveSectionForm'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -56,6 +60,7 @@ layout_builder.choose_block:
|
||||
_controller: '\Drupal\layout_builder\Controller\ChooseBlockController::build'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -68,6 +73,7 @@ layout_builder.add_block:
|
||||
_form: '\Drupal\layout_builder\Form\AddBlockForm'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -80,6 +86,7 @@ layout_builder.update_block:
|
||||
_form: '\Drupal\layout_builder\Form\UpdateBlockForm'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -92,6 +99,7 @@ layout_builder.remove_block:
|
||||
_form: '\Drupal\layout_builder\Form\RemoveBlockForm'
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
@@ -110,6 +118,7 @@ layout_builder.move_block:
|
||||
preceding_block_uuid: null
|
||||
requirements:
|
||||
_permission: 'configure any layout'
|
||||
_layout_builder_access: 'view'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
parameters:
|
||||
|
||||
@@ -2,6 +2,10 @@ services:
|
||||
layout_builder.tempstore_repository:
|
||||
class: Drupal\layout_builder\LayoutTempstoreRepository
|
||||
arguments: ['@tempstore.shared']
|
||||
access_check.entity.layout_builder_access:
|
||||
class: Drupal\layout_builder\Access\LayoutBuilderAccessCheck
|
||||
tags:
|
||||
- { name: access_check, applies_to: _layout_builder_access }
|
||||
access_check.entity.layout:
|
||||
class: Drupal\layout_builder\Access\LayoutSectionAccessCheck
|
||||
tags:
|
||||
@@ -36,3 +40,9 @@ services:
|
||||
arguments: ['@current_user']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
logger.channel.layout_builder:
|
||||
parent: logger.channel_base
|
||||
arguments: ['layout_builder']
|
||||
inline_block.usage:
|
||||
class: Drupal\layout_builder\InlineBlockUsage
|
||||
arguments: ['@database']
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Access;
|
||||
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\layout_builder\SectionStorageInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides an access check for the Layout Builder defaults.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LayoutBuilderAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* Checks routing access to the layout.
|
||||
*
|
||||
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
|
||||
* The section storage.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current user.
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(SectionStorageInterface $section_storage, AccountInterface $account, Route $route) {
|
||||
$operation = $route->getRequirement('_layout_builder_access');
|
||||
$access = $section_storage->access($operation, $account, TRUE);
|
||||
if ($access instanceof RefinableCacheableDependencyInterface) {
|
||||
$access->addCacheableDependency($section_storage);
|
||||
}
|
||||
return $access;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Accessible class to allow access for inline blocks in the Layout Builder.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LayoutPreviewAccessAllowed implements AccessibleInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
if ($operation === 'view') {
|
||||
return $return_as_object ? AccessResult::allowed() : TRUE;
|
||||
}
|
||||
// The layout builder preview should only need 'view' access.
|
||||
return $return_as_object ? AccessResult::forbidden() : FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\layout_builder\Controller;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxHelperTrait;
|
||||
use Drupal\Core\DependencyInjection\ClassResolverInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Controller;
|
||||
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
|
||||
/**
|
||||
* Provides a helper to determine if the current request is via AJAX.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @todo Move to \Drupal\Core in https://www.drupal.org/node/2896535.
|
||||
*/
|
||||
trait AjaxHelperTrait {
|
||||
|
||||
/**
|
||||
* Determines if the current request is via AJAX.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the current request is via AJAX, FALSE otherwise.
|
||||
*/
|
||||
protected function isAjax() {
|
||||
return in_array($this->getRequestWrapperFormat(), [
|
||||
'drupal_ajax',
|
||||
'drupal_dialog',
|
||||
'drupal_dialog.off_canvas',
|
||||
'drupal_modal',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the wrapper format of the current request.
|
||||
*
|
||||
* @string
|
||||
* The wrapper format.
|
||||
*/
|
||||
protected function getRequestWrapperFormat() {
|
||||
return \Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\layout_builder\Controller;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxHelperTrait;
|
||||
use Drupal\Core\Block\BlockManagerInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Url;
|
||||
@@ -62,7 +63,15 @@ class ChooseBlockController implements ContainerInjectionInterface {
|
||||
$build['#type'] = 'container';
|
||||
$build['#attributes']['class'][] = 'block-categories';
|
||||
|
||||
$definitions = $this->blockManager->getDefinitionsForContexts($this->getAvailableContexts($section_storage));
|
||||
// @todo Explicitly cast delta to an integer, remove this in
|
||||
// https://www.drupal.org/project/drupal/issues/2984509.
|
||||
$delta = (int) $delta;
|
||||
|
||||
$definitions = $this->blockManager->getFilteredDefinitions('layout_builder', $this->getAvailableContexts($section_storage), [
|
||||
'section_storage' => $section_storage,
|
||||
'delta' => $delta,
|
||||
'region' => $region,
|
||||
]);
|
||||
foreach ($this->blockManager->getGroupedDefinitions($definitions) as $category => $blocks) {
|
||||
$build[$category]['#type'] = 'details';
|
||||
$build[$category]['#open'] = TRUE;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\layout_builder\Controller;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxHelperTrait;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Layout\LayoutPluginManagerInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
@@ -61,7 +62,8 @@ class ChooseSectionController implements ContainerInjectionInterface {
|
||||
$output['#title'] = $this->t('Choose a layout');
|
||||
|
||||
$items = [];
|
||||
foreach ($this->layoutManager->getDefinitions() as $plugin_id => $definition) {
|
||||
$definitions = $this->layoutManager->getFilteredDefinitions('layout_builder', [], ['section_storage' => $section_storage]);
|
||||
foreach ($definitions as $plugin_id => $definition) {
|
||||
$layout = $this->layoutManager->createInstance($plugin_id);
|
||||
$item = [
|
||||
'#type' => 'link',
|
||||
|
||||
@@ -161,7 +161,7 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
||||
],
|
||||
[
|
||||
'attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'class' => ['use-ajax', 'new-section__link'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'off_canvas',
|
||||
],
|
||||
@@ -170,7 +170,7 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
||||
],
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => ['add-section'],
|
||||
'class' => ['new-section'],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -226,7 +226,7 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
||||
],
|
||||
[
|
||||
'attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'class' => ['use-ajax', 'new-block__link'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'off_canvas',
|
||||
],
|
||||
@@ -234,7 +234,7 @@ class LayoutBuilderController implements ContainerInjectionInterface {
|
||||
),
|
||||
];
|
||||
$build[$region]['layout_builder_add_block']['#type'] = 'container';
|
||||
$build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['add-block']];
|
||||
$build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['new-block']];
|
||||
$build[$region]['layout_builder_add_block']['#weight'] = 1000;
|
||||
$build[$region]['#attributes']['data-region'] = $region;
|
||||
$build[$region]['#attributes']['class'][] = 'layout-builder--layout__region';
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Drupal\layout_builder;
|
||||
|
||||
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for an object that stores layout sections for defaults.
|
||||
*
|
||||
@@ -9,8 +11,10 @@ namespace Drupal\layout_builder;
|
||||
* Layout Builder is currently experimental and should only be leveraged by
|
||||
* experimental modules and development releases of contributed modules.
|
||||
* See https://www.drupal.org/core/experimental for more information.
|
||||
*
|
||||
* @todo Refactor this interface in https://www.drupal.org/node/2985362.
|
||||
*/
|
||||
interface DefaultsSectionStorageInterface extends SectionStorageInterface {
|
||||
interface DefaultsSectionStorageInterface extends SectionStorageInterface, ThirdPartySettingsInterface, LayoutBuilderEnabledInterface {
|
||||
|
||||
/**
|
||||
* Determines if the defaults allow custom overrides.
|
||||
|
||||
@@ -2,16 +2,10 @@
|
||||
|
||||
namespace Drupal\layout_builder\Entity;
|
||||
|
||||
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\Plugin\Definition\DependentPluginDefinitionInterface;
|
||||
use Drupal\Core\Plugin\Context\EntityContext;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
@@ -31,6 +25,24 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
||||
|
||||
use SectionStorageTrait;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $values, $entity_type) {
|
||||
// Set $entityFieldManager before calling the parent constructor because the
|
||||
// constructor will call init() which then calls setComponent() which needs
|
||||
// $entityFieldManager.
|
||||
$this->entityFieldManager = \Drupal::service('entity_field.manager');
|
||||
parent::__construct($values, $entity_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -46,6 +58,30 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLayoutBuilderEnabled() {
|
||||
return (bool) $this->getThirdPartySetting('layout_builder', 'enabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enableLayoutBuilder() {
|
||||
$this->setThirdPartySetting('layout_builder', 'enabled', TRUE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disableLayoutBuilder() {
|
||||
$this->setOverridable(FALSE);
|
||||
$this->setThirdPartySetting('layout_builder', 'enabled', FALSE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -80,6 +116,27 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
||||
$field->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$already_enabled = isset($this->original) ? $this->original->isLayoutBuilderEnabled() : FALSE;
|
||||
$set_enabled = $this->isLayoutBuilderEnabled();
|
||||
if ($already_enabled !== $set_enabled) {
|
||||
if ($set_enabled) {
|
||||
// Loop through all existing field-based components and add them as
|
||||
// section-based components.
|
||||
$components = $this->getComponents();
|
||||
// Sort the components by weight.
|
||||
uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement');
|
||||
foreach ($components as $name => $component) {
|
||||
$this->setComponent($name, $component);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// When being disabled, remove all existing section data.
|
||||
while (count($this) > 0) {
|
||||
$this->removeSection(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +198,11 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
||||
*/
|
||||
public function buildMultiple(array $entities) {
|
||||
$build_list = parent::buildMultiple($entities);
|
||||
if (!$this->isLayoutBuilderEnabled()) {
|
||||
return $build_list;
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity */
|
||||
foreach ($entities as $id => $entity) {
|
||||
$sections = $this->getRuntimeSections($entity);
|
||||
if ($sections) {
|
||||
@@ -155,9 +216,10 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
||||
// Bypass ::getContexts() in order to use the runtime entity, not a
|
||||
// sample entity.
|
||||
$contexts = $this->contextRepository()->getAvailableContexts();
|
||||
// @todo Use EntityContextDefinition after resolving
|
||||
// https://www.drupal.org/node/2932462.
|
||||
$contexts['layout_builder.entity'] = new Context(new ContextDefinition("entity:{$entity->getEntityTypeId()}", new TranslatableMarkup('@entity being viewed', ['@entity' => $entity->getEntityType()->getLabel()])), $entity);
|
||||
$label = new TranslatableMarkup('@entity being viewed', [
|
||||
'@entity' => $entity->getEntityType()->getSingularLabel(),
|
||||
]);
|
||||
$contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label);
|
||||
foreach ($sections as $delta => $section) {
|
||||
$build_list[$id]['_layout_builder'][$delta] = $section->toRenderArray($contexts);
|
||||
}
|
||||
@@ -246,50 +308,14 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
||||
return $changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and returns dependencies of a specific plugin instance.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\PluginInspectionInterface $instance
|
||||
* The plugin instance.
|
||||
*
|
||||
* @return array
|
||||
* An array of dependencies keyed by the type of dependency.
|
||||
*
|
||||
* @todo Replace this in https://www.drupal.org/project/drupal/issues/2939925.
|
||||
*/
|
||||
protected function getPluginDependencies(PluginInspectionInterface $instance) {
|
||||
$dependencies = [];
|
||||
$definition = $instance->getPluginDefinition();
|
||||
if ($definition instanceof PluginDefinitionInterface) {
|
||||
$dependencies['module'][] = $definition->getProvider();
|
||||
if ($definition instanceof DependentPluginDefinitionInterface && $config_dependencies = $definition->getConfigDependencies()) {
|
||||
$dependencies = NestedArray::mergeDeep($dependencies, $config_dependencies);
|
||||
}
|
||||
}
|
||||
elseif (is_array($definition)) {
|
||||
$dependencies['module'][] = $definition['provider'];
|
||||
// Plugins can declare additional dependencies in their definition.
|
||||
if (isset($definition['config_dependencies'])) {
|
||||
$dependencies = NestedArray::mergeDeep($dependencies, $definition['config_dependencies']);
|
||||
}
|
||||
}
|
||||
|
||||
// If a plugin is dependent, calculate its dependencies.
|
||||
if ($instance instanceof DependentPluginInterface && $plugin_dependencies = $instance->calculateDependencies()) {
|
||||
$dependencies = NestedArray::mergeDeep($dependencies, $plugin_dependencies);
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setComponent($name, array $options = []) {
|
||||
parent::setComponent($name, $options);
|
||||
|
||||
// @todo Remove workaround for EntityViewBuilder::getSingleFieldDisplay() in
|
||||
// https://www.drupal.org/project/drupal/issues/2936464.
|
||||
if ($this->getMode() === static::CUSTOM_MODE) {
|
||||
// Only continue if Layout Builder is enabled.
|
||||
if (!$this->isLayoutBuilderEnabled()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -297,13 +323,21 @@ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements La
|
||||
$options = $this->content[$name];
|
||||
// Provide backwards compatibility by converting to a section component.
|
||||
$field_definition = $this->getFieldDefinition($name);
|
||||
if ($field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type'])) {
|
||||
$configuration = [];
|
||||
$configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
|
||||
$configuration['label_display'] = FALSE;
|
||||
$keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
|
||||
$configuration['formatter'] = array_intersect_key($options, $keys);
|
||||
$configuration['context_mapping']['entity'] = 'layout_builder.entity';
|
||||
$extra_fields = $this->entityFieldManager->getExtraFields($this->getTargetEntityTypeId(), $this->getTargetBundle());
|
||||
$is_view_configurable_non_extra_field = $field_definition && $field_definition->isDisplayConfigurable('view') && isset($options['type']);
|
||||
if ($is_view_configurable_non_extra_field || isset($extra_fields['display'][$name])) {
|
||||
$configuration = [
|
||||
'label_display' => '0',
|
||||
'context_mapping' => ['entity' => 'layout_builder.entity'],
|
||||
];
|
||||
if ($is_view_configurable_non_extra_field) {
|
||||
$configuration['id'] = 'field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
|
||||
$keys = array_flip(['type', 'label', 'settings', 'third_party_settings']);
|
||||
$configuration['formatter'] = array_intersect_key($options, $keys);
|
||||
}
|
||||
else {
|
||||
$configuration['id'] = 'extra_field_block:' . $this->getTargetEntityTypeId() . ':' . $this->getTargetBundle() . ':' . $name;
|
||||
}
|
||||
|
||||
$section = $this->getDefaultSection();
|
||||
$region = isset($options['region']) ? $options['region'] : $section->getDefaultRegion();
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace Drupal\layout_builder\Entity;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\layout_builder\Section;
|
||||
use Drupal\layout_builder\SectionComponent;
|
||||
|
||||
/**
|
||||
* Provides storage for entity view display entities that have layouts.
|
||||
@@ -38,20 +37,7 @@ class LayoutBuilderEntityViewDisplayStorage extends ConfigEntityStorage {
|
||||
foreach ($records as $id => &$record) {
|
||||
if (!empty($record['third_party_settings']['layout_builder']['sections'])) {
|
||||
$sections = &$record['third_party_settings']['layout_builder']['sections'];
|
||||
foreach ($sections as $section_delta => $section) {
|
||||
$sections[$section_delta] = new Section(
|
||||
$section['layout_id'],
|
||||
$section['layout_settings'],
|
||||
array_map(function (array $component) {
|
||||
return (new SectionComponent(
|
||||
$component['uuid'],
|
||||
$component['region'],
|
||||
$component['configuration'],
|
||||
$component['additional']
|
||||
))->setWeight($component['weight']);
|
||||
}, $section['components'])
|
||||
);
|
||||
}
|
||||
$sections = array_map([Section::class, 'fromArray'], $sections);
|
||||
}
|
||||
}
|
||||
return parent::mapFromStorageRecords($records);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Drupal\layout_builder\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Display\EntityDisplayInterface;
|
||||
use Drupal\layout_builder\LayoutBuilderEnabledInterface;
|
||||
use Drupal\layout_builder\SectionListInterface;
|
||||
|
||||
/**
|
||||
@@ -12,8 +13,10 @@ use Drupal\layout_builder\SectionListInterface;
|
||||
* Layout Builder is currently experimental and should only be leveraged by
|
||||
* experimental modules and development releases of contributed modules.
|
||||
* See https://www.drupal.org/core/experimental for more information.
|
||||
*
|
||||
* @todo Refactor this interface in https://www.drupal.org/node/2985362.
|
||||
*/
|
||||
interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface {
|
||||
interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface, LayoutBuilderEnabledInterface {
|
||||
|
||||
/**
|
||||
* Determines if the display allows custom overrides.
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace Drupal\layout_builder\EventSubscriber;
|
||||
|
||||
use Drupal\block_content\Access\RefinableDependentAccessInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\layout_builder\Access\LayoutPreviewAccessAllowed;
|
||||
use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
|
||||
use Drupal\layout_builder\LayoutBuilderEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
@@ -56,6 +58,24 @@ class BlockComponentRenderArray implements EventSubscriberInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set block access dependency even if we are not checking access on
|
||||
// this level. The block itself may render another
|
||||
// RefinableDependentAccessInterface object and need to pass on this value.
|
||||
if ($block instanceof RefinableDependentAccessInterface) {
|
||||
$contexts = $event->getContexts();
|
||||
if (isset($contexts['layout_builder.entity'])) {
|
||||
if ($entity = $contexts['layout_builder.entity']->getContextValue()) {
|
||||
if ($event->inPreview()) {
|
||||
// If previewing in Layout Builder allow access.
|
||||
$block->setAccessDependency(new LayoutPreviewAccessAllowed());
|
||||
}
|
||||
else {
|
||||
$block->setAccessDependency($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only check access if the component is not being previewed.
|
||||
if ($event->inPreview()) {
|
||||
$access = AccessResult::allowed()->setCacheMaxAge(0);
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\EventSubscriber;
|
||||
|
||||
use Drupal\block_content\BlockContentEvents;
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\block_content\Event\BlockContentGetDependencyEvent;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\layout_builder\InlineBlockUsage;
|
||||
use Drupal\layout_builder\LayoutEntityHelperTrait;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* An event subscriber that returns an access dependency for inline blocks.
|
||||
*
|
||||
* When used within the layout builder the access dependency for inline blocks
|
||||
* will be explicitly set but if access is evaluated outside of the layout
|
||||
* builder then the dependency may not have been set.
|
||||
*
|
||||
* A known example of when the access dependency will not have been set is when
|
||||
* determining 'view' or 'download' access to a file entity that is attached
|
||||
* to a content block via a field that is using the private file system. The
|
||||
* file access handler will evaluate access on the content block without setting
|
||||
* the dependency.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @see \Drupal\file\FileAccessControlHandler::checkAccess()
|
||||
* @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
|
||||
*/
|
||||
class SetInlineBlockDependency implements EventSubscriberInterface {
|
||||
|
||||
use LayoutEntityHelperTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The inline block usage service.
|
||||
*
|
||||
* @var \Drupal\layout_builder\InlineBlockUsage
|
||||
*/
|
||||
protected $usage;
|
||||
|
||||
/**
|
||||
* Constructs SetInlineBlockDependency object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
* @param \Drupal\layout_builder\InlineBlockUsage $usage
|
||||
* The inline block usage service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, InlineBlockUsage $usage) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->database = $database;
|
||||
$this->usage = $usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return [
|
||||
BlockContentEvents::BLOCK_CONTENT_GET_DEPENDENCY => 'onGetDependency',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the BlockContentEvents::INLINE_BLOCK_GET_DEPENDENCY event.
|
||||
*
|
||||
* @param \Drupal\block_content\Event\BlockContentGetDependencyEvent $event
|
||||
* The event.
|
||||
*/
|
||||
public function onGetDependency(BlockContentGetDependencyEvent $event) {
|
||||
if ($dependency = $this->getInlineBlockDependency($event->getBlockContentEntity())) {
|
||||
$event->setAccessDependency($dependency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access dependency of an inline block.
|
||||
*
|
||||
* If the block is used in an entity that entity will be returned as the
|
||||
* dependency.
|
||||
*
|
||||
* For revisionable entities the entity will only be returned if it is used in
|
||||
* the latest revision of the entity. For inline blocks that are not used in
|
||||
* the latest revision but are used in a previous revision the entity will not
|
||||
* be returned because calling
|
||||
* \Drupal\Core\Access\AccessibleInterface::access() will only check access on
|
||||
* the latest revision. Therefore if the previous revision of the entity was
|
||||
* returned as the dependency access would be granted to inline block
|
||||
* regardless of whether the user has access to the revision in which the
|
||||
* inline block was used.
|
||||
*
|
||||
* @param \Drupal\block_content\BlockContentInterface $block_content
|
||||
* The block content entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* Returns the layout dependency.
|
||||
*
|
||||
* @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
|
||||
* @see \Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray::onBuildRender()
|
||||
*/
|
||||
protected function getInlineBlockDependency(BlockContentInterface $block_content) {
|
||||
$layout_entity_info = $this->usage->getUsage($block_content->id());
|
||||
if (empty($layout_entity_info)) {
|
||||
// If the block does not have usage information then we cannot set a
|
||||
// dependency. It may be used by another module besides layout builder.
|
||||
return NULL;
|
||||
}
|
||||
/** @var \Drupal\layout_builder\InlineBlockUsage $usage */
|
||||
$layout_entity_storage = $this->entityTypeManager->getStorage($layout_entity_info->layout_entity_type);
|
||||
$layout_entity = $layout_entity_storage->load($layout_entity_info->layout_entity_id);
|
||||
if ($this->isLayoutCompatibleEntity($layout_entity)) {
|
||||
if ($this->isBlockRevisionUsedInEntity($layout_entity, $block_content)) {
|
||||
return $layout_entity;
|
||||
}
|
||||
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a block content revision is used in an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $layout_entity
|
||||
* The layout entity.
|
||||
* @param \Drupal\block_content\BlockContentInterface $block_content
|
||||
* The block content revision.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the block content revision is used as an inline block in the
|
||||
* layout entity.
|
||||
*/
|
||||
protected function isBlockRevisionUsedInEntity(EntityInterface $layout_entity, BlockContentInterface $block_content) {
|
||||
$sections_blocks_revision_ids = $this->getInlineBlockRevisionIdsInSections($this->getEntitySections($layout_entity));
|
||||
return in_array($block_content->getRevisionId(), $sections_blocks_revision_ids);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,10 +48,10 @@ class AddBlockForm extends ConfigureBlockFormBase {
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $plugin_id = NULL) {
|
||||
// Only generate a new component once per form submission.
|
||||
if (!$component = $form_state->getTemporaryValue('layout_builder__component')) {
|
||||
if (!$component = $form_state->get('layout_builder__component')) {
|
||||
$component = new SectionComponent($this->uuidGenerator->generate(), $region, ['id' => $plugin_id]);
|
||||
$section_storage->getSection($delta)->appendComponent($component);
|
||||
$form_state->setTemporaryValue('layout_builder__component', $component);
|
||||
$form_state->set('layout_builder__component', $component);
|
||||
}
|
||||
return $this->doBuildForm($form, $form_state, $section_storage, $delta, $component);
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\ReplaceCommand;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\layout_builder\Controller\AjaxHelperTrait;
|
||||
|
||||
/**
|
||||
* Provides a helper to for submitting an AJAX form.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @todo Move to \Drupal\Core in https://www.drupal.org/node/2896535.
|
||||
*/
|
||||
trait AjaxFormHelperTrait {
|
||||
|
||||
use AjaxHelperTrait;
|
||||
|
||||
/**
|
||||
* Submit form dialog #ajax callback.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* An AJAX response that display validation error messages or represents a
|
||||
* successful submission.
|
||||
*/
|
||||
public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->hasAnyErrors()) {
|
||||
$form['status_messages'] = [
|
||||
'#type' => 'status_messages',
|
||||
'#weight' => -1000,
|
||||
];
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
|
||||
}
|
||||
else {
|
||||
$response = $this->successfulAjaxSubmit($form, $form_state);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the form to respond to a successful AJAX submission.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* An AJAX response.
|
||||
*/
|
||||
abstract protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state);
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Drupal\layout_builder\Form;
|
||||
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Ajax\AjaxFormHelperTrait;
|
||||
use Drupal\Core\Block\BlockManagerInterface;
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
use Drupal\Core\DependencyInjection\ClassResolverInterface;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\layout_builder\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxFormHelperTrait;
|
||||
use Drupal\Core\DependencyInjection\ClassResolverInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Form;
|
||||
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\layout_builder\DefaultsSectionStorageInterface;
|
||||
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
|
||||
use Drupal\layout_builder\SectionStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Disables Layout Builder for a given default.
|
||||
*/
|
||||
class LayoutBuilderDisableForm extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The layout tempstore repository.
|
||||
*
|
||||
* @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
|
||||
*/
|
||||
protected $layoutTempstoreRepository;
|
||||
|
||||
/**
|
||||
* The section storage.
|
||||
*
|
||||
* @var \Drupal\layout_builder\DefaultsSectionStorageInterface
|
||||
*/
|
||||
protected $sectionStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new RevertOverridesForm.
|
||||
*
|
||||
* @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
|
||||
* The layout tempstore repository.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
*/
|
||||
public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
|
||||
$this->layoutTempstoreRepository = $layout_tempstore_repository;
|
||||
$this->setMessenger($messenger);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('layout_builder.tempstore_repository'),
|
||||
$container->get('messenger')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'layout_builder_disable_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to disable Layout Builder?');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('All customizations will be removed. This action cannot be undone.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->sectionStorage->getRedirectUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) {
|
||||
if (!$section_storage instanceof DefaultsSectionStorageInterface) {
|
||||
throw new \InvalidArgumentException(sprintf('The section storage with type "%s" and ID "%s" does not provide defaults', $section_storage->getStorageType(), $section_storage->getStorageId()));
|
||||
}
|
||||
|
||||
$this->sectionStorage = $section_storage;
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->sectionStorage->disableLayoutBuilder()->save();
|
||||
$this->layoutTempstoreRepository->delete($this->sectionStorage);
|
||||
|
||||
$this->messenger()->addMessage($this->t('Layout Builder has been disabled.'));
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\layout_builder\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\field_ui\Form\EntityViewDisplayEditForm;
|
||||
@@ -28,7 +29,7 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
|
||||
/**
|
||||
* The storage section.
|
||||
*
|
||||
* @var \Drupal\layout_builder\SectionStorageInterface
|
||||
* @var \Drupal\layout_builder\DefaultsSectionStorageInterface
|
||||
*/
|
||||
protected $sectionStorage;
|
||||
|
||||
@@ -46,10 +47,17 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
// Hide the table of fields.
|
||||
$form['fields']['#access'] = FALSE;
|
||||
$form['#fields'] = [];
|
||||
$form['#extra'] = [];
|
||||
$is_enabled = $this->entity->isLayoutBuilderEnabled();
|
||||
if ($is_enabled) {
|
||||
// Hide the table of fields.
|
||||
$form['fields']['#access'] = FALSE;
|
||||
$form['#fields'] = [];
|
||||
$form['#extra'] = [];
|
||||
}
|
||||
else {
|
||||
// Remove the Layout Builder field from the list.
|
||||
$form['#fields'] = array_diff($form['#fields'], ['layout_builder__layout']);
|
||||
}
|
||||
|
||||
$form['manage_layout'] = [
|
||||
'#type' => 'link',
|
||||
@@ -57,18 +65,26 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
|
||||
'#weight' => -10,
|
||||
'#attributes' => ['class' => ['button']],
|
||||
'#url' => $this->sectionStorage->getLayoutBuilderUrl(),
|
||||
'#access' => $is_enabled,
|
||||
];
|
||||
|
||||
$form['layout'] = [
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => $this->t('Layout options'),
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
$form['layout']['enabled'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Use Layout Builder'),
|
||||
'#default_value' => $is_enabled,
|
||||
];
|
||||
$form['#entity_builders']['layout_builder'] = '::entityFormEntityBuild';
|
||||
|
||||
// @todo Expand to work for all view modes in
|
||||
// https://www.drupal.org/node/2907413.
|
||||
if ($this->entity->getMode() === 'default') {
|
||||
$form['layout'] = [
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => $this->t('Layout options'),
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
$entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
|
||||
$form['layout']['allow_custom'] = [
|
||||
'#type' => 'checkbox',
|
||||
@@ -76,14 +92,26 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
|
||||
'@entity' => $entity_type->getSingularLabel(),
|
||||
]),
|
||||
'#default_value' => $this->entity->isOverridable(),
|
||||
'#states' => [
|
||||
'disabled' => [
|
||||
':input[name="layout[enabled]"]' => ['checked' => FALSE],
|
||||
],
|
||||
'invisible' => [
|
||||
':input[name="layout[enabled]"]' => ['checked' => FALSE],
|
||||
],
|
||||
],
|
||||
];
|
||||
if (!$is_enabled) {
|
||||
$form['layout']['allow_custom']['#attributes']['disabled'] = 'disabled';
|
||||
}
|
||||
// Prevent turning off overrides while any exist.
|
||||
if ($this->hasOverrides($this->entity)) {
|
||||
$form['layout']['enabled']['#disabled'] = TRUE;
|
||||
$form['layout']['enabled']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.');
|
||||
$form['layout']['allow_custom']['#disabled'] = TRUE;
|
||||
$form['layout']['allow_custom']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.');
|
||||
}
|
||||
else {
|
||||
$form['#entity_builders'][] = '::entityFormEntityBuild';
|
||||
unset($form['layout']['allow_custom']['#states']);
|
||||
unset($form['#entity_builders']['layout_builder']);
|
||||
}
|
||||
}
|
||||
return $form;
|
||||
@@ -112,26 +140,62 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
|
||||
return (bool) $query->count()->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
// Do not process field values if Layout Builder is or will be enabled.
|
||||
$set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE);
|
||||
/** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $entity */
|
||||
$already_enabled = $entity->isLayoutBuilderEnabled();
|
||||
if ($already_enabled || $set_enabled) {
|
||||
$form['#fields'] = [];
|
||||
$form['#extra'] = [];
|
||||
}
|
||||
|
||||
parent::copyFormValuesToEntity($entity, $form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity builder for layout options on the entity view display form.
|
||||
*/
|
||||
public function entityFormEntityBuild($entity_type_id, LayoutEntityDisplayInterface $display, &$form, FormStateInterface &$form_state) {
|
||||
$new_value = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE);
|
||||
$display->setOverridable($new_value);
|
||||
$set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE);
|
||||
$already_enabled = $display->isLayoutBuilderEnabled();
|
||||
|
||||
if ($set_enabled) {
|
||||
$overridable = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE);
|
||||
$display->setOverridable($overridable);
|
||||
|
||||
if (!$already_enabled) {
|
||||
$display->enableLayoutBuilder();
|
||||
}
|
||||
}
|
||||
elseif ($already_enabled) {
|
||||
$form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl('disable'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) {
|
||||
// Intentionally empty.
|
||||
if ($this->entity->isLayoutBuilderEnabled() || $field_definition->getType() === 'layout_section') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return parent::buildFieldRow($field_definition, $form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildExtraFieldRow($field_id, $extra_field) {
|
||||
// Intentionally empty.
|
||||
if ($this->entity->isLayoutBuilderEnabled()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return parent::buildExtraFieldRow($field_id, $extra_field);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\layout_builder\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxFormHelperTrait;
|
||||
use Drupal\Core\DependencyInjection\ClassResolverInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
@@ -103,6 +103,10 @@ class RevertOverridesForm extends ConfirmFormBase {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Ensure the section storage is loaded from the database.
|
||||
// @todo Remove after https://www.drupal.org/node/2970801.
|
||||
$this->sectionStorage = \Drupal::service('plugin.manager.layout_builder.section_storage')->loadFromStorageId($this->sectionStorage->getStorageType(), $this->sectionStorage->getStorageId());
|
||||
|
||||
// Remove all sections.
|
||||
while ($this->sectionStorage->count()) {
|
||||
$this->sectionStorage->removeSection(0);
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\RevisionableInterface;
|
||||
use Drupal\layout_builder\Plugin\Block\InlineBlock;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a class for reacting to entity events related to Inline Blocks.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class InlineBlockEntityOperations implements ContainerInjectionInterface {
|
||||
|
||||
use LayoutEntityHelperTrait;
|
||||
|
||||
/**
|
||||
* Inline block usage tracking service.
|
||||
*
|
||||
* @var \Drupal\layout_builder\InlineBlockUsage
|
||||
*/
|
||||
protected $usage;
|
||||
|
||||
/**
|
||||
* The block content storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $blockContentStorage;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityOperations object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\layout_builder\InlineBlockUsage $usage
|
||||
* Inline block usage tracking service.
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsage $usage, Connection $database) {
|
||||
$this->entityTypeManager = $entityTypeManager;
|
||||
$this->blockContentStorage = $entityTypeManager->getStorage('block_content');
|
||||
$this->usage = $usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('inline_block.usage'),
|
||||
$container->get('database')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all unused inline blocks on save.
|
||||
*
|
||||
* Entities that were used in prevision revisions will be removed if not
|
||||
* saving a new revision.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The parent entity.
|
||||
*/
|
||||
protected function removeUnusedForEntityOnSave(EntityInterface $entity) {
|
||||
// If the entity is new or '$entity->original' is not set then there will
|
||||
// not be any unused inline blocks to remove.
|
||||
// If this is a revisionable entity then do not remove inline blocks. They
|
||||
// could be referenced in previous revisions even if this is not a new
|
||||
// revision.
|
||||
if ($entity->isNew() || !isset($entity->original) || $entity instanceof RevisionableInterface) {
|
||||
return;
|
||||
}
|
||||
$sections = $this->getEntitySections($entity);
|
||||
// If this is a layout override and there are no sections then it is a new
|
||||
// override.
|
||||
if ($this->isEntityUsingFieldOverride($entity) && empty($sections)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete and remove the usage for inline blocks that were removed.
|
||||
if ($removed_block_ids = $this->getRemovedBlockIds($entity)) {
|
||||
$this->deleteBlocksAndUsage($removed_block_ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the IDs of the inline blocks that were removed.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The layout entity.
|
||||
*
|
||||
* @return int[]
|
||||
* The block content IDs that were removed.
|
||||
*/
|
||||
protected function getRemovedBlockIds(EntityInterface $entity) {
|
||||
$original_sections = $this->getEntitySections($entity->original);
|
||||
$current_sections = $this->getEntitySections($entity);
|
||||
// Avoid un-needed conversion from revision IDs to block content IDs by
|
||||
// first determining if there are any revisions in the original that are not
|
||||
// also in the current sections.
|
||||
$current_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($current_sections);
|
||||
$original_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($original_sections);
|
||||
if ($unused_original_revision_ids = array_diff($original_block_content_revision_ids, $current_block_content_revision_ids)) {
|
||||
// If there are any revisions in the original that aren't in the current
|
||||
// there may some blocks that need to be removed.
|
||||
$current_block_content_ids = $this->getBlockIdsForRevisionIds($current_block_content_revision_ids);
|
||||
$unused_original_block_content_ids = $this->getBlockIdsForRevisionIds($unused_original_revision_ids);
|
||||
return array_diff($unused_original_block_content_ids, $current_block_content_ids);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles entity tracking on deleting a parent entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The parent entity.
|
||||
*/
|
||||
public function handleEntityDelete(EntityInterface $entity) {
|
||||
if ($this->isLayoutCompatibleEntity($entity)) {
|
||||
$this->usage->removeByLayoutEntity($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles saving a parent entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The parent entity.
|
||||
*/
|
||||
public function handlePreSave(EntityInterface $entity) {
|
||||
if (!$this->isLayoutCompatibleEntity($entity)) {
|
||||
return;
|
||||
}
|
||||
$duplicate_blocks = FALSE;
|
||||
|
||||
if ($sections = $this->getEntitySections($entity)) {
|
||||
if ($this->isEntityUsingFieldOverride($entity)) {
|
||||
if (!$entity->isNew() && isset($entity->original)) {
|
||||
if (empty($this->getEntitySections($entity->original))) {
|
||||
// If there were no sections in the original entity then this is a
|
||||
// new override from a default and the blocks need to be duplicated.
|
||||
$duplicate_blocks = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
$new_revision = FALSE;
|
||||
if ($entity instanceof RevisionableInterface) {
|
||||
// If the parent entity will have a new revision create a new revision
|
||||
// of the block.
|
||||
// @todo Currently revisions are never created for the parent entity.
|
||||
// This will be fixed in https://www.drupal.org/node/2937199.
|
||||
// To work around this always make a revision when the parent entity
|
||||
// is an instance of RevisionableInterface. After the issue is fixed
|
||||
// only create a new revision if '$entity->isNewRevision()'.
|
||||
$new_revision = TRUE;
|
||||
}
|
||||
|
||||
foreach ($this->getInlineBlockComponents($sections) as $component) {
|
||||
$this->saveInlineBlockComponent($entity, $component, $new_revision, $duplicate_blocks);
|
||||
}
|
||||
}
|
||||
$this->removeUnusedForEntityOnSave($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a block ID for an inline block plugin.
|
||||
*
|
||||
* @param \Drupal\layout_builder\Plugin\Block\InlineBlock $block_plugin
|
||||
* The inline block plugin.
|
||||
*
|
||||
* @return int
|
||||
* The block content ID or null none available.
|
||||
*/
|
||||
protected function getPluginBlockId(InlineBlock $block_plugin) {
|
||||
$configuration = $block_plugin->getConfiguration();
|
||||
if (!empty($configuration['block_revision_id'])) {
|
||||
$revision_ids = $this->getBlockIdsForRevisionIds([$configuration['block_revision_id']]);
|
||||
return array_pop($revision_ids);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the inline blocks and the usage records.
|
||||
*
|
||||
* @param int[] $block_content_ids
|
||||
* The block content entity IDs.
|
||||
*/
|
||||
protected function deleteBlocksAndUsage(array $block_content_ids) {
|
||||
foreach ($block_content_ids as $block_content_id) {
|
||||
if ($block = $this->blockContentStorage->load($block_content_id)) {
|
||||
$block->delete();
|
||||
}
|
||||
}
|
||||
$this->usage->deleteUsage($block_content_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unused inline blocks.
|
||||
*
|
||||
* @param int $limit
|
||||
* The maximum number of inline blocks to remove.
|
||||
*/
|
||||
public function removeUnused($limit = 100) {
|
||||
$this->deleteBlocksAndUsage($this->usage->getUnused($limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets blocks IDs for an array of revision IDs.
|
||||
*
|
||||
* @param int[] $revision_ids
|
||||
* The revision IDs.
|
||||
*
|
||||
* @return int[]
|
||||
* The block IDs.
|
||||
*/
|
||||
protected function getBlockIdsForRevisionIds(array $revision_ids) {
|
||||
if ($revision_ids) {
|
||||
$query = $this->blockContentStorage->getQuery();
|
||||
$query->condition('revision_id', $revision_ids, 'IN');
|
||||
$block_ids = $query->execute();
|
||||
return $block_ids;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an inline block component.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity with the layout.
|
||||
* @param \Drupal\layout_builder\SectionComponent $component
|
||||
* The section component with an inline block.
|
||||
* @param bool $new_revision
|
||||
* Whether a new revision of the block should be created.
|
||||
* @param bool $duplicate_blocks
|
||||
* Whether the blocks should be duplicated.
|
||||
*/
|
||||
protected function saveInlineBlockComponent(EntityInterface $entity, SectionComponent $component, $new_revision, $duplicate_blocks) {
|
||||
/** @var \Drupal\layout_builder\Plugin\Block\InlineBlock $plugin */
|
||||
$plugin = $component->getPlugin();
|
||||
$pre_save_configuration = $plugin->getConfiguration();
|
||||
$plugin->saveBlockContent($new_revision, $duplicate_blocks);
|
||||
$post_save_configuration = $plugin->getConfiguration();
|
||||
if ($duplicate_blocks || (empty($pre_save_configuration['block_revision_id']) && !empty($post_save_configuration['block_revision_id']))) {
|
||||
$this->usage->addUsage($this->getPluginBlockId($plugin), $entity);
|
||||
}
|
||||
$component->setConfiguration($post_save_configuration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Service class to track inline block usage.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class InlineBlockUsage {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* Creates an InlineBlockUsage object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
*/
|
||||
public function __construct(Connection $database) {
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a usage record.
|
||||
*
|
||||
* @param int $block_content_id
|
||||
* The block content id.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The layout entity.
|
||||
*/
|
||||
public function addUsage($block_content_id, EntityInterface $entity) {
|
||||
$this->database->merge('inline_block_usage')
|
||||
->keys([
|
||||
'block_content_id' => $block_content_id,
|
||||
'layout_entity_id' => $entity->id(),
|
||||
'layout_entity_type' => $entity->getEntityTypeId(),
|
||||
])->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets unused inline block IDs.
|
||||
*
|
||||
* @param int $limit
|
||||
* The maximum number of block content entity IDs to return.
|
||||
*
|
||||
* @return int[]
|
||||
* The entity IDs.
|
||||
*/
|
||||
public function getUnused($limit = 100) {
|
||||
$query = $this->database->select('inline_block_usage', 't');
|
||||
$query->fields('t', ['block_content_id']);
|
||||
$query->isNull('layout_entity_id');
|
||||
$query->isNull('layout_entity_type');
|
||||
return $query->range(0, $limit)->execute()->fetchCol();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove usage record by layout entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The layout entity.
|
||||
*/
|
||||
public function removeByLayoutEntity(EntityInterface $entity) {
|
||||
$query = $this->database->update('inline_block_usage')
|
||||
->fields([
|
||||
'layout_entity_type' => NULL,
|
||||
'layout_entity_id' => NULL,
|
||||
]);
|
||||
$query->condition('layout_entity_type', $entity->getEntityTypeId());
|
||||
$query->condition('layout_entity_id', $entity->id());
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the inline blocks' the usage records.
|
||||
*
|
||||
* @param int[] $block_content_ids
|
||||
* The block content entity IDs.
|
||||
*/
|
||||
public function deleteUsage(array $block_content_ids) {
|
||||
$query = $this->database->delete('inline_block_usage')->condition('block_content_id', $block_content_ids, 'IN');
|
||||
$query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets usage record for inline block by ID.
|
||||
*
|
||||
* @param int $block_content_id
|
||||
* The block content entity ID.
|
||||
*
|
||||
* @return object
|
||||
* The usage record with properties layout_entity_id and layout_entity_type.
|
||||
*/
|
||||
public function getUsage($block_content_id) {
|
||||
$query = $this->database->select('inline_block_usage');
|
||||
$query->condition('block_content_id', $block_content_id);
|
||||
$query->fields('inline_block_usage', ['layout_entity_id', 'layout_entity_type']);
|
||||
$query->range(0, 1);
|
||||
return $query->execute()->fetchObject();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder;
|
||||
|
||||
/**
|
||||
* Provides methods for enabling and disabling Layout Builder.
|
||||
*/
|
||||
interface LayoutBuilderEnabledInterface {
|
||||
|
||||
/**
|
||||
* Determines if Layout Builder is enabled.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if Layout Builder is enabled, FALSE otherwise.
|
||||
*/
|
||||
public function isLayoutBuilderEnabled();
|
||||
|
||||
/**
|
||||
* Enables the Layout Builder.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enableLayoutBuilder();
|
||||
|
||||
/**
|
||||
* Disables the Layout Builder.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disableLayoutBuilder();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
|
||||
use Drupal\layout_builder\EventSubscriber\SetInlineBlockDependency;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Sets the layout_builder.get_block_dependency_subscriber service definition.
|
||||
*
|
||||
* This service is dependent on the block_content module so it must be provided
|
||||
* dynamically.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @see \Drupal\layout_builder\EventSubscriber\SetInlineBlockDependency
|
||||
*/
|
||||
class LayoutBuilderServiceProvider implements ServiceProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container) {
|
||||
$modules = $container->getParameter('container.modules');
|
||||
if (isset($modules['block_content'])) {
|
||||
$definition = new Definition(SetInlineBlockDependency::class);
|
||||
$definition->setArguments([
|
||||
new Reference('entity_type.manager'),
|
||||
new Reference('database'),
|
||||
new Reference('inline_block.usage'),
|
||||
]);
|
||||
$definition->addTag('event_subscriber');
|
||||
$container->setDefinition('layout_builder.get_block_dependency_subscriber', $definition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder;
|
||||
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
|
||||
|
||||
/**
|
||||
* Methods to help with entities using the layout builder.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait LayoutEntityHelperTrait {
|
||||
|
||||
/**
|
||||
* Determines if an entity can have a layout.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity can have a layout otherwise FALSE.
|
||||
*/
|
||||
protected function isLayoutCompatibleEntity(EntityInterface $entity) {
|
||||
return $entity instanceof LayoutEntityDisplayInterface || $this->isEntityUsingFieldOverride($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets revision IDs for layout sections.
|
||||
*
|
||||
* @param \Drupal\layout_builder\Section[] $sections
|
||||
* The layout sections.
|
||||
*
|
||||
* @return int[]
|
||||
* The revision IDs.
|
||||
*/
|
||||
protected function getInlineBlockRevisionIdsInSections(array $sections) {
|
||||
$revision_ids = [];
|
||||
foreach ($this->getInlineBlockComponents($sections) as $component) {
|
||||
$configuration = $component->getPlugin()->getConfiguration();
|
||||
if (!empty($configuration['block_revision_id'])) {
|
||||
$revision_ids[] = $configuration['block_revision_id'];
|
||||
}
|
||||
}
|
||||
return $revision_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sections for an entity if any.
|
||||
*
|
||||
* @todo Replace this method with calls to the SectionStorageManagerInterface
|
||||
* method for getting sections from an entity in
|
||||
* https://www.drupal.org/node/2986403.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return \Drupal\layout_builder\Section[]|null
|
||||
* The entity layout sections if available.
|
||||
*/
|
||||
protected function getEntitySections(EntityInterface $entity) {
|
||||
if ($entity instanceof LayoutEntityDisplayInterface) {
|
||||
return $entity->getSections();
|
||||
}
|
||||
elseif ($this->isEntityUsingFieldOverride($entity)) {
|
||||
return $entity->get('layout_builder__layout')->getSections();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets components that have Inline Block plugins.
|
||||
*
|
||||
* @param \Drupal\layout_builder\Section[] $sections
|
||||
* The layout sections.
|
||||
*
|
||||
* @return \Drupal\layout_builder\SectionComponent[]
|
||||
* The components that contain Inline Block plugins.
|
||||
*/
|
||||
protected function getInlineBlockComponents(array $sections) {
|
||||
$inline_block_components = [];
|
||||
foreach ($sections as $section) {
|
||||
foreach ($section->getComponents() as $component) {
|
||||
$plugin = $component->getPlugin();
|
||||
if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'inline_block') {
|
||||
$inline_block_components[] = $component;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $inline_block_components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an entity is using a field for the layout override.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity is using a field for a layout override.
|
||||
*/
|
||||
protected function isEntityUsingFieldOverride(EntityInterface $entity) {
|
||||
return $entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@ interface OverridesSectionStorageInterface extends SectionStorageInterface {
|
||||
/**
|
||||
* Returns the corresponding defaults section storage for this override.
|
||||
*
|
||||
* @return \Drupal\layout_builder\SectionStorageInterface
|
||||
* @return \Drupal\layout_builder\DefaultsSectionStorageInterface
|
||||
* The defaults section storage.
|
||||
*
|
||||
* @todo Determine if this method needs a parameter in
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a block that renders an extra field from an entity.
|
||||
*
|
||||
* This block handles fields that are provided by implementations of
|
||||
* hook_entity_extra_field_info().
|
||||
*
|
||||
* @see \Drupal\layout_builder\Plugin\Block\FieldBlock
|
||||
* This block plugin handles all other field entities not provided by
|
||||
* hook_entity_extra_field_info().
|
||||
*
|
||||
* @Block(
|
||||
* id = "extra_field_block",
|
||||
* deriver = "\Drupal\layout_builder\Plugin\Derivative\ExtraFieldBlockDeriver",
|
||||
* )
|
||||
*
|
||||
* @internal
|
||||
* Plugin classes are internal.
|
||||
*/
|
||||
class ExtraFieldBlock extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ExtraFieldBlock.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
// Get field name from the plugin ID.
|
||||
list (, , , $field_name) = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4);
|
||||
assert(!empty($field_name));
|
||||
$this->fieldName = $field_name;
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'label_display' => FALSE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_field.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity that has the field.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\FieldableEntityInterface
|
||||
* The entity.
|
||||
*/
|
||||
protected function getEntity() {
|
||||
return $this->getContextValue('entity');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$entity = $this->getEntity();
|
||||
// Add a placeholder to replace after the entity view is built.
|
||||
// @see layout_builder_entity_view_alter().
|
||||
$extra_fields = $this->entityFieldManager->getExtraFields($entity->getEntityTypeId(), $entity->bundle());
|
||||
if (!isset($extra_fields['display'][$this->fieldName])) {
|
||||
$build = [];
|
||||
}
|
||||
else {
|
||||
$build = [
|
||||
'#extra_field_placeholder_field_name' => $this->fieldName,
|
||||
// Always provide a placeholder. The Layout Builder will NOT invoke
|
||||
// hook_entity_view_alter() so extra fields will not be added to the
|
||||
// render array. If the hook is invoked the placeholder will be
|
||||
// replaced.
|
||||
// @see ::replaceFieldPlaceholder()
|
||||
'#markup' => new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $extra_fields['display'][$this->fieldName]['label']]),
|
||||
];
|
||||
}
|
||||
CacheableMetadata::createFromObject($this)->applyTo($build);
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all placeholders for a given field.
|
||||
*
|
||||
* @param array $build
|
||||
* The built render array for the elements.
|
||||
* @param array $built_field
|
||||
* The render array to replace the placeholder.
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
*
|
||||
* @see ::build()
|
||||
*/
|
||||
public static function replaceFieldPlaceholder(array &$build, array $built_field, $field_name) {
|
||||
foreach (Element::children($build) as $child) {
|
||||
if (isset($build[$child]['#extra_field_placeholder_field_name']) && $build[$child]['#extra_field_placeholder_field_name'] === $field_name) {
|
||||
$placeholder_cache = CacheableMetadata::createFromRenderArray($build[$child]);
|
||||
$built_cache = CacheableMetadata::createFromRenderArray($built_field);
|
||||
$merged_cache = $placeholder_cache->merge($built_cache);
|
||||
$build[$child] = $built_field;
|
||||
$merged_cache->applyTo($build);
|
||||
}
|
||||
else {
|
||||
static::replaceFieldPlaceholder($build[$child], $built_field, $field_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockAccess(AccountInterface $account) {
|
||||
return $this->getEntity()->access('view', $account, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ use Drupal\Core\Plugin\ContextAwarePluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -81,6 +82,13 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a new FieldBlock.
|
||||
*
|
||||
@@ -96,11 +104,14 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta
|
||||
* The formatter manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* The logger.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FormatterPluginManager $formatter_manager, ModuleHandlerInterface $module_handler) {
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FormatterPluginManager $formatter_manager, ModuleHandlerInterface $module_handler, LoggerInterface $logger) {
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->formatterManager = $formatter_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->logger = $logger;
|
||||
|
||||
// Get the entity type and field name from the plugin ID.
|
||||
list (, $entity_type_id, $bundle, $field_name) = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4);
|
||||
@@ -121,7 +132,8 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta
|
||||
$plugin_definition,
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('plugin.manager.field.formatter'),
|
||||
$container->get('module_handler')
|
||||
$container->get('module_handler'),
|
||||
$container->get('logger.channel.layout_builder')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -141,7 +153,13 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta
|
||||
public function build() {
|
||||
$display_settings = $this->getConfiguration()['formatter'];
|
||||
$entity = $this->getEntity();
|
||||
$build = $entity->get($this->fieldName)->view($display_settings);
|
||||
try {
|
||||
$build = $entity->get($this->fieldName)->view($display_settings);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$build = [];
|
||||
$this->logger->warning('The field "%field" failed to render with the error of "%error".', ['%field' => $this->fieldName, '%error' => $e->getMessage()]);
|
||||
}
|
||||
if (!empty($entity->in_preview) && !Element::getVisibleChildren($build)) {
|
||||
$build['content']['#markup'] = new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Plugin\Block;
|
||||
|
||||
use Drupal\block_content\Access\RefinableDependentAccessInterface;
|
||||
use Drupal\block_content\Access\RefinableDependentAccessTrait;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\SubformStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines an inline block plugin type.
|
||||
*
|
||||
* @Block(
|
||||
* id = "inline_block",
|
||||
* admin_label = @Translation("Inline block"),
|
||||
* category = @Translation("Inline blocks"),
|
||||
* deriver = "Drupal\layout_builder\Plugin\Derivative\InlineBlockDeriver",
|
||||
* )
|
||||
*
|
||||
* @internal
|
||||
* Plugin classes are internal.
|
||||
*/
|
||||
class InlineBlock extends BlockBase implements ContainerFactoryPluginInterface, RefinableDependentAccessInterface {
|
||||
|
||||
use RefinableDependentAccessTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The block content entity.
|
||||
*
|
||||
* @var \Drupal\block_content\BlockContentInterface
|
||||
*/
|
||||
protected $blockContent;
|
||||
|
||||
/**
|
||||
* The entity display repository.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
|
||||
*/
|
||||
protected $entityDisplayRepository;
|
||||
|
||||
/**
|
||||
* Whether a new block is being created.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isNew = TRUE;
|
||||
|
||||
/**
|
||||
* Constructs a new InlineBlock.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
|
||||
* The entity display repository.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityDisplayRepository = $entity_display_repository;
|
||||
if (!empty($this->configuration['block_revision_id']) || !empty($this->configuration['block_serialized'])) {
|
||||
$this->isNew = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_display.repository')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'view_mode' => 'full',
|
||||
'block_revision_id' => NULL,
|
||||
'block_serialized' => NULL,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$block = $this->getEntity();
|
||||
|
||||
// Add the entity form display in a process callback so that #parents can
|
||||
// be successfully propagated to field widgets.
|
||||
$form['block_form'] = [
|
||||
'#type' => 'container',
|
||||
'#process' => [[static::class, 'processBlockForm']],
|
||||
'#block' => $block,
|
||||
];
|
||||
|
||||
$options = $this->entityDisplayRepository->getViewModeOptionsByBundle('block_content', $block->bundle());
|
||||
|
||||
$form['view_mode'] = [
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
'#title' => $this->t('View mode'),
|
||||
'#description' => $this->t('The view mode in which to render the block.'),
|
||||
'#default_value' => $this->configuration['view_mode'],
|
||||
'#access' => count($options) > 1,
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process callback to insert a Custom Block form.
|
||||
*
|
||||
* @param array $element
|
||||
* The containing element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @return array
|
||||
* The containing element, with the Custom Block form inserted.
|
||||
*/
|
||||
public static function processBlockForm(array $element, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\block_content\BlockContentInterface $block */
|
||||
$block = $element['#block'];
|
||||
EntityFormDisplay::collectRenderDisplay($block, 'edit')->buildForm($block, $element, $form_state);
|
||||
$element['revision_log']['#access'] = FALSE;
|
||||
$element['info']['#access'] = FALSE;
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockValidate($form, FormStateInterface $form_state) {
|
||||
$block_form = $form['block_form'];
|
||||
/** @var \Drupal\block_content\BlockContentInterface $block */
|
||||
$block = $block_form['#block'];
|
||||
$form_display = EntityFormDisplay::collectRenderDisplay($block, 'edit');
|
||||
$complete_form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
|
||||
$form_display->extractFormValues($block, $block_form, $complete_form_state);
|
||||
$form_display->validateFormValues($block, $block_form, $complete_form_state);
|
||||
// @todo Remove when https://www.drupal.org/project/drupal/issues/2948549 is closed.
|
||||
$form_state->setTemporaryValue('block_form_parents', $block_form['#parents']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['view_mode'] = $form_state->getValue('view_mode');
|
||||
|
||||
// @todo Remove when https://www.drupal.org/project/drupal/issues/2948549 is closed.
|
||||
$block_form = NestedArray::getValue($form, $form_state->getTemporaryValue('block_form_parents'));
|
||||
/** @var \Drupal\block_content\BlockContentInterface $block */
|
||||
$block = $block_form['#block'];
|
||||
$form_display = EntityFormDisplay::collectRenderDisplay($block, 'edit');
|
||||
$complete_form_state = $form_state instanceof SubformStateInterface ? $form_state->getCompleteFormState() : $form_state;
|
||||
$form_display->extractFormValues($block, $block_form, $complete_form_state);
|
||||
$block->setInfo($this->configuration['label']);
|
||||
$this->configuration['block_serialized'] = serialize($block);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockAccess(AccountInterface $account) {
|
||||
if ($entity = $this->getEntity()) {
|
||||
return $entity->access('view', $account, TRUE);
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$block = $this->getEntity();
|
||||
return $this->entityTypeManager->getViewBuilder($block->getEntityTypeId())->view($block, $this->configuration['view_mode']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads or creates the block content entity of the block.
|
||||
*
|
||||
* @return \Drupal\block_content\BlockContentInterface
|
||||
* The block content entity.
|
||||
*/
|
||||
protected function getEntity() {
|
||||
if (!isset($this->blockContent)) {
|
||||
if (!empty($this->configuration['block_serialized'])) {
|
||||
$this->blockContent = unserialize($this->configuration['block_serialized']);
|
||||
}
|
||||
elseif (!empty($this->configuration['block_revision_id'])) {
|
||||
$entity = $this->entityTypeManager->getStorage('block_content')->loadRevision($this->configuration['block_revision_id']);
|
||||
$this->blockContent = $entity;
|
||||
}
|
||||
else {
|
||||
$this->blockContent = $this->entityTypeManager->getStorage('block_content')->create([
|
||||
'type' => $this->getDerivativeId(),
|
||||
'reusable' => FALSE,
|
||||
]);
|
||||
}
|
||||
if ($this->blockContent instanceof RefinableDependentAccessInterface && $dependee = $this->getAccessDependency()) {
|
||||
$this->blockContent->setAccessDependency($dependee);
|
||||
}
|
||||
}
|
||||
return $this->blockContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
if ($this->isNew) {
|
||||
// If the Content Block is new then don't provide a default label.
|
||||
unset($form['label']['#default_value']);
|
||||
}
|
||||
$form['label']['#description'] = $this->t('The title of the block as shown to the user.');
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the block_content entity for this plugin.
|
||||
*
|
||||
* @param bool $new_revision
|
||||
* Whether to create new revision.
|
||||
* @param bool $duplicate_block
|
||||
* Whether to duplicate the "block_content" entity.
|
||||
*/
|
||||
public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE) {
|
||||
/** @var \Drupal\block_content\BlockContentInterface $block */
|
||||
$block = NULL;
|
||||
if (!empty($this->configuration['block_serialized'])) {
|
||||
$block = unserialize($this->configuration['block_serialized']);
|
||||
}
|
||||
if ($duplicate_block) {
|
||||
if (empty($block) && !empty($this->configuration['block_revision_id'])) {
|
||||
$block = $this->entityTypeManager->getStorage('block_content')->loadRevision($this->configuration['block_revision_id']);
|
||||
}
|
||||
if ($block) {
|
||||
$block = $block->createDuplicate();
|
||||
}
|
||||
}
|
||||
|
||||
if ($block) {
|
||||
if ($new_revision) {
|
||||
$block->setNewRevision();
|
||||
}
|
||||
$block->save();
|
||||
$this->configuration['block_revision_id'] = $block->getRevisionId();
|
||||
$this->configuration['block_serialized'] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Plugin\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides entity field block definitions for every field.
|
||||
*
|
||||
* @internal
|
||||
* Layout Builder is currently experimental and should only be leveraged by
|
||||
* experimental modules and development releases of contributed modules.
|
||||
* See https://www.drupal.org/core/experimental for more information.
|
||||
*/
|
||||
class ExtraFieldBlockDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity type bundle info.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected $entityTypeBundleInfo;
|
||||
|
||||
/**
|
||||
* Constructs new FieldBlockDeriver.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
|
||||
* The entity type bundle info.
|
||||
*/
|
||||
public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityTypeBundleInfo = $entity_type_bundle_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.bundle.info')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
// Only process fieldable entity types.
|
||||
if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
|
||||
foreach ($bundles as $bundle_id => $bundle) {
|
||||
$extra_fields = $this->entityFieldManager->getExtraFields($entity_type_id, $bundle_id);
|
||||
// Skip bundles without any extra fields.
|
||||
if (empty($extra_fields['display'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($extra_fields['display'] as $extra_field_id => $extra_field) {
|
||||
$derivative = $base_plugin_definition;
|
||||
|
||||
$derivative['category'] = $entity_type->getLabel();
|
||||
|
||||
$derivative['admin_label'] = $extra_field['label'];
|
||||
|
||||
$context_definition = EntityContextDefinition::fromEntityType($entity_type)
|
||||
->addConstraint('Bundle', [$bundle_id]);
|
||||
$derivative['context'] = [
|
||||
'entity' => $context_definition,
|
||||
];
|
||||
|
||||
$derivative_id = $entity_type_id . PluginBase::DERIVATIVE_SEPARATOR . $bundle_id . PluginBase::DERIVATIVE_SEPARATOR . $extra_field_id;
|
||||
$this->derivatives[$derivative_id] = $derivative;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use Drupal\Core\Entity\EntityTypeRepositoryInterface;
|
||||
use Drupal\Core\Field\FieldConfigInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\Field\FormatterPluginManager;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
@@ -118,9 +118,7 @@ class FieldBlockDeriver extends DeriverBase implements ContainerDeriverInterface
|
||||
// unavailable to place in the block UI.
|
||||
$derivative['_block_ui_hidden'] = !$field_definition->isDisplayConfigurable('view');
|
||||
|
||||
// @todo Use EntityContextDefinition after resolving
|
||||
// https://www.drupal.org/node/2932462.
|
||||
$context_definition = new ContextDefinition('entity:' . $entity_type_id, $entity_type_labels[$entity_type_id], TRUE);
|
||||
$context_definition = EntityContextDefinition::fromEntityTypeId($entity_type_id)->setLabel($entity_type_labels[$entity_type_id]);
|
||||
$context_definition->addConstraint('Bundle', [$bundle]);
|
||||
$derivative['context'] = [
|
||||
'entity' => $context_definition,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder\Plugin\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides inline block plugin definitions for all custom block types.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class InlineBlockDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a BlockContentDeriver object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$this->derivatives = [];
|
||||
if ($this->entityTypeManager->hasDefinition('block_content_type')) {
|
||||
$block_content_types = $this->entityTypeManager->getStorage('block_content_type')->loadMultiple();
|
||||
foreach ($block_content_types as $id => $type) {
|
||||
$this->derivatives[$id] = $base_plugin_definition;
|
||||
$this->derivatives[$id]['admin_label'] = $type->label();
|
||||
$this->derivatives[$id]['config_dependencies'][$type->getConfigDependencyKey()][] = $type->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
return parent::getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,14 +3,14 @@
|
||||
namespace Drupal\layout_builder\Plugin\SectionStorage;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Plugin\Context\EntityContext;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\field_ui\FieldUI;
|
||||
use Drupal\layout_builder\DefaultsSectionStorageInterface;
|
||||
@@ -125,8 +125,8 @@ class DefaultsSectionStorage extends SectionStorageBase implements ContainerFact
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLayoutBuilderUrl() {
|
||||
return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.view", $this->getRouteParameters());
|
||||
public function getLayoutBuilderUrl($rel = 'view') {
|
||||
return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.$rel", $this->getRouteParameters());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,12 +255,9 @@ class DefaultsSectionStorage extends SectionStorageBase implements ContainerFact
|
||||
public function getContexts() {
|
||||
$display = $this->getDisplay();
|
||||
$entity = $this->sampleEntityGenerator->get($display->getTargetEntityTypeId(), $display->getTargetBundle());
|
||||
$context_label = new TranslatableMarkup('@entity being viewed', ['@entity' => $entity->getEntityType()->getLabel()]);
|
||||
|
||||
// @todo Use EntityContextDefinition after resolving
|
||||
// https://www.drupal.org/node/2932462.
|
||||
$contexts = [];
|
||||
$contexts['layout_builder.entity'] = new Context(new ContextDefinition("entity:{$entity->getEntityTypeId()}", $context_label), $entity);
|
||||
$contexts['layout_builder.entity'] = EntityContext::fromEntity($entity);
|
||||
return $contexts;
|
||||
}
|
||||
|
||||
@@ -293,4 +290,72 @@ class DefaultsSectionStorage extends SectionStorageBase implements ContainerFact
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setThirdPartySetting($module, $key, $value) {
|
||||
$this->getDisplay()->setThirdPartySetting($module, $key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLayoutBuilderEnabled() {
|
||||
return $this->getDisplay()->isLayoutBuilderEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enableLayoutBuilder() {
|
||||
$this->getDisplay()->enableLayoutBuilder();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function disableLayoutBuilder() {
|
||||
$this->getDisplay()->disableLayoutBuilder();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getThirdPartySetting($module, $key, $default = NULL) {
|
||||
return $this->getDisplay()->getThirdPartySetting($module, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getThirdPartySettings($module) {
|
||||
return $this->getDisplay()->getThirdPartySettings($module);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unsetThirdPartySetting($module, $key) {
|
||||
$this->getDisplay()->unsetThirdPartySetting($module, $key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getThirdPartyProviders() {
|
||||
return $this->getDisplay()->getThirdPartyProviders();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowedIf($this->isLayoutBuilderEnabled());
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
namespace Drupal\layout_builder\Plugin\SectionStorage;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Plugin\Context\EntityContext;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
|
||||
@@ -187,7 +187,9 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultSectionStorage() {
|
||||
return LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), 'default');
|
||||
// @todo Expand to work for all view modes in
|
||||
// https://www.drupal.org/node/2907413.
|
||||
return LayoutBuilderEntityViewDisplay::collectRenderDisplay($this->getEntity(), 'full');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,10 +202,10 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLayoutBuilderUrl() {
|
||||
public function getLayoutBuilderUrl($rel = 'view') {
|
||||
$entity = $this->getEntity();
|
||||
$route_parameters[$entity->getEntityTypeId()] = $entity->id();
|
||||
return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.view", $route_parameters);
|
||||
return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.$rel", $route_parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,9 +213,7 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
|
||||
*/
|
||||
public function getContexts() {
|
||||
$entity = $this->getEntity();
|
||||
// @todo Use EntityContextDefinition after resolving
|
||||
// https://www.drupal.org/node/2932462.
|
||||
$contexts['layout_builder.entity'] = new Context(new ContextDefinition("entity:{$entity->getEntityTypeId()}", new TranslatableMarkup('@entity being viewed', ['@entity' => $entity->getEntityType()->getLabel()])), $entity);
|
||||
$contexts['layout_builder.entity'] = EntityContext::fromEntity($entity);
|
||||
return $contexts;
|
||||
}
|
||||
|
||||
@@ -231,4 +231,13 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
|
||||
return $this->getEntity()->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$default_section_storage = $this->getDefaultSectionStorage();
|
||||
$result = AccessResult::allowedIf($default_section_storage->isLayoutBuilderEnabled())->addCacheableDependency($default_section_storage);
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Drupal\layout_builder\Routing;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\layout_builder\DefaultsSectionStorageInterface;
|
||||
use Drupal\layout_builder\OverridesSectionStorageInterface;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
|
||||
use Symfony\Component\Routing\Route;
|
||||
@@ -43,6 +44,7 @@ trait LayoutBuilderRoutesTrait {
|
||||
$defaults['section_storage'] = '';
|
||||
// Trigger the layout builder access check.
|
||||
$requirements['_has_layout_section'] = 'true';
|
||||
$requirements['_layout_builder_access'] = 'view';
|
||||
// Trigger the layout builder RouteEnhancer.
|
||||
$options['_layout_builder'] = TRUE;
|
||||
// Trigger the layout builder param converter.
|
||||
@@ -92,6 +94,17 @@ trait LayoutBuilderRoutesTrait {
|
||||
->setOptions($options);
|
||||
$collection->add("$route_name_prefix.revert", $route);
|
||||
}
|
||||
elseif (is_subclass_of($definition->getClass(), DefaultsSectionStorageInterface::class)) {
|
||||
$disable_defaults = $defaults;
|
||||
$disable_defaults['_form'] = '\Drupal\layout_builder\Form\LayoutBuilderDisableForm';
|
||||
$disable_options = $options;
|
||||
unset($disable_options['_admin_route'], $disable_options['_layout_builder']);
|
||||
$route = (new Route("$path/disable"))
|
||||
->setDefaults($disable_defaults)
|
||||
->setRequirements($requirements)
|
||||
->setOptions($disable_options);
|
||||
$collection->add("$route_name_prefix.disable", $route);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -322,8 +322,7 @@ class Section {
|
||||
/**
|
||||
* Returns an array representation of the section.
|
||||
*
|
||||
* @internal
|
||||
* This is intended for use by a storage mechanism for sections.
|
||||
* Only use this method if you are implementing custom storage for sections.
|
||||
*
|
||||
* @return array
|
||||
* An array representation of the section component.
|
||||
@@ -338,4 +337,23 @@ class Section {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object from an array representation of the section.
|
||||
*
|
||||
* Only use this method if you are implementing custom storage for sections.
|
||||
*
|
||||
* @param array $section
|
||||
* An array of section data in the format returned by ::toArray().
|
||||
*
|
||||
* @return static
|
||||
* The section object.
|
||||
*/
|
||||
public static function fromArray(array $section) {
|
||||
return new static(
|
||||
$section['layout_id'],
|
||||
$section['layout_settings'],
|
||||
array_map([SectionComponent::class, 'fromArray'], $section['components'])
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -292,8 +292,7 @@ class SectionComponent {
|
||||
/**
|
||||
* Returns an array representation of the section component.
|
||||
*
|
||||
* @internal
|
||||
* This is intended for use by a storage mechanism for section components.
|
||||
* Only use this method if you are implementing custom storage for sections.
|
||||
*
|
||||
* @return array
|
||||
* An array representation of the section component.
|
||||
@@ -308,4 +307,24 @@ class SectionComponent {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object from an array representation of the section component.
|
||||
*
|
||||
* Only use this method if you are implementing custom storage for sections.
|
||||
*
|
||||
* @param array $component
|
||||
* An array of section component data in the format returned by ::toArray().
|
||||
*
|
||||
* @return static
|
||||
* The section component object.
|
||||
*/
|
||||
public static function fromArray(array $component) {
|
||||
return (new static(
|
||||
$component['uuid'],
|
||||
$component['region'],
|
||||
$component['configuration'],
|
||||
$component['additional']
|
||||
))->setWeight($component['weight']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Drupal\layout_builder;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
@@ -13,7 +14,7 @@ use Symfony\Component\Routing\RouteCollection;
|
||||
* experimental modules and development releases of contributed modules.
|
||||
* See https://www.drupal.org/core/experimental for more information.
|
||||
*/
|
||||
interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface {
|
||||
interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface, AccessibleInterface {
|
||||
|
||||
/**
|
||||
* Returns an identifier for this storage.
|
||||
@@ -88,10 +89,14 @@ interface SectionStorageInterface extends SectionListInterface, PluginInspection
|
||||
/**
|
||||
* Gets the URL used to display the Layout Builder UI.
|
||||
*
|
||||
* @param string $rel
|
||||
* (optional) The link relationship type, for example: 'view' or 'disable'.
|
||||
* Defaults to 'view'.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The URL object.
|
||||
*/
|
||||
public function getLayoutBuilderUrl();
|
||||
public function getLayoutBuilderUrl($rel = 'view');
|
||||
|
||||
/**
|
||||
* Configures the plugin based on route values.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test fixture.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Add a layout plugin to an existing entity view display without explicitly
|
||||
// enabling Layout Builder for this display.
|
||||
$display = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.entity_view_display.block_content.basic.default')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$display = unserialize($display);
|
||||
$display['third_party_settings']['layout_builder']['sections'][] = [
|
||||
'layout_id' => 'layout_onecol',
|
||||
'layout_settings' => [],
|
||||
'components' => [
|
||||
'some-uuid' => [
|
||||
'uuid' => 'some-uuid',
|
||||
'region' => 'content',
|
||||
'configuration' => [
|
||||
'id' => 'system_powered_by_block',
|
||||
],
|
||||
'additional' => [],
|
||||
'weight' => 0,
|
||||
],
|
||||
],
|
||||
];
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($display),
|
||||
'collection' => '',
|
||||
'name' => 'core.entity_view_display.block_content.basic.default',
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.entity_view_display.block_content.basic.default')
|
||||
->execute();
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test fixture.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Enable Layout Builder on an existing entity view display.
|
||||
$display = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.entity_view_display.node.article.default')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$display = unserialize($display);
|
||||
$display['third_party_settings']['layout_builder']['enabled'] = TRUE;
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($display),
|
||||
'collection' => '',
|
||||
'name' => 'core.entity_view_display.node.article.default',
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.entity_view_display.node.article.default')
|
||||
->execute();
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test fixture.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Set the schema version.
|
||||
$connection->merge('key_value')
|
||||
->fields([
|
||||
'value' => 'i:8000;',
|
||||
'name' => 'layout_builder',
|
||||
'collection' => 'system.schema',
|
||||
])
|
||||
->condition('collection', 'system.schema')
|
||||
->condition('name', 'layout_builder')
|
||||
->execute();
|
||||
|
||||
// Update core.extension.
|
||||
$extensions = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$extensions = unserialize($extensions);
|
||||
$extensions['module']['layout_builder'] = 0;
|
||||
$extensions['module']['layout_discovery'] = 0;
|
||||
$extensions['module']['layout_test'] = 0;
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($extensions),
|
||||
'collection' => '',
|
||||
'name' => 'core.extension',
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute();
|
||||
@@ -9,27 +9,6 @@ use Drupal\Core\Database\Database;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Update core.extension.
|
||||
$extensions = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$extensions = unserialize($extensions);
|
||||
$extensions['module']['layout_builder'] = 0;
|
||||
$extensions['module']['layout_discovery'] = 0;
|
||||
$extensions['module']['layout_test'] = 0;
|
||||
$connection->update('config')
|
||||
->fields([
|
||||
'data' => serialize($extensions),
|
||||
'collection' => '',
|
||||
'name' => 'core.extension',
|
||||
])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute();
|
||||
|
||||
// Add a layout plugin with a dependency to an existing entity view display.
|
||||
$display = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
name: 'Layout Builder test'
|
||||
type: module
|
||||
description: 'Support module for testing layout building.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides hook implementations for Layout Builder tests.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_plugin_filter_TYPE__CONSUMER_alter().
|
||||
*/
|
||||
function layout_builder_test_plugin_filter_block__layout_builder_alter(array &$definitions, array $extra) {
|
||||
// Explicitly remove the "Help" blocks from the list.
|
||||
unset($definitions['help_block']);
|
||||
|
||||
// Explicitly remove the "Sticky at top of lists field_block".
|
||||
$disallowed_fields = [
|
||||
'sticky',
|
||||
];
|
||||
|
||||
// Remove "Changed" field if this is the first section.
|
||||
if ($extra['delta'] === 0) {
|
||||
$disallowed_fields[] = 'changed';
|
||||
}
|
||||
|
||||
foreach ($definitions as $plugin_id => $definition) {
|
||||
// Field block IDs are in the form 'field_block:{entity}:{bundle}:{name}',
|
||||
// for example 'field_block:node:article:revision_timestamp'.
|
||||
preg_match('/field_block:.*:.*:(.*)/', $plugin_id, $parts);
|
||||
if (isset($parts[1]) && in_array($parts[1], $disallowed_fields, TRUE)) {
|
||||
// Unset any field blocks that match our predefined list.
|
||||
unset($definitions[$plugin_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
function layout_builder_test_entity_extra_field_info() {
|
||||
$extra['node']['bundle_with_section_field']['display']['layout_builder_test'] = [
|
||||
'label' => t('Extra label'),
|
||||
'description' => t('Extra description'),
|
||||
'weight' => 0,
|
||||
];
|
||||
return $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_node_view().
|
||||
*/
|
||||
function layout_builder_test_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
|
||||
if ($display->getComponent('layout_builder_test')) {
|
||||
$build['layout_builder_test'] = [
|
||||
'#markup' => 'Extra, Extra read all about it.',
|
||||
];
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\layout_builder_test\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'TestAjax' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "layout_builder_test_testajax",
|
||||
* admin_label = @Translation("TestAjax"),
|
||||
* category = @Translation("Test")
|
||||
* )
|
||||
*/
|
||||
class TestAjaxBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$form['ajax_test'] = [
|
||||
'#type' => 'radios',
|
||||
'#options' => [
|
||||
1 => $this->t('Ajax test option 1'),
|
||||
2 => $this->t('Ajax test option 2'),
|
||||
],
|
||||
'#prefix' => '<div id="test-ajax-wrapper">',
|
||||
'#suffix' => '</div>',
|
||||
'#title' => $this->t('Time in this ajax test is @time', [
|
||||
'@time' => time(),
|
||||
]),
|
||||
'#ajax' => [
|
||||
'wrapper' => 'test-ajax-wrapper',
|
||||
'callback' => [$this, 'ajaxCallback'],
|
||||
],
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback.
|
||||
*/
|
||||
public function ajaxCallback($form, $form_state) {
|
||||
return $form['settings']['ajax_test'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$build['content'] = [
|
||||
'#markup' => $this->t('Every word is like an unnecessary stain on silence and nothingness.'),
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_block_view
|
||||
label: 'Test Block View'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: some
|
||||
options:
|
||||
items_per_page: 5
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
settings:
|
||||
link_to_entity: true
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
plugin_id: date
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
granularity: second
|
||||
title: 'Test Block View'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
block_1:
|
||||
display_plugin: block
|
||||
id: block_1
|
||||
display_title: Block
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- 'user.node_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
name: 'Layout Builder Views Test'
|
||||
type: module
|
||||
description: 'Support module for testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- views
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Remove all transitions for testing.
|
||||
*/
|
||||
* {
|
||||
/* CSS transitions. */
|
||||
-o-transition-property: none !important;
|
||||
-moz-transition-property: none !important;
|
||||
-ms-transition-property: none !important;
|
||||
-webkit-transition-property: none !important;
|
||||
transition-property: none !important;
|
||||
/* CSS transforms. */
|
||||
-o-transform: none !important;
|
||||
-moz-transform: none !important;
|
||||
-ms-transform: none !important;
|
||||
-webkit-transform: none !important;
|
||||
transform: none !important;
|
||||
/* CSS animations. */
|
||||
-webkit-animation: none !important;
|
||||
-moz-animation: none !important;
|
||||
-o-animation: none !important;
|
||||
-ms-animation: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
name: 'CSS Test fix'
|
||||
type: module
|
||||
description: 'Provides CSS fixes for tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
drupal.css_fix:
|
||||
version: VERSION
|
||||
css:
|
||||
theme:
|
||||
css/css_fix.theme.css: {}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Module for attaching CSS during tests.
|
||||
*
|
||||
* CSS pointer-events properties cause testing errors.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_page_attachments().
|
||||
*/
|
||||
function settings_tray_test_css_page_attachments(array &$attachments) {
|
||||
// Unconditionally attach an asset to the page.
|
||||
$attachments['#attached']['library'][] = 'settings_tray_test_css/drupal.css_fix';
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Drupal\Tests\layout_builder\Functional;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\views\Entity\View;
|
||||
|
||||
/**
|
||||
* Tests the Layout Builder UI.
|
||||
@@ -15,10 +17,13 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'views',
|
||||
'layout_builder',
|
||||
'layout_builder_views_test',
|
||||
'layout_test',
|
||||
'block',
|
||||
'node',
|
||||
'layout_builder_test',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -75,11 +80,32 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
|
||||
// From the manage display page, go to manage the layout.
|
||||
$this->drupalGet("$field_ui_prefix/display/default");
|
||||
$assert_session->linkNotExists('Manage layout');
|
||||
$assert_session->fieldDisabled('layout[allow_custom]');
|
||||
|
||||
$this->drupalPostForm(NULL, ['layout[enabled]' => TRUE], 'Save');
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->addressEquals("$field_ui_prefix/display-layout/default");
|
||||
// The body field is present.
|
||||
$assert_session->elementExists('css', '.field--name-body');
|
||||
// The body field is only present once.
|
||||
$assert_session->elementsCount('css', '.field--name-body', 1);
|
||||
// The extra field is only present once.
|
||||
$this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
|
||||
// Save the defaults.
|
||||
$assert_session->linkExists('Save Layout');
|
||||
$this->clickLink('Save Layout');
|
||||
$assert_session->addressEquals("$field_ui_prefix/display/default");
|
||||
|
||||
// Load the default layouts again after saving to confirm fields are only
|
||||
// added on new layouts.
|
||||
$this->drupalGet("$field_ui_prefix/display/default");
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->addressEquals("$field_ui_prefix/display-layout/default");
|
||||
// The body field is only present once.
|
||||
$assert_session->elementsCount('css', '.field--name-body', 1);
|
||||
// The extra field is only present once.
|
||||
$this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
|
||||
|
||||
// Add a new block.
|
||||
$assert_session->linkExists('Add Block');
|
||||
@@ -96,12 +122,15 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
// Save the defaults.
|
||||
$assert_session->linkExists('Save Layout');
|
||||
$this->clickLink('Save Layout');
|
||||
$assert_session->pageTextContains('The layout has been saved.');
|
||||
$assert_session->addressEquals("$field_ui_prefix/display/default");
|
||||
|
||||
// The node uses the defaults, no overrides available.
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The first node body');
|
||||
$assert_session->pageTextContains('Powered by Drupal');
|
||||
$assert_session->pageTextContains('Extra, Extra read all about it.');
|
||||
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
|
||||
$assert_session->linkNotExists('Layout');
|
||||
|
||||
// Enable overrides.
|
||||
@@ -111,6 +140,7 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
// Remove the section from the defaults.
|
||||
$assert_session->linkExists('Layout');
|
||||
$this->clickLink('Layout');
|
||||
$assert_session->pageTextContains('Placeholder for the "Extra label" field');
|
||||
$assert_session->linkExists('Remove section');
|
||||
$this->clickLink('Remove section');
|
||||
$page->pressButton('Remove');
|
||||
@@ -123,9 +153,12 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
$this->clickLink('Save Layout');
|
||||
$assert_session->pageTextNotContains('The first node body');
|
||||
$assert_session->pageTextNotContains('Powered by Drupal');
|
||||
$assert_session->pageTextNotContains('Extra, Extra read all about it.');
|
||||
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
|
||||
|
||||
// Assert that overrides cannot be turned off while overrides exist.
|
||||
$this->drupalGet("$field_ui_prefix/display/default");
|
||||
$assert_session->checkboxChecked('layout[allow_custom]');
|
||||
$assert_session->fieldDisabled('layout[allow_custom]');
|
||||
|
||||
// Alter the defaults.
|
||||
@@ -144,12 +177,16 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
$assert_session->pageTextContains('The second node title');
|
||||
$assert_session->pageTextContains('The second node body');
|
||||
$assert_session->pageTextContains('Powered by Drupal');
|
||||
$assert_session->pageTextContains('Extra, Extra read all about it.');
|
||||
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
|
||||
|
||||
// The overridden node does not pick up the changes to defaults.
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->elementNotExists('css', '.field--name-title');
|
||||
$assert_session->pageTextNotContains('The first node body');
|
||||
$assert_session->pageTextNotContains('Powered by Drupal');
|
||||
$assert_session->pageTextNotContains('Extra, Extra read all about it.');
|
||||
$assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
|
||||
$assert_session->linkExists('Layout');
|
||||
|
||||
// Reverting the override returns it to the defaults.
|
||||
@@ -161,6 +198,7 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
$assert_session->elementExists('css', '.field--name-title');
|
||||
$assert_session->pageTextContains('The first node body');
|
||||
$assert_session->pageTextContains('Powered by Drupal');
|
||||
$assert_session->pageTextContains('Placeholder for the "Extra label" field');
|
||||
|
||||
// Assert that overrides can be turned off now that all overrides are gone.
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => FALSE], 'Save');
|
||||
@@ -211,7 +249,9 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
$page->fillField('id', 'myothermenu');
|
||||
$page->pressButton('Save');
|
||||
|
||||
$this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default');
|
||||
$this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display', ['layout[enabled]' => TRUE], 'Save');
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->linkExists('Add Section');
|
||||
$this->clickLink('Add Section');
|
||||
$assert_session->linkExists('Layout plugin (with dependencies)');
|
||||
@@ -256,4 +296,163 @@ class LayoutBuilderTest extends BrowserTestBase {
|
||||
$assert_session->elementNotExists('css', '.block.menu--mymenu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the interaction between full and default view modes.
|
||||
*
|
||||
* @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getDefaultSectionStorage()
|
||||
*/
|
||||
public function testLayoutBuilderUiFullViewMode() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
|
||||
// Allow overrides for the layout.
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
|
||||
|
||||
// Customize the default view mode.
|
||||
$this->drupalGet("$field_ui_prefix/display-layout/default");
|
||||
$this->clickLink('Add Block');
|
||||
$this->clickLink('Powered by Drupal');
|
||||
$page->fillField('settings[label]', 'This is the default view mode');
|
||||
$page->checkField('settings[label_display]');
|
||||
$page->pressButton('Add Block');
|
||||
$assert_session->pageTextContains('This is the default view mode');
|
||||
$this->clickLink('Save Layout');
|
||||
|
||||
// The default view mode is used for both the node display and layout UI.
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('This is the default view mode');
|
||||
$this->drupalGet('node/1/layout');
|
||||
$assert_session->pageTextContains('This is the default view mode');
|
||||
$this->clickLink('Cancel Layout');
|
||||
|
||||
// Enable the full view mode and customize it.
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['display_modes_custom[full]' => TRUE], 'Save');
|
||||
$this->drupalGet("$field_ui_prefix/display-layout/full");
|
||||
$this->clickLink('Add Block');
|
||||
$this->clickLink('Powered by Drupal');
|
||||
$page->fillField('settings[label]', 'This is the full view mode');
|
||||
$page->checkField('settings[label_display]');
|
||||
$page->pressButton('Add Block');
|
||||
$assert_session->pageTextContains('This is the full view mode');
|
||||
$this->clickLink('Save Layout');
|
||||
|
||||
// The full view mode is now used for both the node display and layout UI.
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('This is the full view mode');
|
||||
$this->drupalGet('node/1/layout');
|
||||
$assert_session->pageTextContains('This is the full view mode');
|
||||
$this->clickLink('Cancel Layout');
|
||||
|
||||
// Disable the full view mode, the default should be used again.
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['display_modes_custom[full]' => FALSE], 'Save');
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('This is the default view mode');
|
||||
$this->drupalGet('node/1/layout');
|
||||
$assert_session->pageTextContains('This is the default view mode');
|
||||
$this->clickLink('Cancel Layout');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testLayoutBuilderChooseBlocksAlter() {
|
||||
// See layout_builder_test_plugin_filter_block__layout_builder_alter().
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
// From the manage display page, go to manage the layout.
|
||||
$this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save');
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
|
||||
// Add a new block.
|
||||
$this->clickLink('Add Block');
|
||||
|
||||
// Verify that blocks not modified are present.
|
||||
$assert_session->linkExists('Powered by Drupal');
|
||||
$assert_session->linkExists('Default revision');
|
||||
|
||||
// Verify that blocks explicitly removed are not present.
|
||||
$assert_session->linkNotExists('Help');
|
||||
$assert_session->linkNotExists('Sticky at top of lists');
|
||||
|
||||
// Verify that Changed block is not present on first section.
|
||||
$assert_session->linkNotExists('Changed');
|
||||
|
||||
// Go back to Manage layout.
|
||||
$this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
|
||||
$this->clickLink('Manage layout');
|
||||
|
||||
// Add a new section.
|
||||
$this->clickLink('Add Section', 1);
|
||||
$assert_session->linkExists('Two column');
|
||||
$this->clickLink('Two column');
|
||||
|
||||
// Add a new block to second section.
|
||||
$this->clickLink('Add Block', 1);
|
||||
|
||||
// Verify that Changed block is present on second section.
|
||||
$assert_session->linkExists('Changed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that deleting a View block used in Layout Builder works.
|
||||
*/
|
||||
public function testDeletedView() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
]));
|
||||
|
||||
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
|
||||
// Enable overrides.
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
|
||||
$this->drupalGet('node/1');
|
||||
|
||||
$assert_session->linkExists('Layout');
|
||||
$this->clickLink('Layout');
|
||||
$this->clickLink('Add Block');
|
||||
$this->clickLink('Test Block View');
|
||||
$page->pressButton('Add Block');
|
||||
|
||||
$assert_session->pageTextContains('Test Block View');
|
||||
$assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
|
||||
$this->clickLink('Save Layout');
|
||||
$assert_session->pageTextContains('Test Block View');
|
||||
$assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
|
||||
|
||||
View::load('test_block_view')->delete();
|
||||
$this->drupalGet('node/1');
|
||||
// Node can be loaded after deleting the View.
|
||||
$assert_session->pageTextContains(Node::load(1)->getTitle());
|
||||
$assert_session->pageTextNotContains('Test Block View');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a text string only appears once on the page.
|
||||
*
|
||||
* @param string $needle
|
||||
* The string to look for.
|
||||
*/
|
||||
protected function assertTextAppearsOnce($needle) {
|
||||
$this->assertEquals(1, substr_count($this->getSession()->getPage()->getContent(), $needle), "'$needle' only appears once on the page.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ class LayoutSectionTest extends BrowserTestBase {
|
||||
]);
|
||||
|
||||
LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default')
|
||||
->enableLayoutBuilder()
|
||||
->setOverridable()
|
||||
->save();
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\Functional\Update;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for Layout Builder extra fields.
|
||||
*
|
||||
* @group layout_builder
|
||||
* @group legacy
|
||||
*/
|
||||
class ExtraFieldUpdatePathTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../fixtures/update/layout-builder.php',
|
||||
__DIR__ . '/../../../fixtures/update/layout-builder-extra.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for Layout Builder extra fields.
|
||||
*/
|
||||
public function testRunUpdates() {
|
||||
// The default view mode has Layout Builder enabled.
|
||||
$data = EntityViewDisplay::load('node.article.default')->toArray();
|
||||
$this->assertArrayHasKey('third_party_settings', $data);
|
||||
$this->assertArrayNotHasKey('sections', $data['third_party_settings']['layout_builder']);
|
||||
|
||||
// The teaser view mode does not have Layout Builder enabled.
|
||||
$data = EntityViewDisplay::load('node.article.teaser')->toArray();
|
||||
$this->assertArrayNotHasKey('third_party_settings', $data);
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// The extra links have been added.
|
||||
$data = EntityViewDisplay::load('node.article.default')->toArray();
|
||||
$components = $data['third_party_settings']['layout_builder']['sections'][0]->getComponents();
|
||||
$component = reset($components);
|
||||
$this->assertSame('extra_field_block:node:article:links', $component->getPluginId());
|
||||
|
||||
// No extra links have been added.
|
||||
$data = EntityViewDisplay::load('node.article.teaser')->toArray();
|
||||
$this->assertArrayNotHasKey('third_party_settings', $data);
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for enabling Layout Builder.
|
||||
*
|
||||
* @see layout_builder_update_8601()
|
||||
*
|
||||
* @group layout_builder
|
||||
* @group legacy
|
||||
*/
|
||||
class LayoutBuilderEnableUpdatePathTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../fixtures/update/layout-builder.php',
|
||||
__DIR__ . '/../../../fixtures/update/layout-builder-enable.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the upgrade path for enabling Layout Builder.
|
||||
*/
|
||||
public function testRunUpdates() {
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$expected = [
|
||||
'sections' => [
|
||||
[
|
||||
'layout_id' => 'layout_onecol',
|
||||
'layout_settings' => [],
|
||||
'components' => [
|
||||
'some-uuid' => [
|
||||
'uuid' => 'some-uuid',
|
||||
'region' => 'content',
|
||||
'configuration' => [
|
||||
'id' => 'system_powered_by_block',
|
||||
],
|
||||
'additional' => [],
|
||||
'weight' => 0,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$this->assertLayoutBuilderSettings($expected, 'block_content', 'basic', 'default');
|
||||
$this->assertLayoutBuilderSettings(NULL, 'node', 'page', 'default');
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// The display with existing sections is enabled while the other is not.
|
||||
$expected['enabled'] = TRUE;
|
||||
$this->assertLayoutBuilderSettings($expected, 'block_content', 'basic', 'default');
|
||||
$this->assertLayoutBuilderSettings(NULL, 'node', 'page', 'default');
|
||||
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$this->drupalGet('admin/structure/block/block-content/manage/basic/display');
|
||||
$assert_session->checkboxChecked('layout[enabled]');
|
||||
$this->drupalGet('admin/structure/types/manage/page/display');
|
||||
$assert_session->checkboxNotChecked('layout[enabled]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the Layout Builder settings for a given display.
|
||||
*
|
||||
* @param mixed $expected
|
||||
* The expected value.
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param string $bundle
|
||||
* The bundle.
|
||||
* @param string $view_mode
|
||||
* The view mode.
|
||||
*/
|
||||
protected function assertLayoutBuilderSettings($expected, $entity_type_id, $bundle, $view_mode) {
|
||||
$this->assertEquals($expected, \Drupal::config("core.entity_view_display.$entity_type_id.$bundle.$view_mode")->get('third_party_settings.layout_builder'));
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -9,6 +9,7 @@ use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
* Tests the upgrade path for Layout Builder section dependencies.
|
||||
*
|
||||
* @group layout_builder
|
||||
* @group legacy
|
||||
*/
|
||||
class SectionDependenciesUpdatePathTest extends UpdatePathTestBase {
|
||||
|
||||
@@ -18,6 +19,7 @@ class SectionDependenciesUpdatePathTest extends UpdatePathTestBase {
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../fixtures/update/layout-builder.php',
|
||||
__DIR__ . '/../../../fixtures/update/section-dependencies.php',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Ajax blocks tests.
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class AjaxBlockTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'block',
|
||||
'node',
|
||||
'datetime',
|
||||
'layout_builder',
|
||||
'user',
|
||||
'layout_builder_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$user = $this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]);
|
||||
$user->save();
|
||||
$this->drupalLogin($user);
|
||||
$this->createContentType(['type' => 'bundle_with_section_field']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuring a field block for a user field.
|
||||
*/
|
||||
public function testAddAjaxBlock() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
// Start by creating a node.
|
||||
$node = $this->createNode([
|
||||
'type' => 'bundle_with_section_field',
|
||||
'body' => [
|
||||
[
|
||||
'value' => 'The node body',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$node->save();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The node body');
|
||||
$assert_session->pageTextNotContains('Every word is like an unnecessary stain on silence and nothingness.');
|
||||
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
|
||||
|
||||
// From the manage display page, go to manage the layout.
|
||||
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->addressEquals("$field_ui_prefix/display-layout/default");
|
||||
// The body field is present.
|
||||
$assert_session->elementExists('css', '.field--name-body');
|
||||
|
||||
// Add a new block.
|
||||
$assert_session->linkExists('Add Block');
|
||||
$this->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->linkExists('TestAjax');
|
||||
$this->clickLink('TestAjax');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
// Find the radio buttons.
|
||||
$name = 'settings[ajax_test]';
|
||||
/** @var \Behat\Mink\Element\NodeElement[] $radios */
|
||||
$radios = $this->cssSelect('input[name="' . $name . '"]');
|
||||
// Click them both a couple of times.
|
||||
foreach ([1, 2] as $rounds) {
|
||||
foreach ($radios as $radio) {
|
||||
$radio->click();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
}
|
||||
}
|
||||
// Then add the block.
|
||||
$page->pressButton('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$block_elements = $this->cssSelect('.block-layout-builder-test-testajax');
|
||||
// Should be exactly one of these in there.
|
||||
$this->assertEquals(1, count($block_elements));
|
||||
$assert_session->pageTextContains('Every word is like an unnecessary stain on silence and nothingness.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,14 +4,14 @@ namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\layout_builder\Plugin\Block\FieldBlock
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldBlockTest extends JavascriptTestBase {
|
||||
class FieldBlockTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Test access to private files in block fields on the Layout Builder.
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class InlineBlockPrivateFilesTest extends InlineBlockTestBase {
|
||||
|
||||
use FileFieldCreationTrait;
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'file',
|
||||
];
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Update the test node type to not create new revisions by default. This
|
||||
// allows testing for cases when a new revision is made and when it isn't.
|
||||
$node_type = NodeType::load('bundle_with_section_field');
|
||||
$node_type->setNewRevision(FALSE);
|
||||
$node_type->save();
|
||||
$field_settings = [
|
||||
'file_extensions' => 'txt',
|
||||
'uri_scheme' => 'private',
|
||||
];
|
||||
$this->createFileField('field_file', 'block_content', 'basic', $field_settings);
|
||||
$this->fileSystem = $this->container->get('file_system');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test access to private files added via inline blocks in the layout builder.
|
||||
*/
|
||||
public function testPrivateFiles() {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
// Enable layout builder and overrides.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Log in as user you can only configure layouts and access content.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'access content',
|
||||
]));
|
||||
$this->drupalGet('node/1/layout');
|
||||
$file = $this->createPrivateFile('drupal.txt');
|
||||
|
||||
$file_real_path = $this->fileSystem->realpath($file->getFileUri());
|
||||
$this->assertFileExists($file_real_path);
|
||||
$this->addInlineFileBlockToLayout('The file', $file);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$private_href1 = $this->assertFileAccessibleOnNode($file);
|
||||
|
||||
// Remove the inline block with the private file.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->removeInlineBlockFromLayout();
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains($file->label());
|
||||
// Try to access file directly after it has been removed. Since a new
|
||||
// revision was not created for the node the inline block is not in the
|
||||
// layout of a previous revision of the node.
|
||||
$this->drupalGet($private_href1);
|
||||
$assert_session->pageTextContains('You are not authorized to access this page');
|
||||
$assert_session->pageTextNotContains($this->getFileSecret($file));
|
||||
$this->assertFileExists($file_real_path);
|
||||
|
||||
$file2 = $this->createPrivateFile('2ndFile.txt');
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->addInlineFileBlockToLayout('Number2', $file2);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$private_href2 = $this->assertFileAccessibleOnNode($file2);
|
||||
|
||||
$this->createNewNodeRevision(1);
|
||||
|
||||
$file3 = $this->createPrivateFile('3rdFile.txt');
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->replaceFileInBlock($file3);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$private_href3 = $this->assertFileAccessibleOnNode($file3);
|
||||
|
||||
// $file2 is on a previous revision of the block which is on a previous
|
||||
// revision of the node. The user does not have access to view the previous
|
||||
// revision of the node.
|
||||
$this->drupalGet($private_href2);
|
||||
$assert_session->pageTextContains('You are not authorized to access this page');
|
||||
|
||||
$node = Node::load(1);
|
||||
$node->setUnpublished();
|
||||
$node->save();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('You are not authorized to access this page');
|
||||
$this->drupalGet($private_href3);
|
||||
$assert_session->pageTextNotContains($this->getFileSecret($file3));
|
||||
$assert_session->pageTextContains('You are not authorized to access this page');
|
||||
|
||||
$this->drupalGet('node/2/layout');
|
||||
$file4 = $this->createPrivateFile('drupal.txt');
|
||||
$this->addInlineFileBlockToLayout('The file', $file4);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/2');
|
||||
$private_href4 = $this->assertFileAccessibleOnNode($file4);
|
||||
|
||||
$this->createNewNodeRevision(2);
|
||||
|
||||
// Remove the inline block with the private file.
|
||||
// The inline block will still be attached to the previous revision of the
|
||||
// node.
|
||||
$this->drupalGet('node/2/layout');
|
||||
$this->removeInlineBlockFromLayout();
|
||||
$this->assertSaveLayout();
|
||||
|
||||
// Ensure that since the user cannot view the previous revision of the node
|
||||
// they can not view the file which is only used on that revision.
|
||||
$this->drupalGet($private_href4);
|
||||
$assert_session->pageTextContains('You are not authorized to access this page');
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the file in the block with another one.
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file entity.
|
||||
*/
|
||||
protected function replaceFileInBlock(FileInterface $file) {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->clickContextualLink(static::INLINE_BLOCK_LOCATOR, 'Configure');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$page->pressButton('Remove');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->attachFileToBlockForm($file);
|
||||
$page->pressButton('Update');
|
||||
$this->assertDialogClosedAndTextVisible($file->label(), static::INLINE_BLOCK_LOCATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entity block with a file.
|
||||
*
|
||||
* @param string $title
|
||||
* The title field value.
|
||||
* @param \Drupal\file\Entity\File $file
|
||||
* The file entity.
|
||||
*/
|
||||
protected function addInlineFileBlockToLayout($title, File $file) {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '.block-categories details:contains(Create new block)'));
|
||||
$this->clickLink('Basic block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->fieldValueEquals('Title', '');
|
||||
$page->findField('Title')->setValue($title);
|
||||
$this->attachFileToBlockForm($file);
|
||||
$page->pressButton('Add Block');
|
||||
$this->assertDialogClosedAndTextVisible($file->label(), static::INLINE_BLOCK_LOCATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a private file.
|
||||
*
|
||||
* @param string $file_name
|
||||
* The file name.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|\Drupal\file\Entity\File
|
||||
* The file entity.
|
||||
*/
|
||||
protected function createPrivateFile($file_name) {
|
||||
// Create a new file entity.
|
||||
$file = File::create([
|
||||
'uid' => 1,
|
||||
'filename' => $file_name,
|
||||
'uri' => "private://$file_name",
|
||||
'filemime' => 'text/plain',
|
||||
'status' => FILE_STATUS_PERMANENT,
|
||||
]);
|
||||
file_put_contents($file->getFileUri(), $this->getFileSecret($file));
|
||||
$file->save();
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a file is accessible on the page.
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file entity.
|
||||
*
|
||||
* @return string
|
||||
* The file href.
|
||||
*/
|
||||
protected function assertFileAccessibleOnNode(FileInterface $file) {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session->linkExists($file->label());
|
||||
$private_href = $page->findLink($file->label())->getAttribute('href');
|
||||
$page->clickLink($file->label());
|
||||
$assert_session->pageTextContains($this->getFileSecret($file));
|
||||
|
||||
// Access file directly.
|
||||
$this->drupalGet($private_href);
|
||||
$assert_session->pageTextContains($this->getFileSecret($file));
|
||||
return $private_href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text secret for a file.
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file entity.
|
||||
*
|
||||
* @return string
|
||||
* The text secret.
|
||||
*/
|
||||
protected function getFileSecret(FileInterface $file) {
|
||||
return "The secret in {$file->label()}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a file to the block edit form.
|
||||
*
|
||||
* @param \Drupal\file\FileInterface $file
|
||||
* The file to be attached.
|
||||
*/
|
||||
protected function attachFileToBlockForm(FileInterface $file) {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->attachFileToField("files[settings_block_form_field_file_0]", $this->fileSystem->realpath($file->getFileUri()));
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($assert_session->waitForLink($file->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new revision of the node.
|
||||
*
|
||||
* @param int $node_id
|
||||
* The node id.
|
||||
*/
|
||||
protected function createNewNodeRevision($node_id) {
|
||||
$node = Node::load($node_id);
|
||||
$node->setTitle('Update node');
|
||||
$node->setNewRevision();
|
||||
$node->save();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\node\Entity\Node;
|
||||
|
||||
/**
|
||||
* Tests that the inline block feature works correctly.
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class InlineBlockTest extends InlineBlockTestBase {
|
||||
|
||||
/**
|
||||
* Tests adding and editing of inline blocks.
|
||||
*/
|
||||
public function testInlineBlocks() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
// Enable layout builder.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
|
||||
// Add a basic block with the body field set.
|
||||
$this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/2');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
// Enable overrides.
|
||||
$this->drupalPostForm(static::FIELD_UI_PREFIX . '/display/default', ['layout[allow_custom]' => TRUE], 'Save');
|
||||
$this->drupalGet('node/1/layout');
|
||||
|
||||
// Confirm the block can be edited.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->configureInlineBlock('The DEFAULT block body', 'The NEW block body!');
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The NEW block body');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/2');
|
||||
// Node 2 should use default layout.
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains('The NEW block body');
|
||||
|
||||
// Add a basic block with the body field set.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->addInlineBlockToLayout('2nd Block title', 'The 2nd block body');
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The NEW block body!');
|
||||
$assert_session->pageTextContains('The 2nd block body');
|
||||
$this->drupalGet('node/2');
|
||||
// Node 2 should use default layout.
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains('The NEW block body');
|
||||
$assert_session->pageTextNotContains('The 2nd block body');
|
||||
|
||||
// Confirm the block can be edited.
|
||||
$this->drupalGet('node/1/layout');
|
||||
/* @var \Behat\Mink\Element\NodeElement $inline_block_2 */
|
||||
$inline_block_2 = $page->findAll('css', static::INLINE_BLOCK_LOCATOR)[1];
|
||||
$uuid = $inline_block_2->getAttribute('data-layout-block-uuid');
|
||||
$block_css_locator = static::INLINE_BLOCK_LOCATOR . "[data-layout-block-uuid=\"$uuid\"]";
|
||||
$this->configureInlineBlock('The 2nd block body', 'The 2nd NEW block body!', $block_css_locator);
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The NEW block body!');
|
||||
$assert_session->pageTextContains('The 2nd NEW block body!');
|
||||
$this->drupalGet('node/2');
|
||||
// Node 2 should use default layout.
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains('The NEW block body!');
|
||||
$assert_session->pageTextNotContains('The 2nd NEW block body!');
|
||||
|
||||
// The default layout entity block should be changed.
|
||||
$this->drupalGet(static::FIELD_UI_PREFIX . '/display-layout/default');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
// Confirm default layout still only has 1 entity block.
|
||||
$assert_session->elementsCount('css', static::INLINE_BLOCK_LOCATOR, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a new entity block and then not saving the layout.
|
||||
*
|
||||
* @dataProvider layoutNoSaveProvider
|
||||
*/
|
||||
public function testNoLayoutSave($operation, $no_save_link_text, $confirm_button_text) {
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
]));
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertEmpty($this->blockStorage->loadMultiple(), 'No entity blocks exist');
|
||||
// Enable layout builder and overrides.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->addInlineBlockToLayout('Block title', 'The block body');
|
||||
$this->clickLink($no_save_link_text);
|
||||
if ($confirm_button_text) {
|
||||
$page->pressButton($confirm_button_text);
|
||||
}
|
||||
$this->drupalGet('node/1');
|
||||
$this->assertEmpty($this->blockStorage->loadMultiple(), 'No entity blocks were created when layout is canceled.');
|
||||
$assert_session->pageTextNotContains('The block body');
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
|
||||
$this->addInlineBlockToLayout('Block title', 'The block body');
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The block body');
|
||||
$blocks = $this->blockStorage->loadMultiple();
|
||||
$this->assertEquals(count($blocks), 1);
|
||||
/* @var \Drupal\Core\Entity\ContentEntityBase $block */
|
||||
$block = array_pop($blocks);
|
||||
$revision_id = $block->getRevisionId();
|
||||
|
||||
// Confirm the block can be edited.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->configureInlineBlock('The block body', 'The block updated body');
|
||||
|
||||
$this->clickLink($no_save_link_text);
|
||||
if ($confirm_button_text) {
|
||||
$page->pressButton($confirm_button_text);
|
||||
}
|
||||
$this->drupalGet('node/1');
|
||||
|
||||
$blocks = $this->blockStorage->loadMultiple();
|
||||
// When reverting or canceling the update block should not be on the page.
|
||||
$assert_session->pageTextNotContains('The block updated body');
|
||||
if ($operation === 'cancel') {
|
||||
// When canceling the original block body should appear.
|
||||
$assert_session->pageTextContains('The block body');
|
||||
|
||||
$this->assertEquals(count($blocks), 1);
|
||||
$block = array_pop($blocks);
|
||||
$this->assertEquals($block->getRevisionId(), $revision_id);
|
||||
$this->assertEquals($block->get('body')->getValue()[0]['value'], 'The block body');
|
||||
}
|
||||
else {
|
||||
// The block should not be visible.
|
||||
// Blocks are currently only deleted when the parent entity is deleted.
|
||||
$assert_session->pageTextNotContains('The block body');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for ::testNoLayoutSave().
|
||||
*/
|
||||
public function layoutNoSaveProvider() {
|
||||
return [
|
||||
'cancel' => [
|
||||
'cancel',
|
||||
'Cancel Layout',
|
||||
NULL,
|
||||
],
|
||||
'revert' => [
|
||||
'revert',
|
||||
'Revert to defaults',
|
||||
'Revert',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity blocks revisioning.
|
||||
*/
|
||||
public function testInlineBlocksRevisioning() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
]));
|
||||
// Enable layout builder and overrides.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
$this->drupalGet('node/1/layout');
|
||||
|
||||
// Add an inline block.
|
||||
$this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
/** @var \Drupal\node\NodeStorageInterface $node_storage */
|
||||
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
|
||||
$original_revision_id = $node_storage->getLatestRevisionId(1);
|
||||
|
||||
// Create a new revision.
|
||||
$this->drupalGet('node/1/edit');
|
||||
$page->findField('title[0][value]')->setValue('Node updated');
|
||||
$page->pressButton('Save');
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$assert_session->linkExists('Revisions');
|
||||
|
||||
// Update the block.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->configureInlineBlock('The DEFAULT block body', 'The NEW block body');
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The NEW block body');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
|
||||
$revision_url = "node/1/revisions/$original_revision_id";
|
||||
|
||||
// Ensure viewing the previous revision shows the previous block revision.
|
||||
$this->drupalGet("$revision_url/view");
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains('The NEW block body');
|
||||
|
||||
// Revert to first revision.
|
||||
$revision_url = "$revision_url/revert";
|
||||
$this->drupalGet($revision_url);
|
||||
$page->pressButton('Revert');
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains('The NEW block body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that entity blocks deleted correctly.
|
||||
*/
|
||||
public function testDeletion() {
|
||||
/** @var \Drupal\Core\Cron $cron */
|
||||
$cron = \Drupal::service('cron');
|
||||
/** @var \Drupal\layout_builder\InlineBlockUsage $usage */
|
||||
$usage = \Drupal::service('inline_block.usage');
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer content types',
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
]));
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Enable layout builder.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
// Add a block to default layout.
|
||||
$this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
|
||||
$this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->assertCount(1, $this->blockStorage->loadMultiple());
|
||||
$default_block_id = $this->getLatestBlockEntityId();
|
||||
|
||||
// Ensure the block shows up on node pages.
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/2');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
// Enable overrides.
|
||||
$this->drupalPostForm(static::FIELD_UI_PREFIX . '/display/default', ['layout[allow_custom]' => TRUE], 'Save');
|
||||
|
||||
// Ensure we have 2 copies of the block in node overrides.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->assertSaveLayout();
|
||||
$node_1_block_id = $this->getLatestBlockEntityId();
|
||||
|
||||
$this->drupalGet('node/2/layout');
|
||||
$this->assertSaveLayout();
|
||||
$node_2_block_id = $this->getLatestBlockEntityId();
|
||||
$this->assertCount(3, $this->blockStorage->loadMultiple());
|
||||
|
||||
$this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
|
||||
|
||||
$this->assertNotEmpty($this->blockStorage->load($default_block_id));
|
||||
$this->assertNotEmpty($usage->getUsage($default_block_id));
|
||||
// Remove block from default.
|
||||
$this->removeInlineBlockFromLayout();
|
||||
$this->assertSaveLayout();
|
||||
// Ensure the block in the default was deleted.
|
||||
$this->blockStorage->resetCache([$default_block_id]);
|
||||
$this->assertEmpty($this->blockStorage->load($default_block_id));
|
||||
// Ensure other blocks still exist.
|
||||
$this->assertCount(2, $this->blockStorage->loadMultiple());
|
||||
$this->assertEmpty($usage->getUsage($default_block_id));
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$this->removeInlineBlockFromLayout();
|
||||
$this->assertSaveLayout();
|
||||
$cron->run();
|
||||
// Ensure entity block is not deleted because it is needed in revision.
|
||||
$this->assertNotEmpty($this->blockStorage->load($node_1_block_id));
|
||||
$this->assertCount(2, $this->blockStorage->loadMultiple());
|
||||
|
||||
$this->assertNotEmpty($usage->getUsage($node_1_block_id));
|
||||
// Ensure entity block is deleted when node is deleted.
|
||||
$this->drupalGet('node/1/delete');
|
||||
$page->pressButton('Delete');
|
||||
$this->assertEmpty(Node::load(1));
|
||||
$cron->run();
|
||||
$this->assertEmpty($this->blockStorage->load($node_1_block_id));
|
||||
$this->assertEmpty($usage->getUsage($node_1_block_id));
|
||||
$this->assertCount(1, $this->blockStorage->loadMultiple());
|
||||
|
||||
// Add another block to the default.
|
||||
$this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
|
||||
$this->clickLink('Manage layout');
|
||||
$assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
|
||||
$this->addInlineBlockToLayout('Title 2', 'Body 2');
|
||||
$this->assertSaveLayout();
|
||||
$cron->run();
|
||||
$default_block2_id = $this->getLatestBlockEntityId();
|
||||
$this->assertCount(2, $this->blockStorage->loadMultiple());
|
||||
|
||||
// Delete the other node so bundle can be deleted.
|
||||
$this->assertNotEmpty($usage->getUsage($node_2_block_id));
|
||||
$this->drupalGet('node/2/delete');
|
||||
$page->pressButton('Delete');
|
||||
$this->assertEmpty(Node::load(2));
|
||||
$cron->run();
|
||||
// Ensure entity block was deleted.
|
||||
$this->assertEmpty($this->blockStorage->load($node_2_block_id));
|
||||
$this->assertEmpty($usage->getUsage($node_2_block_id));
|
||||
$this->assertCount(1, $this->blockStorage->loadMultiple());
|
||||
|
||||
// Delete the bundle which has the default layout.
|
||||
$this->assertNotEmpty($usage->getUsage($default_block2_id));
|
||||
$this->drupalGet(static::FIELD_UI_PREFIX . '/delete');
|
||||
$page->pressButton('Delete');
|
||||
$cron->run();
|
||||
|
||||
// Ensure the entity block in default is deleted when bundle is deleted.
|
||||
$this->assertEmpty($this->blockStorage->load($default_block2_id));
|
||||
$this->assertEmpty($usage->getUsage($default_block2_id));
|
||||
$this->assertCount(0, $this->blockStorage->loadMultiple());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests access to the block edit form of inline blocks.
|
||||
*
|
||||
* This module does not provide links to these forms but in case the paths are
|
||||
* accessed directly they should accessible by users with the
|
||||
* 'configure any layout' permission.
|
||||
*
|
||||
* @see layout_builder_block_content_access()
|
||||
*/
|
||||
public function testAccess() {
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Enable layout builder and overrides.
|
||||
$this->drupalPostForm(
|
||||
static::FIELD_UI_PREFIX . '/display/default',
|
||||
['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
|
||||
'Save'
|
||||
);
|
||||
|
||||
// Ensure we have 2 copies of the block in node overrides.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->addInlineBlockToLayout('Block title', 'Block body');
|
||||
$this->assertSaveLayout();
|
||||
$node_1_block_id = $this->getLatestBlockEntityId();
|
||||
|
||||
$this->drupalGet("block/$node_1_block_id");
|
||||
$assert_session->pageTextNotContains('You are not authorized to access this page');
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer nodes',
|
||||
]));
|
||||
|
||||
$this->drupalGet("block/$node_1_block_id");
|
||||
$assert_session->pageTextContains('You are not authorized to access this page');
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
]));
|
||||
$this->drupalGet("block/$node_1_block_id");
|
||||
$assert_session->pageTextNotContains('You are not authorized to access this page');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
|
||||
|
||||
/**
|
||||
* Base class for testing inline blocks.
|
||||
*/
|
||||
abstract class InlineBlockTestBase extends WebDriverTestBase {
|
||||
|
||||
use ContextualLinkClickTrait;
|
||||
|
||||
/**
|
||||
* Locator for inline blocks.
|
||||
*/
|
||||
const INLINE_BLOCK_LOCATOR = '.block-inline-blockbasic';
|
||||
|
||||
/**
|
||||
* Path prefix for the field UI for the test bundle.
|
||||
*/
|
||||
const FIELD_UI_PREFIX = 'admin/structure/types/manage/bundle_with_section_field';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'block_content',
|
||||
'layout_builder',
|
||||
'block',
|
||||
'node',
|
||||
'contextual',
|
||||
// @todo Remove after https://www.drupal.org/project/drupal/issues/2901792.
|
||||
'no_transitions_css',
|
||||
];
|
||||
|
||||
/**
|
||||
* The block storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $blockStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// @todo The Layout Builder UI relies on local tasks; fix in
|
||||
// https://www.drupal.org/project/drupal/issues/2917777.
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
|
||||
$this->createContentType(['type' => 'bundle_with_section_field', 'new_revision' => TRUE]);
|
||||
$this->createNode([
|
||||
'type' => 'bundle_with_section_field',
|
||||
'title' => 'The node title',
|
||||
'body' => [
|
||||
[
|
||||
'value' => 'The node body',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->createNode([
|
||||
'type' => 'bundle_with_section_field',
|
||||
'title' => 'The node2 title',
|
||||
'body' => [
|
||||
[
|
||||
'value' => 'The node2 body',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$bundle = BlockContentType::create([
|
||||
'id' => 'basic',
|
||||
'label' => 'Basic block',
|
||||
'revision' => 1,
|
||||
]);
|
||||
$bundle->save();
|
||||
block_content_add_body_field($bundle->id());
|
||||
|
||||
$this->blockStorage = $this->container->get('entity_type.manager')->getStorage('block_content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a layout and asserts the message is correct.
|
||||
*/
|
||||
protected function assertSaveLayout() {
|
||||
$assert_session = $this->assertSession();
|
||||
$assert_session->linkExists('Save Layout');
|
||||
// Go to the Save Layout page. Currently there are random test failures if
|
||||
// 'clickLink()' is used.
|
||||
// @todo Convert tests that extend this class to NightWatch tests in
|
||||
// https://www.drupal.org/node/2984161
|
||||
$link = $this->getSession()->getPage()->findLink('Save Layout');
|
||||
$this->drupalGet($link->getAttribute('href'));
|
||||
$this->assertNotEmpty($assert_session->waitForElement('css', '.messages--status'));
|
||||
|
||||
if (stristr($this->getUrl(), 'admin/structure') === FALSE) {
|
||||
$assert_session->pageTextContains('The layout override has been saved.');
|
||||
}
|
||||
else {
|
||||
$assert_session->pageTextContains('The layout has been saved.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest block entity id.
|
||||
*/
|
||||
protected function getLatestBlockEntityId() {
|
||||
$block_ids = \Drupal::entityQuery('block_content')->sort('id', 'DESC')->range(0, 1)->execute();
|
||||
$block_id = array_pop($block_ids);
|
||||
$this->assertNotEmpty($this->blockStorage->load($block_id));
|
||||
return $block_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an entity block from the layout but does not save the layout.
|
||||
*/
|
||||
protected function removeInlineBlockFromLayout() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$block_text = $page->find('css', static::INLINE_BLOCK_LOCATOR)->getText();
|
||||
$this->assertNotEmpty($block_text);
|
||||
$assert_session->pageTextContains($block_text);
|
||||
$this->clickContextualLink(static::INLINE_BLOCK_LOCATOR, 'Remove block');
|
||||
$assert_session->waitForElement('css', "#drupal-off-canvas input[value='Remove']");
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$page->find('css', '#drupal-off-canvas')->pressButton('Remove');
|
||||
$this->waitForNoElement('#drupal-off-canvas');
|
||||
$this->waitForNoElement(static::INLINE_BLOCK_LOCATOR);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextNotContains($block_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entity block to the layout.
|
||||
*
|
||||
* @param string $title
|
||||
* The title field value.
|
||||
* @param string $body
|
||||
* The body field value.
|
||||
*/
|
||||
protected function addInlineBlockToLayout($title, $body) {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '.block-categories details:contains(Create new block)'));
|
||||
$this->clickLink('Basic block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$textarea = $assert_session->waitForElement('css', '[name="settings[block_form][body][0][value]"]');
|
||||
$this->assertNotEmpty($textarea);
|
||||
$assert_session->fieldValueEquals('Title', '');
|
||||
$page->findField('Title')->setValue($title);
|
||||
$textarea->setValue($body);
|
||||
$page->pressButton('Add Block');
|
||||
$this->assertDialogClosedAndTextVisible($body, static::INLINE_BLOCK_LOCATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an inline block in the Layout Builder.
|
||||
*
|
||||
* @param string $old_body
|
||||
* The old body field value.
|
||||
* @param string $new_body
|
||||
* The new body field value.
|
||||
* @param string $block_css_locator
|
||||
* The CSS locator to use to select the contextual link.
|
||||
*/
|
||||
protected function configureInlineBlock($old_body, $new_body, $block_css_locator = NULL) {
|
||||
$block_css_locator = $block_css_locator ?: static::INLINE_BLOCK_LOCATOR;
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->clickContextualLink($block_css_locator, 'Configure');
|
||||
$textarea = $assert_session->waitForElementVisible('css', '[name="settings[block_form][body][0][value]"]');
|
||||
$this->assertNotEmpty($textarea);
|
||||
$this->assertSame($old_body, $textarea->getValue());
|
||||
$textarea->setValue($new_body);
|
||||
$page->pressButton('Update');
|
||||
$this->waitForNoElement('#drupal-off-canvas');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertDialogClosedAndTextVisible($new_body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an element to be removed from the page.
|
||||
*
|
||||
* @param string $selector
|
||||
* CSS selector.
|
||||
* @param int $timeout
|
||||
* (optional) Timeout in milliseconds, defaults to 10000.
|
||||
*
|
||||
* @todo Remove in https://www.drupal.org/node/2892440.
|
||||
*/
|
||||
protected function waitForNoElement($selector, $timeout = 10000) {
|
||||
$condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)";
|
||||
$this->assertJsCondition($condition, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the dialog closes and the new text appears on the main canvas.
|
||||
*
|
||||
* @param string $text
|
||||
* The text.
|
||||
* @param string|null $css_locator
|
||||
* The css locator to use inside the main canvas if any.
|
||||
*/
|
||||
protected function assertDialogClosedAndTextVisible($text, $css_locator = NULL) {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->waitForNoElement('#drupal-off-canvas');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->elementNotExists('css', '#drupal-off-canvas');
|
||||
if ($css_locator) {
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', ".dialog-off-canvas-main-canvas $css_locator:contains('$text')"));
|
||||
}
|
||||
else {
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', ".dialog-off-canvas-main-canvas:contains('$text')"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Field blocks tests for the override layout.
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class ItemLayoutFieldBlockTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'layout_builder',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
// We need more then one content type for this test.
|
||||
$this->createContentType(['type' => 'bundle_with_layout_overrides']);
|
||||
$this->createContentType(['type' => 'filler_bundle']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuring a field block for a user field.
|
||||
*/
|
||||
public function testAddAjaxBlock() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Allow overrides for the layout.
|
||||
$this->drupalGet('admin/structure/types/manage/bundle_with_layout_overrides/display/default');
|
||||
$page->checkField('layout[enabled]');
|
||||
$page->checkField('layout[allow_custom]');
|
||||
$page->pressButton('Save');
|
||||
|
||||
// Start by creating a node of type with layout overrides.
|
||||
$node = $this->createNode([
|
||||
'type' => 'bundle_with_layout_overrides',
|
||||
'body' => [
|
||||
[
|
||||
'value' => 'The node body',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
// Open single item layout page.
|
||||
$this->drupalGet('node/1/layout');
|
||||
|
||||
// Add a new block.
|
||||
$this->clickLink('Add Block');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
|
||||
// Validate that only field blocks for layouted bundle are present.
|
||||
$valid_links = $page->findAll('css', 'a[href$="field_block%3Anode%3Abundle_with_layout_overrides%3Abody"]');
|
||||
$this->assertCount(1, $valid_links);
|
||||
$invalid_links = $page->findAll('css', 'a[href$="field_block%3Anode%3Afiller_bundle%3Abody"]');
|
||||
$this->assertCount(0, $invalid_links);
|
||||
}
|
||||
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the ability for opting in and out of Layout Builder.
|
||||
*
|
||||
* @group layout_builder
|
||||
*/
|
||||
class LayoutBuilderOptInTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// @todo The Layout Builder UI relies on local tasks; fix in
|
||||
// https://www.drupal.org/project/drupal/issues/2917777.
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
|
||||
// Create one content type before installing Layout Builder and one after.
|
||||
$this->createContentType(['type' => 'before']);
|
||||
$this->container->get('module_installer')->install(['layout_builder']);
|
||||
$this->rebuildAll();
|
||||
$this->createContentType(['type' => 'after']);
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the interaction between the two layout checkboxes.
|
||||
*/
|
||||
public function testCheckboxLogic() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$this->drupalGet('admin/structure/types/manage/before/display/default');
|
||||
// Both fields are unchecked and allow_custom is disabled and hidden.
|
||||
$assert_session->checkboxNotChecked('layout[enabled]');
|
||||
$assert_session->checkboxNotChecked('layout[allow_custom]');
|
||||
$assert_session->fieldDisabled('layout[allow_custom]');
|
||||
$this->assertFalse($page->findField('layout[allow_custom]')->isVisible());
|
||||
|
||||
// Checking is_enable will show allow_custom.
|
||||
$page->checkField('layout[enabled]');
|
||||
$assert_session->checkboxNotChecked('layout[allow_custom]');
|
||||
$this->assertTrue($page->findField('layout[allow_custom]')->isVisible());
|
||||
$page->pressButton('Save');
|
||||
$assert_session->checkboxChecked('layout[enabled]');
|
||||
$assert_session->checkboxNotChecked('layout[allow_custom]');
|
||||
|
||||
// Check and submit allow_custom.
|
||||
$page->checkField('layout[allow_custom]');
|
||||
$page->pressButton('Save');
|
||||
$assert_session->checkboxChecked('layout[enabled]');
|
||||
$assert_session->checkboxChecked('layout[allow_custom]');
|
||||
|
||||
// Reset the checkboxes.
|
||||
$page->uncheckField('layout[enabled]');
|
||||
$page->pressButton('Save');
|
||||
$page->pressButton('Confirm');
|
||||
$assert_session->checkboxNotChecked('layout[enabled]');
|
||||
$assert_session->checkboxNotChecked('layout[allow_custom]');
|
||||
|
||||
// Check both at the same time.
|
||||
$page->checkField('layout[enabled]');
|
||||
$page->checkField('layout[allow_custom]');
|
||||
$page->pressButton('Save');
|
||||
$assert_session->checkboxChecked('layout[enabled]');
|
||||
$assert_session->checkboxChecked('layout[allow_custom]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the expected default values for enabling Layout Builder.
|
||||
*/
|
||||
public function testDefaultValues() {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Both the content type created before and after Layout Builder was
|
||||
// installed is still using the Field UI.
|
||||
$this->drupalGet('admin/structure/types/manage/before/display/default');
|
||||
$assert_session->checkboxNotChecked('layout[enabled]');
|
||||
|
||||
$field_ui_prefix = 'admin/structure/types/manage/after/display/default';
|
||||
$this->drupalGet($field_ui_prefix);
|
||||
$assert_session->checkboxNotChecked('layout[enabled]');
|
||||
$page->checkField('layout[enabled]');
|
||||
$page->pressButton('Save');
|
||||
|
||||
$layout_builder_ui = $this->getPathForFieldBlock('node', 'after', 'default', 'body');
|
||||
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
// Ensure the body appears once and only once.
|
||||
$assert_session->elementsCount('css', '.field--name-body', 1);
|
||||
|
||||
// Change the body formatter to Trimmed.
|
||||
$this->drupalGet($layout_builder_ui);
|
||||
$assert_session->fieldValueEquals('settings[formatter][type]', 'text_default');
|
||||
$page->selectFieldOption('settings[formatter][type]', 'text_trimmed');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$page->pressButton('Update');
|
||||
$assert_session->linkExists('Save Layout');
|
||||
$this->clickLink('Save Layout');
|
||||
|
||||
$this->drupalGet($layout_builder_ui);
|
||||
$assert_session->fieldValueEquals('settings[formatter][type]', 'text_trimmed');
|
||||
|
||||
// Disable Layout Builder.
|
||||
$this->drupalPostForm($field_ui_prefix, ['layout[enabled]' => FALSE], 'Save');
|
||||
$page->pressButton('Confirm');
|
||||
|
||||
// The Layout Builder UI is no longer accessible.
|
||||
$this->drupalGet($layout_builder_ui);
|
||||
$assert_session->pageTextContains('You are not authorized to access this page.');
|
||||
|
||||
// The original body formatter is reflected in Field UI.
|
||||
$this->drupalGet($field_ui_prefix);
|
||||
$assert_session->fieldValueEquals('fields[body][type]', 'text_default');
|
||||
|
||||
// Change the body formatter to Summary.
|
||||
$page->selectFieldOption('fields[body][type]', 'text_summary_or_trimmed');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$page->pressButton('Save');
|
||||
$assert_session->fieldValueEquals('fields[body][type]', 'text_summary_or_trimmed');
|
||||
|
||||
// Reactivate Layout Builder.
|
||||
$this->drupalPostForm($field_ui_prefix, ['layout[enabled]' => TRUE], 'Save');
|
||||
$assert_session->linkExists('Manage layout');
|
||||
$this->clickLink('Manage layout');
|
||||
// Ensure the body appears once and only once.
|
||||
$assert_session->elementsCount('css', '.field--name-body', 1);
|
||||
|
||||
// The changed body formatter is reflected in Layout Builder UI.
|
||||
$this->drupalGet($this->getPathForFieldBlock('node', 'after', 'default', 'body'));
|
||||
$assert_session->fieldValueEquals('settings[formatter][type]', 'text_summary_or_trimmed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to update a field block in the UI.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param string $bundle
|
||||
* The bundle.
|
||||
* @param string $view_mode
|
||||
* The view mode.
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
*
|
||||
* @return string
|
||||
* The path.
|
||||
*/
|
||||
protected function getPathForFieldBlock($entity_type_id, $bundle, $view_mode, $field_name) {
|
||||
$delta = 0;
|
||||
/** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $display */
|
||||
$display = $this->container->get('entity_type.manager')->getStorage('entity_view_display')->load("$entity_type_id.$bundle.$view_mode");
|
||||
$body_component = NULL;
|
||||
foreach ($display->getSection($delta)->getComponents() as $component) {
|
||||
if ($component->getPluginId() === "field_block:$entity_type_id:$bundle:$field_name") {
|
||||
$body_component = $component;
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($body_component);
|
||||
return 'layout_builder/update/block/defaults/node.after.default/0/content/' . $body_component->getUuid();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,13 +7,19 @@ use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterPluginManager;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\Plugin\Context\EntityContextDefinition;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||
use Drupal\layout_builder\Plugin\Block\FieldBlock;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Promise\PromiseInterface;
|
||||
use Prophecy\Promise\ReturnPromise;
|
||||
use Prophecy\Promise\ThrowPromise;
|
||||
use Prophecy\Prophecy\ProphecyInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\layout_builder\Plugin\Block\FieldBlock
|
||||
@@ -21,6 +27,30 @@ use Prophecy\Prophecy\ProphecyInterface;
|
||||
*/
|
||||
class FieldBlockTest extends EntityKernelTestBase {
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
|
||||
*/
|
||||
protected $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class);
|
||||
$this->logger = $this->prophesize(LoggerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity access.
|
||||
*
|
||||
@@ -177,10 +207,9 @@ class FieldBlockTest extends EntityKernelTestBase {
|
||||
'admin_label' => 'Test Block',
|
||||
'bundles' => ['entity_test'],
|
||||
'context' => [
|
||||
'entity' => new ContextDefinition('entity:entity_test', 'Test', TRUE),
|
||||
'entity' => EntityContextDefinition::fromEntityTypeId('entity_test')->setLabel('Test'),
|
||||
],
|
||||
];
|
||||
$entity_field_manager = $this->prophesize(EntityFieldManagerInterface::class);
|
||||
$formatter_manager = $this->prophesize(FormatterPluginManager::class);
|
||||
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
|
||||
|
||||
@@ -188,12 +217,93 @@ class FieldBlockTest extends EntityKernelTestBase {
|
||||
$configuration,
|
||||
'field_block:entity_test:entity_test:the_field_name',
|
||||
$plugin_definition,
|
||||
$entity_field_manager->reveal(),
|
||||
$this->entityFieldManager->reveal(),
|
||||
$formatter_manager->reveal(),
|
||||
$module_handler->reveal()
|
||||
$module_handler->reveal(),
|
||||
$this->logger->reveal()
|
||||
);
|
||||
$block->setContextValue('entity', $entity_prophecy->reveal());
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::build
|
||||
* @dataProvider providerTestBuild
|
||||
*/
|
||||
public function testBuild(PromiseInterface $promise, $in_preview, $expected_markup, $log_message = '', $log_arguments = []) {
|
||||
$entity = $this->prophesize(FieldableEntityInterface::class);
|
||||
$field = $this->prophesize(FieldItemListInterface::class);
|
||||
$entity->get('the_field_name')->willReturn($field->reveal());
|
||||
$entity->in_preview = $in_preview;
|
||||
$field->view(Argument::type('array'))->will($promise);
|
||||
|
||||
$field_definition = $this->prophesize(FieldDefinitionInterface::class);
|
||||
$field_definition->getLabel()->willReturn('The Field Label');
|
||||
$this->entityFieldManager->getFieldDefinitions('entity_test', 'entity_test')->willReturn(['the_field_name' => $field_definition]);
|
||||
|
||||
if ($log_message) {
|
||||
$this->logger->warning($log_message, $log_arguments)->shouldBeCalled();
|
||||
}
|
||||
else {
|
||||
$this->logger->warning(Argument::cetera())->shouldNotBeCalled();
|
||||
}
|
||||
|
||||
$block = $this->getTestBlock($entity);
|
||||
$expected = [
|
||||
'#cache' => [
|
||||
'contexts' => [],
|
||||
'tags' => [],
|
||||
'max-age' => 0,
|
||||
],
|
||||
];
|
||||
if ($expected_markup) {
|
||||
$expected['content']['#markup'] = $expected_markup;
|
||||
}
|
||||
|
||||
$actual = $block->build();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for ::testBuild().
|
||||
*/
|
||||
public function providerTestBuild() {
|
||||
$data = [];
|
||||
$data['array, no preview'] = [
|
||||
new ReturnPromise([['content' => ['#markup' => 'The field value']]]),
|
||||
FALSE,
|
||||
'The field value',
|
||||
];
|
||||
$data['array, preview'] = [
|
||||
new ReturnPromise([['content' => ['#markup' => 'The field value']]]),
|
||||
TRUE,
|
||||
'The field value',
|
||||
];
|
||||
$data['empty array, no preview'] = [
|
||||
new ReturnPromise([[]]),
|
||||
FALSE,
|
||||
'',
|
||||
];
|
||||
$data['empty array, preview'] = [
|
||||
new ReturnPromise([[]]),
|
||||
TRUE,
|
||||
'Placeholder for the "The Field Label" field',
|
||||
];
|
||||
$data['exception, no preview'] = [
|
||||
new ThrowPromise(new \Exception('The exception message')),
|
||||
FALSE,
|
||||
'',
|
||||
'The field "%field" failed to render with the error of "%error".',
|
||||
['%field' => 'the_field_name', '%error' => 'The exception message'],
|
||||
];
|
||||
$data['exception, preview'] = [
|
||||
new ThrowPromise(new \Exception('The exception message')),
|
||||
TRUE,
|
||||
'Placeholder for the "The Field Label" field',
|
||||
'The field "%field" failed to render with the error of "%error".',
|
||||
['%field' => 'the_field_name', '%error' => 'The exception message'],
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-5
@@ -23,7 +23,7 @@ abstract class LayoutBuilderCompatibilityTestBase extends EntityKernelTestBase {
|
||||
/**
|
||||
* The entity view display.
|
||||
*
|
||||
* @var \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface
|
||||
* @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
@@ -86,13 +86,21 @@ abstract class LayoutBuilderCompatibilityTestBase extends EntityKernelTestBase {
|
||||
/**
|
||||
* Installs the Layout Builder.
|
||||
*
|
||||
* Also configures and reloads the entity display, and reloads the entity.
|
||||
* Also configures and reloads the entity display.
|
||||
*/
|
||||
protected function installLayoutBuilder() {
|
||||
$this->container->get('module_installer')->install(['layout_builder']);
|
||||
$this->refreshServices();
|
||||
|
||||
$this->display = $this->reloadEntity($this->display);
|
||||
$this->display->enableLayoutBuilder()->save();
|
||||
$this->entity = $this->reloadEntity($this->entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables overrides for the display and reloads the entity.
|
||||
*/
|
||||
protected function enableOverrides() {
|
||||
$this->display->setOverridable()->save();
|
||||
$this->entity = $this->reloadEntity($this->entity);
|
||||
}
|
||||
@@ -104,9 +112,6 @@ abstract class LayoutBuilderCompatibilityTestBase extends EntityKernelTestBase {
|
||||
* The entity to render.
|
||||
* @param array $attributes
|
||||
* An array of field attributes to assert.
|
||||
*
|
||||
* @return string
|
||||
* The rendered string output (typically HTML).
|
||||
*/
|
||||
protected function assertFieldAttributes(EntityInterface $entity, array $attributes) {
|
||||
$view_builder = $this->container->get('entity_type.manager')->getViewBuilder($entity->getEntityTypeId());
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ class LayoutBuilderFieldLayoutCompatibilityTest extends LayoutBuilderCompatibili
|
||||
$this->assertFieldAttributes($this->entity, $expected_fields);
|
||||
|
||||
// Add a layout override.
|
||||
$this->enableOverrides();
|
||||
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
|
||||
$field_list = $this->entity->get('layout_builder__layout');
|
||||
$field_list->appendSection(new Section('layout_onecol'));
|
||||
|
||||
@@ -33,6 +33,8 @@ class LayoutBuilderInstallTest extends LayoutBuilderCompatibilityTestBase {
|
||||
$this->assertFieldAttributes($this->entity, $expected_fields);
|
||||
|
||||
// Add a layout override.
|
||||
$this->enableOverrides();
|
||||
$this->entity = $this->reloadEntity($this->entity);
|
||||
$this->entity->get('layout_builder__layout')->appendSection(new Section('layout_onecol'));
|
||||
$this->entity->save();
|
||||
|
||||
|
||||
@@ -32,7 +32,10 @@ class LayoutSectionItemListTest extends SectionStorageTestBase {
|
||||
'bundle' => 'entity_test_base_field_display',
|
||||
'mode' => 'default',
|
||||
'status' => TRUE,
|
||||
])->setOverridable()->save();
|
||||
])
|
||||
->enableLayoutBuilder()
|
||||
->setOverridable()
|
||||
->save();
|
||||
|
||||
array_map(function ($row) {
|
||||
return ['section' => $row];
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
namespace Drupal\Tests\layout_builder\Unit;
|
||||
|
||||
use Drupal\block_content\Access\RefinableDependentAccessInterface;
|
||||
use Drupal\Component\Plugin\Context\ContextInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockManagerInterface;
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\layout_builder\Access\LayoutPreviewAccessAllowed;
|
||||
use Drupal\layout_builder\Event\SectionComponentBuildRenderArrayEvent;
|
||||
use Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray;
|
||||
use Drupal\layout_builder\SectionComponent;
|
||||
@@ -33,6 +38,16 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
*/
|
||||
protected $blockManager;
|
||||
|
||||
/**
|
||||
* Dataprovider for test functions that should test block types.
|
||||
*/
|
||||
public function providerBlockTypes() {
|
||||
return [
|
||||
[TRUE],
|
||||
[FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -44,14 +59,30 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('plugin.manager.block', $this->blockManager->reveal());
|
||||
$container->set('context.handler', $this->prophesize(ContextHandlerInterface::class));
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*
|
||||
* @dataProvider providerBlockTypes
|
||||
*/
|
||||
public function testOnBuildRender() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
public function testOnBuildRender($refinable_dependent_access) {
|
||||
$contexts = [];
|
||||
if ($refinable_dependent_access) {
|
||||
$block = $this->prophesize(TestBlockPluginWithRefinableDependentAccessInterface::class);
|
||||
$layout_entity = $this->prophesize(EntityInterface::class);
|
||||
$layout_entity = $layout_entity->reveal();
|
||||
$context = $this->prophesize(ContextInterface::class);
|
||||
$context->getContextValue()->willReturn($layout_entity);
|
||||
$contexts['layout_builder.entity'] = $context->reveal();
|
||||
|
||||
$block->setAccessDependency($layout_entity)->shouldBeCalled();
|
||||
}
|
||||
else {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
}
|
||||
$access_result = AccessResult::allowed();
|
||||
$block->access($this->account->reveal(), TRUE)->willReturn($access_result)->shouldBeCalled();
|
||||
$block->getCacheContexts()->willReturn([]);
|
||||
@@ -67,7 +98,6 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
$this->blockManager->createInstance('some_block_id', ['id' => 'some_block_id'])->willReturn($block->reveal());
|
||||
|
||||
$component = new SectionComponent('some-uuid', 'some-region', ['id' => 'some_block_id']);
|
||||
$contexts = [];
|
||||
$in_preview = FALSE;
|
||||
$event = new SectionComponentBuildRenderArrayEvent($component, $contexts, $in_preview);
|
||||
|
||||
@@ -100,9 +130,26 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*
|
||||
* @dataProvider providerBlockTypes
|
||||
*/
|
||||
public function testOnBuildRenderDenied() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
public function testOnBuildRenderDenied($refinable_dependent_access) {
|
||||
$contexts = [];
|
||||
if ($refinable_dependent_access) {
|
||||
$block = $this->prophesize(TestBlockPluginWithRefinableDependentAccessInterface::class);
|
||||
|
||||
$layout_entity = $this->prophesize(EntityInterface::class);
|
||||
$layout_entity = $layout_entity->reveal();
|
||||
$context = $this->prophesize(ContextInterface::class);
|
||||
$context->getContextValue()->willReturn($layout_entity);
|
||||
$contexts['layout_builder.entity'] = $context->reveal();
|
||||
|
||||
$block->setAccessDependency($layout_entity)->shouldBeCalled();
|
||||
}
|
||||
else {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
}
|
||||
|
||||
$access_result = AccessResult::forbidden();
|
||||
$block->access($this->account->reveal(), TRUE)->willReturn($access_result)->shouldBeCalled();
|
||||
$block->getCacheContexts()->shouldNotBeCalled();
|
||||
@@ -118,7 +165,6 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
$this->blockManager->createInstance('some_block_id', ['id' => 'some_block_id'])->willReturn($block->reveal());
|
||||
|
||||
$component = new SectionComponent('some-uuid', 'some-region', ['id' => 'some_block_id']);
|
||||
$contexts = [];
|
||||
$in_preview = FALSE;
|
||||
$event = new SectionComponentBuildRenderArrayEvent($component, $contexts, $in_preview);
|
||||
|
||||
@@ -142,9 +188,26 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*
|
||||
* @dataProvider providerBlockTypes
|
||||
*/
|
||||
public function testOnBuildRenderInPreview() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
public function testOnBuildRenderInPreview($refinable_dependent_access) {
|
||||
$contexts = [];
|
||||
if ($refinable_dependent_access) {
|
||||
$block = $this->prophesize(TestBlockPluginWithRefinableDependentAccessInterface::class);
|
||||
$block->setAccessDependency(new LayoutPreviewAccessAllowed())->shouldBeCalled();
|
||||
|
||||
$layout_entity = $this->prophesize(EntityInterface::class);
|
||||
$layout_entity = $layout_entity->reveal();
|
||||
$layout_entity->in_preview = TRUE;
|
||||
$context = $this->prophesize(ContextInterface::class);
|
||||
$context->getContextValue()->willReturn($layout_entity);
|
||||
$contexts['layout_builder.entity'] = $context->reveal();
|
||||
}
|
||||
else {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
}
|
||||
|
||||
$block->access($this->account->reveal(), TRUE)->shouldNotBeCalled();
|
||||
$block->getCacheContexts()->willReturn([]);
|
||||
$block->getCacheTags()->willReturn(['test']);
|
||||
@@ -159,7 +222,6 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
$this->blockManager->createInstance('some_block_id', ['id' => 'some_block_id'])->willReturn($block->reveal());
|
||||
|
||||
$component = new SectionComponent('some-uuid', 'some-region', ['id' => 'some_block_id']);
|
||||
$contexts = [];
|
||||
$in_preview = TRUE;
|
||||
$event = new SectionComponentBuildRenderArrayEvent($component, $contexts, $in_preview);
|
||||
|
||||
@@ -220,3 +282,10 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interface for dependent access block plugins.
|
||||
*/
|
||||
interface TestBlockPluginWithRefinableDependentAccessInterface extends BlockPluginInterface, RefinableDependentAccessInterface {
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderSampleEntityGenerator;
|
||||
use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
|
||||
use Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage;
|
||||
use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
@@ -53,6 +54,30 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
$this->plugin = new DefaultsSectionStorage([], '', $definition, $this->entityTypeManager->reveal(), $entity_type_bundle_info->reveal(), $sample_entity_generator->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getThirdPartySetting
|
||||
* @covers ::setThirdPartySetting
|
||||
*/
|
||||
public function testThirdPartySettings() {
|
||||
// Set an initial value on the section list.
|
||||
$section_list = $this->prophesize(LayoutEntityDisplayInterface::class);
|
||||
$section_list->getThirdPartySetting('the_module', 'the_key', NULL)->willReturn('value 1');
|
||||
$this->plugin->setSectionList($section_list->reveal());
|
||||
|
||||
// The plugin returns the initial value.
|
||||
$this->assertSame('value 1', $this->plugin->getThirdPartySetting('the_module', 'the_key'));
|
||||
|
||||
// When the section list is updated, also update the result returned.
|
||||
$section_list->setThirdPartySetting('the_module', 'the_key', 'value 2')->shouldBeCalled()->will(function ($args) {
|
||||
$this->getThirdPartySetting('the_module', 'the_key', NULL)->willReturn($args[2]);
|
||||
});
|
||||
|
||||
// Update the plugin value.
|
||||
$this->plugin->setThirdPartySetting('the_module', 'the_key', 'value 2');
|
||||
// Assert that the returned value matches.
|
||||
$this->assertSame('value 2', $this->plugin->getThirdPartySetting('the_module', 'the_key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::extractIdFromRoute
|
||||
*
|
||||
@@ -229,6 +254,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_key display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -250,6 +276,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_key display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -271,6 +298,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_key display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -280,6 +308,26 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
'_admin_route' => FALSE,
|
||||
]
|
||||
),
|
||||
'layout_builder.defaults.with_bundle_key.disable' => new Route(
|
||||
'/admin/entity/whatever/display-layout/{view_mode_name}/disable',
|
||||
[
|
||||
'entity_type_id' => 'with_bundle_key',
|
||||
'bundle_key' => 'my_bundle_type',
|
||||
'section_storage_type' => 'defaults',
|
||||
'section_storage' => '',
|
||||
'_form' => '\Drupal\layout_builder\Form\LayoutBuilderDisableForm',
|
||||
],
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_key display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'section_storage' => ['layout_builder_tempstore' => TRUE],
|
||||
],
|
||||
]
|
||||
),
|
||||
'layout_builder.defaults.with_bundle_parameter.view' => new Route(
|
||||
'/admin/entity/{bundle}/display-layout/{view_mode_name}',
|
||||
[
|
||||
@@ -293,6 +341,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -313,6 +362,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -333,6 +383,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -342,6 +393,25 @@ class DefaultsSectionStorageTest extends UnitTestCase {
|
||||
'_admin_route' => FALSE,
|
||||
]
|
||||
),
|
||||
'layout_builder.defaults.with_bundle_parameter.disable' => new Route(
|
||||
'/admin/entity/{bundle}/display-layout/{view_mode_name}/disable',
|
||||
[
|
||||
'entity_type_id' => 'with_bundle_parameter',
|
||||
'section_storage_type' => 'defaults',
|
||||
'section_storage' => '',
|
||||
'_form' => '\Drupal\layout_builder\Form\LayoutBuilderDisableForm',
|
||||
],
|
||||
[
|
||||
'_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
'section_storage' => ['layout_builder_tempstore' => TRUE],
|
||||
],
|
||||
]
|
||||
),
|
||||
];
|
||||
|
||||
$collection = new RouteCollection();
|
||||
|
||||
@@ -223,6 +223,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -242,6 +243,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -261,6 +263,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -280,6 +283,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
],
|
||||
[
|
||||
'parameters' => [
|
||||
@@ -301,6 +305,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
'with_integer_id' => '\d+',
|
||||
],
|
||||
[
|
||||
@@ -321,6 +326,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
'with_integer_id' => '\d+',
|
||||
],
|
||||
[
|
||||
@@ -341,6 +347,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
'with_integer_id' => '\d+',
|
||||
],
|
||||
[
|
||||
@@ -361,6 +368,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
|
||||
],
|
||||
[
|
||||
'_has_layout_section' => 'true',
|
||||
'_layout_builder_access' => 'view',
|
||||
'with_integer_id' => '\d+',
|
||||
],
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user