Skip to content
Snippets Groups Projects
Commit dd4dcb99 authored by Francesco Pesenti's avatar Francesco Pesenti
Browse files

Check block types permissions

parent 081c8fdf
No related branches found
Tags 7.98
No related merge requests found
block_type_permissions: null
# Schema for the configuration files of the LB+ module.
lb_plus.settings:
type: config_object
label: 'LB+ settings'
mapping:
block_type_permissions:
type: array
label: 'Block type permissions'
#drupal-off-canvas .lb-plus__row {
display: flex;
align-items: center;
border-bottom: 1px solid grey;
border: 1px solid grey;
gap: 10px;
}
#drupal-off-canvas .lb-plus__draggable {
padding: 10px;
margin: 0 !important;
margin: 0 0 10px 0 !important;
}
#drupal-off-canvas .lb-plus__draggable * {
......@@ -27,6 +27,11 @@
filter: invert(1);
}
.lb-plus__draggable:active {
cursor: move;
background: black;
}
.lb_plus__paste {
cursor: pointer;
font-size: 0;
......@@ -76,9 +81,17 @@
}
.layout-builder__region.droppable {
outline: 3px solid #ad0606;
outline: 3px solid #4D7C0F;
}
.layout-builder__region.droppable-hover {
outline: 3px solid red;
outline: 3px solid #20CC16;
}
.lb_plus__not-allowed .layout-builder__region.droppable-hover {
outline: 3px solid #DC2626;
}
.lb_plus__not-allowed .layout-builder__region.droppable-hover .layout-builder__add-block {
opacity: 0.5;
}
......@@ -10,6 +10,8 @@ NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.
let currentRegion = null;
document.addEventListener('dragstart', (event) => {
event.dataTransfer.effectAllowed = 'copyMove';
event.dataTransfer.dropEffect = 'copy';
dragged = event.target;
document.querySelectorAll('.layout-builder__region').forEach((region) => region.classList.add('droppable'));
});
......@@ -17,6 +19,7 @@ NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.
document.addEventListener('dragenter', (event) => {
const lbregion = event.target.classList.contains('layout-builder__region') ? event.target : event.target.closest('.layout-builder__region');
if (lbregion) {
lbregion.classList.remove('lb_plus__not-allowed');
if (currentRegion) {
currentRegion.classList.remove('droppable-hover');
}
......@@ -25,7 +28,37 @@ NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.
}
});
/**
* On hover, if the block type is not allowed for that region, hide the cursor and show a red outline around the
* dragged element.
*/
document.addEventListener('dragover', (event) => {
const lbregion = event.target.classList.contains('layout-builder__layout') ? event.target : event.target.closest('.layout-builder__layout');
if (lbregion) {
const blockType = dragged.dataset.block_id;
const permissions = settings.lb_plus.block_type_permissions;
const regex = /^layout--.+$/gm;
lbregion.classList.forEach((className) => {
if (regex.exec(className)) {
const template = className.replaceAll('--', '_');
if (permissions[template] && permissions[template][blockType] == false) {
event.dataTransfer.dropEffect = 'move';
lbregion.classList.add('lb_plus__not-allowed');
}
else {
event.dataTransfer.dropEffect = 'copy';
lbregion.classList.remove('lb_plus__not-allowed');
}
}
});
}
else {
event.dataTransfer.dropEffect = 'copy';
if (lbregion) {
lbregion.classList.remove('lb_plus__not-allowed');
}
}
event.preventDefault();
});
......@@ -41,6 +74,7 @@ NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.
const lbregion = event.target.classList.contains('layout-builder__region') ? event.target : event.target.closest('.layout-builder__region');
if (lbregion && dragged) {
lbregion.classList.remove('lb_plus__not-allowed');
const target = lbregion.querySelector('.layout-builder__add-block');
const [type, delta, region] = target.dataset.layoutBuilderHighlightId.split('-');
const storage_id = dragged.dataset.storage_id;
......@@ -51,6 +85,16 @@ NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.
};
Drupal.ajax(ajaxSettings).execute();
// Emit an event on drop.
const dropEvent = new CustomEvent('lb_plus_drop', {
detail: {
region: lbregion,
target: target,
dragged: dragged
}
});
document.dispatchEvent(dropEvent);
// Reset the dragged element.
dragged = null;
}
......
......@@ -8,3 +8,4 @@ lb_plus:
dependencies:
- core/drupal.ajax
- core/drupalSettings
- views/views.ajax
lb_plus.layouts:
title: 'LB+ configuration'
description: 'Configure LB+.'
route_name: lb_plus.layouts
parent: system.admin_config_content
weight: 10
......@@ -17,6 +17,8 @@ function lb_plus_page_bottom(array &$page_bottom) {
if ($route_name === 'layout_builder.overrides.node.view') {
$node = \Drupal::routeMatch()->getParameter('node');
$config = \Drupal::config('lb_plus.settings');
$page_bottom['lb_plus_block_selector'] = [
'#type' => 'link',
'#title' => 'Block selector',
......@@ -39,6 +41,11 @@ function lb_plus_page_bottom(array &$page_bottom) {
]
),
'#attached' => [
'drupalSettings' => [
'lb_plus' => [
'block_type_permissions' => $config->get('block_type_permissions'),
],
],
'library' => [
'core/drupal.dialog.ajax',
'lb_plus/lb_plus',
......
administer layout builder plus configuration:
title: 'Administer layout builder plus configuration'
......@@ -57,3 +57,11 @@ lb_plus.autocomplete.categories:
_format: json
requirements:
_permission: 'access content'
lb_plus.layouts:
path: '/admin/config/content/lb_plus_layouts'
defaults:
_form: '\Drupal\lb_plus\Form\LbPlusLayoutsForm'
_title: 'LB+ layouts'
requirements:
_permission: 'administer layout builder plus configuration'
......@@ -5,16 +5,15 @@ namespace Drupal\lb_plus\Controller;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Ajax\AfterCommand;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\InsertCommand;
use Drupal\Core\Ajax\MessageCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Layout\LayoutPluginManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
......@@ -27,6 +26,8 @@ use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\SectionComponent;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\lb_plus\Entity\LbPlusBlockContentSettings;
use Drupal\lb_plus\Mixin\LbPlusTrait;
use Drupal\views\Ajax\ScrollTopCommand;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\block_content\Entity\BlockContent;
......@@ -34,6 +35,7 @@ use Drupal\block_content\Entity\BlockContent;
class LbPlusBlockController extends ChooseBlockController implements ContainerInjectionInterface {
use LayoutRebuildTrait;
use LbPlusTrait;
/**
* The layout tempstore repository.
......@@ -51,19 +53,27 @@ class LbPlusBlockController extends ChooseBlockController implements ContainerI
protected $moduleList;
/**
* The layout manager.
*
* @var \Drupal\Core\Layout\LayoutPluginManagerInterface
*/
protected $layoutManager;
/**
* LayoutController constructor.
*
* @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
* The layout tempstore repository.
*/
public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, UuidInterface $uuid, BlockManagerInterface $block_manager, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user, ModuleExtensionList $moduleList) {
public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, UuidInterface $uuid, BlockManagerInterface $block_manager, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user, ModuleExtensionList $moduleList, LayoutPluginManagerInterface $layout_manager) {
$this->layoutTempstoreRepository = $layout_tempstore_repository;
$this->uuidGenerator = $uuid;
$this->blockManager = $block_manager;
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $current_user;
$this->moduleList = $moduleList;
$this->layoutManager = $layout_manager;
}
/**
......@@ -76,7 +86,8 @@ class LbPlusBlockController extends ChooseBlockController implements ContainerI
$container->get('plugin.manager.block'),
$container->get('entity_type.manager'),
$container->get('current_user'),
$container->get('extension.list.module')
$container->get('extension.list.module'),
$container->get('plugin.manager.core.layout')
);
}
......@@ -92,63 +103,75 @@ class LbPlusBlockController extends ChooseBlockController implements ContainerI
$build = [];
$block_categories['#type'] = 'container';
$types = BlockContentType::loadMultiple();
asort($types);
$types = $this->getAvailableBlockTypes();
$config = \Drupal::config('lb_plus.settings');
// Collects available types for layouts.
$block_type_permissions = $config->get('block_type_permissions');
$available_types = [];
foreach ($section_storage->getSections() as $section) {
if (!empty($block_type_permissions[$section->getLayoutId()])) {
foreach ($block_type_permissions[$section->getLayoutId()] as $type => $value) {
if ($value) {
$available_types[$type] = $type;
}
}
}
}
foreach ($types as $type) {
// Skip unavailable types.
if (empty($available_types[$type->id()])) {
continue;
}
$config = LbPlusBlockContentSettings::load($type->id());
if ($config && $config->get('expose')) {
$category = $config->get('category') ?: $this->t('Default')->render();
if (empty( $block_categories[$category])) {
$block_categories[$category] = [
'#type' => 'container',
[
[
'#type' => 'item',
'#markup' => $category,
],
]
];
}
$category = $config->get('category') ?: $this->t('Default')->render();
$icon_path = $this->moduleList->getPath('lb_plus') . '/images/default.svg';
if (empty($block_categories[$category])) {
$block_categories[$category] = [
'#type' => 'details',
'#open' => TRUE,
'#title' => $category,
];
}
if ($config->get('image') && ($file = File::load($config->get('image')))) {
$icon_path = $file->getFileUri();
}
$icon_path = $this->moduleList->getPath('lb_plus') . '/images/default.svg';
if ($config->get('image') && ($file = File::load($config->get('image')))) {
$icon_path = $file->getFileUri();
}
$block_categories[$category]['items'][] = [
'#type' => 'container',
$block_categories[$category][] = [
'#type' => 'container',
'#attributes' => [
'draggable' => 'true',
'data-block_id' => $type->id(),
'data-storage_id' => $section_storage->getStorageId(),
'class' => [
'lb-plus__draggable',
'lb-plus__row',
],
],
[
'#theme' => 'image',
'#uri' => $icon_path,
'#alt' => $type->label(),
'#title' => $type->label(),
'#attributes' => [
'draggable' => 'true',
'data-block_id' => $type->id(),
'data-storage_id' => $section_storage->getStorageId(),
'width' => '20',
'height' => '20',
'style' => 'width: 20px; height: 20px; overflow: hidden;',
'class' => [
'lb-plus__draggable',
'lb-plus__row',
],
],
[
'#theme' => 'image',
'#uri' => $icon_path,
'#alt' => $type->label(),
'#title' => $type->label(),
'#attributes' => [
'width' => '20',
'height' => '20',
'style' => 'width: 20px; height: 20px; overflow: hidden;',
'class' => [
'icon',
],
'icon',
],
],
[
'#type' => 'item',
'#markup' => $type->label(),
],
];
}
],
[
'#type' => 'item',
'#markup' => $type->label(),
],
];
}
if (Element::children($block_categories)) {
......@@ -233,47 +256,69 @@ class LbPlusBlockController extends ChooseBlockController implements ContainerI
* @return mixed
*/
public function addBlock(SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $plugin_id = NULL) {
$block = BlockContent::create([
'info' => new FormattableMarkup('Block @plugin_id for @region region of @label', [
'@plugin_id' => $plugin_id,
'@region' => $region,
'@label' => $section_storage->label(),
]),
'type' => $plugin_id,
]);
// Generate sample data for required fields without default value.
foreach ($block->getFields(FALSE) as $field) {
$data_definition = $field->getDataDefinition();
$field_definition = $field->getFieldDefinition();
if ($data_definition instanceof FieldConfig) {
$default_value = NULL;
// Image field special case.
if ($field_definition instanceof FieldConfigInterface && $field_definition->getType() == 'image') {
$settings = $field_definition->getSetting('default_image');
$default_value = !empty($settings['uuid']);
}
else {
$default_value = $field->getValue();
}
if ($data_definition->get('required') && empty($default_value)) {
$field->generateSampleItems();
// Check if current block type can be dropped into current layout.
$config = \Drupal::config('lb_plus.settings');
$block_type_permissions = $config->get('block_type_permissions');
$layout_id = $section_storage->getSection($delta)->getLayoutId();
$access = !empty($block_type_permissions[$layout_id][$plugin_id]);
if ($access) {
$block = BlockContent::create([
'info' => new FormattableMarkup('Block @plugin_id for @region region of @label', [
'@plugin_id' => $plugin_id,
'@region' => $region,
'@label' => $section_storage->label(),
]),
'type' => $plugin_id,
]);
// Generate sample data for required fields without default value.
foreach ($block->getFields(FALSE) as $field) {
$data_definition = $field->getDataDefinition();
$field_definition = $field->getFieldDefinition();
if ($data_definition instanceof FieldConfig) {
$default_value = NULL;
// Image field special case.
if ($field_definition instanceof FieldConfigInterface && $field_definition->getType() == 'image') {
$settings = $field_definition->getSetting('default_image');
$default_value = !empty($settings['uuid']);
}
else {
$default_value = $field->getValue();
}
if ($data_definition->get('required') && empty($default_value)) {
$field->generateSampleItems();
}
}
}
}
$block->setReusable();
$block->setReusable();
try {
if ($block->save()) {
$section = $section_storage->getSection($delta);
$component = new SectionComponent($this->uuidGenerator->generate(), $region, ['id' => 'block_content:' . $block->uuid()]);
$section->appendComponent($component);
$this->layoutTempstoreRepository->set($section_storage);
try {
if ($block->save()) {
$section = $section_storage->getSection($delta);
$component = new SectionComponent($this->uuidGenerator->generate(), $region, ['id' => 'block_content:' . $block->uuid()]);
$section->appendComponent($component);
$this->layoutTempstoreRepository->set($section_storage);
}
} catch (\Exception $e) {
}
} catch (\Exception $e) {}
return $this->rebuildLayout($section_storage);
return $this->rebuildLayout($section_storage);
}
else {
$response = new AjaxResponse();
$block_type = BlockContentType::load($plugin_id);
$layout = $section_storage->getSection($delta)->getLayout();
$message = $this->t('The "@block_type" block type is not permitted for the "@section_type" layout section.', [
'@block_type' => $block_type->label(),
'@section_type' => $layout->getPluginDefinition()->getLabel(),
]);
$selector = '#node-page-layout-builder-form';
$response->addCommand(new MessageCommand($message, $selector, ['type' => 'error'], TRUE));
$response->addCommand(new ScrollTopCommand($selector));
return $response;
}
}
......
<?php
namespace Drupal\lb_plus\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Layout\LayoutPluginManagerInterface;
use Drupal\lb_plus\Mixin\LbPlusTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class LbPlusLayoutsForm extends ConfigFormBase {
use LbPlusTrait;
/**
* The layout manager.
*
* @var \Drupal\Core\Layout\LayoutPluginManagerInterface
*/
protected $layoutManager;
/**
* ChooseSectionController constructor.
*
* @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_manager
* The layout manager.
*/
public function __construct(LayoutPluginManagerInterface $layout_manager) {
$this->layoutManager = $layout_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.core.layout')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'lb_plus_layouts_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['lb_plus.layouts'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('lb_plus.settings')->get('block_type_permissions');
$form['#tree'] = TRUE;
$form['block_type_permissions'] = ['#type' => 'container'];
$definitions = $this->layoutManager->getDefinitions();
$types = $this->getAvailableBlockTypes();
if (!empty($definitions) && !empty($types)) {
foreach ($definitions as $definition_key => $definition) {
if ($definition_key !== 'layout_builder_blank') {
$form['block_type_permissions'][$definition_key] = [
'#type' => 'details',
'#open' => TRUE,
'#title' => $definition->getLabel(),
];
foreach ($types as $type) {
$form['block_type_permissions'][$definition_key][$type->id()] = [
'#type' => 'checkbox',
'#title' => $type->label(),
'#default_value' => !empty($config[$definition_key][$type->id()]),
];
}
}
}
}
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValue(['block_type_permissions']);
$config = \Drupal::service('config.factory')->getEditable('lb_plus.settings');
$config->set('block_type_permissions', $values)->save();
}
}
<?php
namespace Drupal\lb_plus\Mixin;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\lb_plus\Entity\LbPlusBlockContentSettings;
trait LbPlusTrait {
protected function getAvailableBlockTypes() {
$available_types = [];
$types = BlockContentType::loadMultiple();
asort($types);
foreach ($types as $type) {
$config = LbPlusBlockContentSettings::load($type->id());
if ($config && $config->get('expose')) {
$available_types[] = $type;
}
}
return $available_types;
}
}
<?php
namespace Drupal\lb_plus\Trait;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\lb_plus\Entity\LbPlusBlockContentSettings;
trait LbPlusTrait {
protected function getAvailableBlockTypes() {
$available_types = [];
$types = BlockContentType::loadMultiple();
asort($types);
foreach ($types as $type) {
$config = LbPlusBlockContentSettings::load($type->id());
if ($config && $config->get('expose')) {
$available_types[] = $type;
}
}
return $available_types;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment