Commit 08d676a2 authored by xjm's avatar xjm

Revert "Issue #2905922 by tim.plunkett, xjm, EclipseGc, vijaycs85, webchick,...

Revert "Issue #2905922 by tim.plunkett, xjm, EclipseGc, vijaycs85, webchick, larowlan, andrewmacpherson, Bojhan, droplet, mgifford, drpal, japerry, tedbow, DyanneNova, phenaproxima: Implementation issue for Layout Builder"

This reverts commit 88a22dda.
parent 916e6ba9
......@@ -112,7 +112,6 @@
"drupal/image": "self.version",
"drupal/inline_form_errors": "self.version",
"drupal/language": "self.version",
"drupal/layout_builder": "self.version",
"drupal/layout_discovery": "self.version",
"drupal/link": "self.version",
"drupal/locale": "self.version",
......
core.entity_view_display.*.*.*.third_party.layout_builder:
type: mapping
label: 'Per-view-mode Layout Builder settings'
mapping:
allow_custom:
type: boolean
label: 'Allow a customized layout'
.add-section {
width: 100%;
outline: 2px dashed #979797;
padding: 1.5em 0;
text-align: center;
margin-bottom: 1.5em;
transition: visually-hidden 2s ease-out, height 2s ease-in;
}
.layout-section {
margin-bottom: 1.5em;
}
.layout-section .layout-builder--layout__region {
outline: 2px dashed #2f91da;
padding: 1.5em 0;
}
.layout-section .layout-builder--layout__region .add-block {
text-align: center;
}
.layout-section .remove-section {
position: relative;
background: url(../../../misc/icons/bebebe/ex.svg) #ffffff center center / 16px 16px no-repeat;
border: 1px solid #cccccc;
box-sizing: border-box;
font-size: 1rem;
padding: 0;
height: 26px;
width: 26px;
white-space: nowrap;
text-indent: -9999px;
display: inline-block;
border-radius: 26px;
margin-left: -10px;
}
.layout-section .remove-section:hover {
background-image: url(../../../misc/icons/787878/ex.svg);
}
#drupal-off-canvas .layout-selection li {
display: block;
padding-bottom: 1em;
}
#drupal-off-canvas .layout-selection li a {
display: block;
padding-top: 0.55em;
}
(($, { ajax, behaviors }) => {
behaviors.layoutBuilder = {
attach(context) {
$(context).find('.layout-builder--layout__region').sortable({
items: '> .draggable',
connectWith: '.layout-builder--layout__region',
/**
* 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) {
// Only process if the item was moved from one region to another.
if (ui.sender) {
ajax({
url: [
ui.item.closest('[data-layout-update-url]').data('layout-update-url'),
ui.sender.closest('[data-layout-delta]').data('layout-delta'),
ui.item.closest('[data-layout-delta]').data('layout-delta'),
ui.sender.data('region'),
$(this).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);
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, _ref) {
var ajax = _ref.ajax,
behaviors = _ref.behaviors;
behaviors.layoutBuilder = {
attach: function attach(context) {
$(context).find('.layout-builder--layout__region').sortable({
items: '> .draggable',
connectWith: '.layout-builder--layout__region',
update: function update(event, ui) {
if (ui.sender) {
ajax({
url: [ui.item.closest('[data-layout-update-url]').data('layout-update-url'), ui.sender.closest('[data-layout-delta]').data('layout-delta'), ui.item.closest('[data-layout-delta]').data('layout-delta'), ui.sender.data('region'), $(this).data('region'), ui.item.data('layout-block-uuid'), ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid')].filter(function (element) {
return element !== undefined;
}).join('/')
}).execute();
}
}
});
}
};
})(jQuery, Drupal);
\ No newline at end of file
name: 'Layout Builder'
type: module
description: 'Provides layout building utility.'
package: Core (Experimental)
version: VERSION
core: 8.x
dependencies:
- layout_discovery
- contextual
drupal.layout_builder:
version: VERSION
css:
theme:
css/layout-builder.css: {}
js:
js/layout-builder.js: {}
dependencies:
- core/jquery.ui.sortable
- core/drupal.dialog.off_canvas
layout_builder_block_update:
title: 'Configure'
route_name: 'layout_builder.update_block'
group: 'layout_builder_block'
options:
attributes:
class: ['use-ajax']
data-dialog-type: dialog
data-dialog-renderer: off_canvas
layout_builder_block_remove:
title: 'Remove block'
route_name: 'layout_builder.remove_block'
group: 'layout_builder_block'
options:
attributes:
class: ['use-ajax']
data-dialog-type: dialog
data-dialog-renderer: off_canvas
layout_builder_ui:
deriver: '\Drupal\layout_builder\Plugin\Derivative\LayoutBuilderLocalTaskDeriver'
<?php
/**
* @file
* Provides hook implementations for Layout Builder.
*/
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Implements hook_help().
*/
function layout_builder_help($route_name) {
switch ($route_name) {
case 'help.page.layout_builder':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Layout Builder provides layout building utility.') . '</p>';
$output .= '<p>' . t('For more information, see the <a href=":layout-builder-documentation">online documentation for the Layout Builder module</a>.', [':layout-builder-documentation' => 'https://www.drupal.org/docs/8/core/modules/layout_builder']) . '</p>';
return $output;
}
}
/**
* Implements hook_entity_type_alter().
*/
function layout_builder_entity_type_alter(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
foreach ($entity_types as $entity_type) {
if ($entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasLinkTemplate('canonical') && $entity_type->hasViewBuilderClass()) {
$entity_type->setLinkTemplate('layout-builder', $entity_type->getLinkTemplate('canonical') . '/layout');
}
}
}
/**
* Removes the Layout Builder field both visually and from the #fields handling.
*
* This prevents any interaction with this field. It is rendered directly
* in layout_builder_entity_view_display_alter().
*
* @internal
*/
function _layout_builder_hide_layout_field(array &$form) {
unset($form['fields']['layout_builder__layout']);
$key = array_search('layout_builder__layout', $form['#fields']);
if ($key !== FALSE) {
unset($form['#fields'][$key]);
}
}
/**
* Implements hook_form_FORM_ID_alter() for \Drupal\field_ui\Form\EntityFormDisplayEditForm.
*/
function layout_builder_form_entity_form_display_edit_form_alter(&$form, FormStateInterface $form_state) {
_layout_builder_hide_layout_field($form);
}
/**
* Implements hook_form_FORM_ID_alter() for \Drupal\field_ui\Form\EntityViewDisplayEditForm.
*/
function layout_builder_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
$display = $form_state->getFormObject()->getEntity();
$entity_type = \Drupal::entityTypeManager()->getDefinition($display->getTargetEntityTypeId());
_layout_builder_hide_layout_field($form);
// @todo Expand to work for all view modes in
// https://www.drupal.org/node/2907413.
if (!in_array($display->getMode(), ['full', 'default'], TRUE)) {
return;
}
$form['layout'] = [
'#type' => 'details',
'#open' => TRUE,
'#title' => t('Layout options'),
'#tree' => TRUE,
];
// @todo Unchecking this box is a destructive action, this should be made
// clear to the user in https://www.drupal.org/node/2914484.
$form['layout']['allow_custom'] = [
'#type' => 'checkbox',
'#title' => t('Allow each @entity to have its layout customized.', [
'@entity' => $entity_type->getSingularLabel(),
]),
'#default_value' => $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE),
];
$form['#entity_builders'][] = 'layout_builder_form_entity_view_display_edit_entity_builder';
}
/**
* Entity builder for layout options on the entity view display form.
*
* @see layout_builder_form_entity_view_display_edit_form_alter()
*/
function layout_builder_form_entity_view_display_edit_entity_builder($entity_type_id, EntityViewDisplayInterface $display, &$form, FormStateInterface &$form_state) {
$new_value = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE);
$display->setThirdPartySetting('layout_builder', 'allow_custom', $new_value);
}
/**
* Implements hook_ENTITY_TYPE_presave().
*/
function layout_builder_entity_view_display_presave(EntityViewDisplayInterface $display) {
$original_value = isset($display->original) ? $display->original->getThirdPartySetting('layout_builder', 'allow_custom', FALSE) : FALSE;
$new_value = $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE);
if ($original_value !== $new_value) {
$entity_type_id = $display->getTargetEntityTypeId();
$bundle = $display->getTargetBundle();
if ($new_value) {
layout_builder_add_layout_section_field($entity_type_id, $bundle);
}
elseif ($field = FieldConfig::loadByName($entity_type_id, $bundle, 'layout_builder__layout')) {
$field->delete();
}
}
}
/**
* Adds a layout section field to a given bundle.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $bundle
* The bundle.
* @param string $field_name
* (optional) The name for the layout section field. Defaults to
* 'layout_builder__layout'.
*
* @return \Drupal\field\FieldConfigInterface
* A layout section field.
*/
function layout_builder_add_layout_section_field($entity_type_id, $bundle, $field_name = 'layout_builder__layout') {
$field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
if (!$field) {
$field_storage = FieldStorageConfig::loadByName($entity_type_id, $field_name);
if (!$field_storage) {
$field_storage = FieldStorageConfig::create([
'entity_type' => $entity_type_id,
'field_name' => $field_name,
'type' => 'layout_section',
]);
$field_storage->save();
}
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $bundle,
'label' => t('Layout'),
]);
$field->save();
}
return $field;
}
/**
* Implements hook_entity_view_display_alter().
*/
function layout_builder_entity_view_display_alter(EntityViewDisplayInterface $display, array $context) {
if ($display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE)) {
// Force the layout to render with no label.
$display->setComponent('layout_builder__layout', [
'label' => 'hidden',
'region' => '__layout_builder',
]);
}
else {
$display->removeComponent('layout_builder__layout');
}
}
/**
* Implements hook_entity_view_alter().
*/
function layout_builder_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
if ($display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE) && !$entity->layout_builder__layout->isEmpty()) {
// If field layout is active, that is all that needs to be removed.
if (\Drupal::moduleHandler()->moduleExists('field_layout') && isset($build['_field_layout'])) {
unset($build['_field_layout']);
return;
}
/** @var \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions */
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle());
// Remove all display-configurable fields.
foreach (array_keys($display->getComponents()) as $name) {
if ($name !== 'layout_builder__layout' && isset($field_definitions[$name]) && $field_definitions[$name]->isDisplayConfigurable('view')) {
unset($build[$name]);
}
}
}
}
# @todo Expand permissions to be more granular in
# https://www.drupal.org/node/2914486.
configure any layout:
title: 'Configure any layout'
restrict access: true
layout_builder.choose_section:
path: '/layout_builder/choose/section/{entity_type_id}/{entity}/{delta}'
defaults:
_controller: '\Drupal\layout_builder\Controller\ChooseSectionController::build'
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.add_section:
path: '/layout_builder/add/section/{entity_type_id}/{entity}/{delta}/{plugin_id}'
defaults:
_controller: '\Drupal\layout_builder\Controller\AddSectionController::build'
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.configure_section:
path: '/layout_builder/configure/section/{entity_type_id}/{entity}/{delta}/{plugin_id}'
defaults:
_title: 'Configure section'
_form: '\Drupal\layout_builder\Form\ConfigureSectionForm'
# Adding a new section requires a plugin_id, while configuring an existing
# section does not.
plugin_id: null
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.remove_section:
path: '/layout_builder/remove/section/{entity_type_id}/{entity}/{delta}'
defaults:
_form: '\Drupal\layout_builder\Form\RemoveSectionForm'
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.choose_block:
path: '/layout_builder/choose/block/{entity_type_id}/{entity}/{delta}/{region}'
defaults:
_controller: '\Drupal\layout_builder\Controller\ChooseBlockController::build'
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.add_block:
path: '/layout_builder/add/block/{entity_type_id}/{entity}/{delta}/{region}/{plugin_id}'
defaults:
_form: '\Drupal\layout_builder\Form\AddBlockForm'
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.update_block:
path: '/layout_builder/update/block/{entity_type_id}/{entity}/{delta}/{region}/{uuid}'
defaults:
_form: '\Drupal\layout_builder\Form\UpdateBlockForm'
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.remove_block:
path: '/layout_builder/remove/block/{entity_type_id}/{entity}/{delta}/{region}/{uuid}'
defaults:
_form: '\Drupal\layout_builder\Form\RemoveBlockForm'
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
layout_builder.move_block:
path: '/layout_builder/move/block/{entity_type_id}/{entity}/{delta_from}/{delta_to}/{region_from}/{region_to}/{block_uuid}/{preceding_block_uuid}'
defaults:
_controller: '\Drupal\layout_builder\Controller\MoveBlockController::build'
delta_from: null
delta_to: null
region_from: null
region_to: null
block_uuid: null
preceding_block_uuid: null
requirements:
_permission: 'configure any layout'
options:
_admin_route: TRUE
parameters:
entity:
type: entity:{entity_type_id}
layout_builder_tempstore: TRUE
route_callbacks:
- 'layout_builder.routes:getRoutes'
services:
layout_builder.builder:
class: Drupal\layout_builder\LayoutSectionBuilder
arguments: ['@current_user', '@plugin.manager.core.layout', '@plugin.manager.block', '@context.handler', '@context.repository']
layout_builder.tempstore_repository:
class: Drupal\layout_builder\LayoutTempstoreRepository
arguments: ['@user.shared_tempstore', '@entity_type.manager']
access_check.entity.layout:
class: Drupal\layout_builder\Access\LayoutSectionAccessCheck
arguments: ['@entity_type.manager']
tags:
- { name: access_check, applies_to: _has_layout_section }
layout_builder.routes:
class: Drupal\layout_builder\Routing\LayoutBuilderRoutes
arguments: ['@entity_type.manager', '@entity_field.manager']
layout_builder.route_enhancer:
class: Drupal\layout_builder\Routing\LayoutBuilderRouteEnhancer
arguments: ['@entity_type.manager']
tags:
- { name: route_enhancer }
layout_builder.param_converter:
class: Drupal\layout_builder\Routing\LayoutTempstoreParamConverter
arguments: ['@entity.manager', '@layout_builder.tempstore_repository']
tags:
- { name: paramconverter, priority: 10 }
cache_context.layout_builder_is_active:
class: Drupal\layout_builder\Cache\LayoutBuilderIsActiveCacheContext
arguments: ['@current_route_match']
tags:
- { name: cache.context}
<?php
namespace Drupal\layout_builder\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Provides an access check for the Layout Builder UI.
*
* @internal
*/
class LayoutSectionAccessCheck implements AccessInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new LayoutSectionAccessCheck.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* Checks routing access to layout for the entity.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(RouteMatchInterface $route_match, AccountInterface $account) {
// Attempt to retrieve the generic 'entity' parameter, otherwise look up the
// specific entity via the entity type ID.
$entity = $route_match->getParameter('entity') ?: $route_match->getParameter($route_match->getParameter('entity_type_id'));
// If we don't have an entity, forbid access.
if (empty($entity)) {
return AccessResult::forbidden()->addCacheContexts(['route']);
}
// If the entity isn't fieldable, forbid access.
if (!$entity instanceof FieldableEntityInterface || !$entity->hasField('layout_builder__layout')) {
$access = AccessResult::forbidden();
}
else {
$access = AccessResult::allowedIfHasPermission($account, 'configure any layout');
}
return $access->addCacheableDependency($entity);
}
}
<?php
namespace Drupal\layout_builder\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CalculatedCacheContextInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Determines whether Layout Builder is active for a given entity type or not.
*
* Cache context ID: 'layout_builder_is_active:%entity_type_id', e.g.
* 'layout_builder_is_active:node' (to vary by whether the Node entity type has
* Layout Builder enabled).
*/
class LayoutBuilderIsActiveCacheContext implements CalculatedCacheContextInterface {
/**
* The current route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* LayoutBuilderCacheContext constructor.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t('Layout Builder');
}
/**
* {@inheritdoc}
*/
public function getContext($entity_type_id = NULL) {
if (!$entity_type_id) {
throw new \LogicException('Missing entity type ID');
}
$display = $this->getDisplay($entity_type_id);
return ($display && $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE)) ? '1' : '0';
}
/**
* {@inheritdoc}
*/
public function getCacheableMetadata($entity_type_id = NULL) {