Revert "Issue #2957425 by tedbow, johndevman, tim.plunkett, hawkeye.twolf, Berdir, alexpott, samuel.mortenson, kevincrafts, jibran, larowlan, amateescu, twfahey, sjerdo, mtodor, japerry, xjm, phenaproxima, mglaman, EclipseGc, johnzzon: Allow the inline creation of non-reusable Custom Blocks in the layout builder"
This reverts commit 500403b458
.
8.7.x
parent
fa6b3d95dd
commit
ba347735e8
|
@ -47,20 +47,3 @@ 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
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
|
||||
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;
|
||||
|
||||
|
@ -64,75 +62,3 @@ function layout_builder_update_8601(&$sandbox) {
|
|||
|
||||
$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_8001() {
|
||||
$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,7 +5,6 @@
|
|||
* 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;
|
||||
|
@ -13,12 +12,10 @@ 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\EntityInterface;
|
||||
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().
|
||||
|
@ -137,68 +134,3 @@ function layout_builder_module_implements_alter(&$implementations, $hook) {
|
|||
$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();
|
||||
}
|
||||
|
|
|
@ -43,6 +43,3 @@ services:
|
|||
logger.channel.layout_builder:
|
||||
parent: logger.channel_base
|
||||
arguments: ['layout_builder']
|
||||
inline_block.usage:
|
||||
class: Drupal\layout_builder\InlineBlockUsage
|
||||
arguments: ['@database']
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
<?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,11 +2,9 @@
|
|||
|
||||
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;
|
||||
|
@ -58,24 +56,6 @@ 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);
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
<?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 content block is used in a layout for a non-revisionable entity the
|
||||
* entity will be returned.
|
||||
*
|
||||
* If the content block is used in a layout for a revisionable entity the
|
||||
* first revision that uses the block will be returned.
|
||||
*
|
||||
* @param \Drupal\block_content\BlockContentInterface $block_content
|
||||
* The block content entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* Returns the layout dependency.
|
||||
*/
|
||||
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 (!$layout_entity->getEntityType()->isRevisionable()) {
|
||||
// Check to see if this revision of the block was used in this entity.
|
||||
// Although the layout builder does not create new block revisions when
|
||||
// the layout entity does not support revisions another module may
|
||||
// have created new revisions for this block.
|
||||
if ($this->isBlockRevisionUsedInEntity($layout_entity, $block_content)) {
|
||||
return $layout_entity;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($this->getEntityRevisionIds($layout_entity) as $revision_id) {
|
||||
$revision = $layout_entity_storage->loadRevision($revision_id);
|
||||
if ($this->isBlockRevisionUsedInEntity($revision, $block_content)) {
|
||||
return $revision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the revision IDs for an entity.
|
||||
*
|
||||
* @todo Move this logic to \Drupal\Core\Entity\Sql\SqlContentEntityStorage in
|
||||
* https://www.drupal.org/project/drupal/issues/2986027.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return int[]
|
||||
* The revision IDs.
|
||||
*/
|
||||
protected function getEntityRevisionIds(EntityInterface $entity) {
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity->getEntityTypeId());
|
||||
if ($revision_table = $entity_type->getRevisionTable()) {
|
||||
$query = $this->database->select($revision_table);
|
||||
$query->condition($entity_type->getKey('id'), $entity->id());
|
||||
$query->fields($revision_table, [$entity_type->getKey('revision')]);
|
||||
$query->orderBy($entity_type->getKey('revision'), 'DESC');
|
||||
return $query->execute()->fetchCol();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
|
@ -103,10 +103,6 @@ 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);
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
<?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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
<?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');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
name: 'CSS Test fix'
|
||||
type: module
|
||||
description: 'Provides CSS fixes for tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -1,5 +0,0 @@
|
|||
drupal.css_fix:
|
||||
version: VERSION
|
||||
css:
|
||||
theme:
|
||||
css/css_fix.theme.css: {}
|
|
@ -1,16 +0,0 @@
|
|||
<?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';
|
||||
}
|
|
@ -122,7 +122,6 @@ 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.
|
||||
|
|
|
@ -1,245 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\file\FileInterface;
|
||||
use Drupal\node\Entity\Node;
|
||||
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();
|
||||
$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);
|
||||
|
||||
$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.
|
||||
$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);
|
||||
|
||||
$node = Node::load(1);
|
||||
$node->setTitle('Update node');
|
||||
$node->setNewRevision();
|
||||
$node->save();
|
||||
|
||||
$file3 = $this->createPrivateFile('3rdFile.txt');
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->replaceFileInBlock($file3);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$private_href3 = $this->assertFileAccessibleOnNode($file3);
|
||||
|
||||
$this->drupalGet($private_href2);
|
||||
$assert_session->pageTextContains('You are not authorized to access this page');
|
||||
|
||||
$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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,431 +0,0 @@
|
|||
<?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');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
<?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')"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,17 +2,12 @@
|
|||
|
||||
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;
|
||||
|
@ -38,16 +33,6 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
|||
*/
|
||||
protected $blockManager;
|
||||
|
||||
/**
|
||||
* Dataprovider for test functions that should test block types.
|
||||
*/
|
||||
public function providerBlockTypes() {
|
||||
return [
|
||||
[TRUE],
|
||||
[FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -59,30 +44,14 @@ 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($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);
|
||||
}
|
||||
public function testOnBuildRender() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
$access_result = AccessResult::allowed();
|
||||
$block->access($this->account->reveal(), TRUE)->willReturn($access_result)->shouldBeCalled();
|
||||
$block->getCacheContexts()->willReturn([]);
|
||||
|
@ -98,6 +67,7 @@ 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);
|
||||
|
||||
|
@ -130,26 +100,9 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
|||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*
|
||||
* @dataProvider providerBlockTypes
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
public function testOnBuildRenderDenied() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
$access_result = AccessResult::forbidden();
|
||||
$block->access($this->account->reveal(), TRUE)->willReturn($access_result)->shouldBeCalled();
|
||||
$block->getCacheContexts()->shouldNotBeCalled();
|
||||
|
@ -165,6 +118,7 @@ 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);
|
||||
|
||||
|
@ -188,26 +142,9 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
|||
|
||||
/**
|
||||
* @covers ::onBuildRender
|
||||
*
|
||||
* @dataProvider providerBlockTypes
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
public function testOnBuildRenderInPreview() {
|
||||
$block = $this->prophesize(BlockPluginInterface::class);
|
||||
$block->access($this->account->reveal(), TRUE)->shouldNotBeCalled();
|
||||
$block->getCacheContexts()->willReturn([]);
|
||||
$block->getCacheTags()->willReturn(['test']);
|
||||
|
@ -222,6 +159,7 @@ 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);
|
||||
|
||||
|
@ -282,10 +220,3 @@ class BlockComponentRenderArrayTest extends UnitTestCase {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interface for dependent access block plugins.
|
||||
*/
|
||||
interface TestBlockPluginWithRefinableDependentAccessInterface extends BlockPluginInterface, RefinableDependentAccessInterface {
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue